Skip to content

Commit b1a6ae6

Browse files
committed
add simple decorator support
1 parent a45af9a commit b1a6ae6

File tree

10 files changed

+77
-77
lines changed

10 files changed

+77
-77
lines changed

src/Symfony/Bridge/Doctrine/Tests/Decorator/DoctrineTransactionDecoratorTest.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Bridge\Doctrine\Attribute\Transactional;
1919
use Symfony\Bridge\Doctrine\Decorator\DoctrineTransactionDecorator;
2020
use Symfony\Component\Decorator\DecoratorChain;
21+
use Symfony\Component\Decorator\DecoratorLocator;
2122

2223
class DoctrineTransactionDecoratorTest extends TestCase
2324
{
@@ -36,9 +37,9 @@ protected function setUp(): void
3637
$this->managerRegistry = $this->createMock(ManagerRegistry::class);
3738
$this->managerRegistry->method('getManager')->willReturn($this->entityManager);
3839

39-
$this->decorator = DecoratorChain::from([
40+
$this->decorator = new DecoratorChain(new DecoratorLocator([
4041
DoctrineTransactionDecorator::class => fn () => new DoctrineTransactionDecorator($this->managerRegistry),
41-
]);
42+
]));
4243
}
4344

4445
public function testDecoratorWrapsInTransactionAndFlushes()

src/Symfony/Component/Decorator/Attribute/Decorate.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
class Decorate
2323
{
2424
/**
25-
* @param string $id The decorator service id
25+
* @param string $id The decorator identifier
2626
* @param array<string, mixed> $options The decorator options to pass through
2727
*/
2828
public function __construct(

src/Symfony/Component/Decorator/DecoratorChain.php

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
use Psr\Container\ContainerInterface;
1515
use Symfony\Component\Decorator\Attribute\Decorate;
16-
use Symfony\Contracts\Service\ServiceLocatorTrait;
1716

1817
/**
1918
* @author Yonel Ceruto <open@yceruto.dev>
@@ -23,23 +22,13 @@
2322
class DecoratorChain implements DecoratorInterface
2423
{
2524
/**
26-
* @param ContainerInterface $decorators Locator of decorators, keyed by service id
25+
* @param ContainerInterface $decorators Locator of decorators, keyed by identifier
2726
*/
2827
public function __construct(
29-
private readonly ContainerInterface $decorators,
28+
private readonly ContainerInterface $decorators = new DecoratorLocator([]),
3029
) {
3130
}
3231

33-
/**
34-
* @param array<string, callable(): DecoratorInterface> $decorators
35-
*/
36-
public static function from(array $decorators): self
37-
{
38-
return new self(new class($decorators) implements ContainerInterface {
39-
use ServiceLocatorTrait;
40-
});
41-
}
42-
4332
public function call(callable $callable, mixed ...$args): mixed
4433
{
4534
return $this->decorate($callable(...))(...$args);
@@ -48,7 +37,13 @@ public function call(callable $callable, mixed ...$args): mixed
4837
public function decorate(\Closure $func): \Closure
4938
{
5039
foreach ($this->getAttributes($func) as $attribute) {
51-
$func = $this->decorators->get($attribute->id)->decorate($func, ...$attribute->options);
40+
if ($attribute instanceof DecoratorInterface && !$this->decorators->has($attribute->id)) {
41+
$decorator = $attribute;
42+
} else {
43+
$decorator = $this->decorators->get($attribute->id);
44+
}
45+
46+
$func = $decorator->decorate($func, ...$attribute->options);
5247
}
5348

5449
return $func;

src/Symfony/Component/Decorator/DecoratorInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
namespace Symfony\Component\Decorator;
1313

1414
/**
15-
* Decorates the functionality of other function.
15+
* Decorates the functionality of a given function.
1616
*
1717
* @author Yonel Ceruto <open@yceruto.dev>
1818
*
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Decorator;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Contracts\Service\ServiceLocatorTrait;
16+
17+
/**
18+
* @author Yonel Ceruto <open@yceruto.dev>
19+
*/
20+
class DecoratorLocator implements ContainerInterface
21+
{
22+
use ServiceLocatorTrait {
23+
get as private doGet;
24+
}
25+
26+
public function get(string $id): DecoratorInterface
27+
{
28+
return $this->doGet($id);
29+
}
30+
}

src/Symfony/Component/Decorator/README.md

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,51 +21,46 @@ use Symfony\Component\Decorator\Attribute\Decorate;
2121
use Symfony\Component\Decorator\DecoratorChain;
2222
use Symfony\Component\Decorator\DecoratorInterface;
2323

24-
class DebugDecorator implements DecoratorInterface
24+
#[\Attribute(\Attribute::TARGET_METHOD)]
25+
class MyDecorator extends Decorate implements DecoratorInterface
2526
{
27+
public function __construct()
28+
{
29+
parent::__construct(self::class);
30+
}
31+
2632
public function decorate(\Closure $func): \Closure
2733
{
28-
return function (mixed ...$args) use ($func): mixed {
34+
return function (mixed ...$args) use ($func): mixed
35+
{
2936
echo "Do something before\n";
3037

3138
$result = $func(...$args);
3239

3340
echo "Do something after\n";
3441

3542
return $result;
36-
}
37-
}
38-
}
39-
40-
$decorator = DecoratorChain::from([
41-
DebugDecorator::class => fn () => new DebugDecorator(),
42-
]);
43-
44-
#[\Attribute(\Attribute::TARGET_METHOD)]
45-
class Debug extends Decorate
46-
{
47-
public function __construct()
48-
{
49-
parent::__construct(DebugDecorator::class);
43+
};
5044
}
5145
}
5246

5347
class Greeting
5448
{
55-
#[Debug]
56-
public function hello(string $name): void
49+
#[MyDecorator]
50+
public function sayHello(string $name): void
5751
{
58-
echo "Hello $name\n"
52+
echo "Hello $name!\n";
5953
}
6054
}
61-
6255
$greeting = new Greeting();
63-
$decorator->call($greeting->hello(...), 'Fabien');
56+
57+
$decorator = new DecoratorChain();
58+
$decorator->call($greeting->sayHello(...), 'Fabien');
6459
```
6560
Output:
6661
```
6762
Do something before
68-
Hello Fabien
63+
Hello Fabien!
6964
Do something after
7065
```
7166

src/Symfony/Component/Decorator/Tests/DecoratorChainTest.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Decorator\DecoratorChain;
16+
use Symfony\Component\Decorator\DecoratorLocator;
1617
use Symfony\Component\Decorator\Tests\Fixtures\Controller\CreateTaskController;
17-
use Symfony\Component\Decorator\Tests\Fixtures\Decorator\JsonDecorator;
1818
use Symfony\Component\Decorator\Tests\Fixtures\Decorator\Logging;
1919
use Symfony\Component\Decorator\Tests\Fixtures\Decorator\LoggingDecorator;
2020
use Symfony\Component\Decorator\Tests\Fixtures\Handler\Message;
@@ -29,10 +29,9 @@ class DecoratorChainTest extends TestCase
2929
protected function setUp(): void
3030
{
3131
$this->logger = new TestLogger();
32-
$this->decorator = DecoratorChain::from([
33-
JsonDecorator::class => fn () => new JsonDecorator(),
32+
$this->decorator = new DecoratorChain(new DecoratorLocator([
3433
LoggingDecorator::class => fn () => new LoggingDecorator($this->logger),
35-
]);
34+
]));
3635
}
3736

3837
public function testTopDecoratedFunc()

src/Symfony/Component/Decorator/Tests/DependencyInjection/DecoratorPassTest.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Decorator\DecoratorChain;
1616
use Symfony\Component\Decorator\DependencyInjection\DecoratorsPass;
17-
use Symfony\Component\Decorator\Tests\Fixtures\Decorator\JsonDecorator;
1817
use Symfony\Component\Decorator\Tests\Fixtures\Decorator\LoggingDecorator;
1918
use Symfony\Component\Decorator\Tests\Fixtures\Handler\Message;
2019
use Symfony\Component\Decorator\Tests\Fixtures\Handler\MessageHandler;
@@ -36,7 +35,7 @@ public function testDefinition()
3635
$argument = $container->getDefinition('decorator.chain')->getArgument(0);
3736

3837
$this->assertInstanceOf(ServiceLocatorArgument::class, $argument);
39-
$this->assertSame(['decorator.json', LoggingDecorator::class], array_keys($argument->getValues()));
38+
$this->assertSame([LoggingDecorator::class], array_keys($argument->getValues()));
4039
}
4140

4241
public function testService()
@@ -76,9 +75,6 @@ private function getDefinition(): ContainerBuilder
7675
->addArgument(new AbstractArgument('set in DecoratorPass'))
7776
->setPublic(true);
7877

79-
$container->register('decorator.json', JsonDecorator::class)
80-
->addTag('decorator');
81-
8278
$container->register(TestLogger::class)
8379
->setPublic(true);
8480

src/Symfony/Component/Decorator/Tests/Fixtures/Decorator/Json.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,22 @@
1212
namespace Symfony\Component\Decorator\Tests\Fixtures\Decorator;
1313

1414
use Symfony\Component\Decorator\Attribute\Decorate;
15+
use Symfony\Component\Decorator\DecoratorInterface;
1516

1617
#[\Attribute(\Attribute::TARGET_METHOD)]
17-
final class Json extends Decorate
18+
final class Json extends Decorate implements DecoratorInterface
1819
{
1920
public function __construct()
2021
{
21-
parent::__construct(JsonDecorator::class);
22+
parent::__construct(self::class);
23+
}
24+
25+
public function decorate(\Closure $func): \Closure
26+
{
27+
return static function (mixed ...$args) use ($func): string {
28+
$result = $func(...$args);
29+
30+
return json_encode($result, JSON_THROW_ON_ERROR);
31+
};
2232
}
2333
}

src/Symfony/Component/Decorator/Tests/Fixtures/Decorator/JsonDecorator.php

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)