From 7faec3756a125703775f65e350c610784af5dd2b Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Thu, 12 Dec 2024 18:53:06 -0500 Subject: [PATCH] Adding support for decorating messenger handlers --- README.md | 7 + composer.json | 11 +- src/DecoratorBundle.php | 2 + src/DependencyInjection/MessengerPass.php | 32 ++++ .../Middleware/HandleMessageMiddleware.php | 164 ++++++++++++++++++ .../ControllerWithCompoundDecorator.php | 15 +- ...ollerWithSecurityAndSerializeDecorator.php | 15 +- .../ControllerWithSerializeDecorator.php | 11 ++ ...erWithSerializeDecoratorAndEmptyResult.php | 12 +- ...zeDecoratorIgnoredWhenRedirectResponse.php | 11 ++ ...ithSerializeDecoratorUnsupportedFormat.php | 12 +- ...erWithSerializerDecoratorCustomOptions.php | 12 +- .../Controller/ControllerWithoutDecorator.php | 11 ++ .../App/DecorateController/config.yaml | 2 +- .../Controller/DefaultController.php | 41 +++++ .../Handler/GreetingHandler.php | 28 +++ .../Message/Greeting.php | 22 +++ .../App/DecorateMessengerHandler/config.yaml | 18 ++ .../App/DecorateMessengerHandler/routes.yaml | 5 + tests/Integration/App/bundles.php | 5 +- tests/Integration/DecorateControllerTest.php | 11 ++ .../DecorateMessengerHandlerTest.php | 29 ++++ .../Fixtures/Decorator/FormatGreeting.php | 41 +++++ .../{Secured.php => HttpSecured.php} | 2 +- ...ecuredSerialize.php => HttpSecuredApi.php} | 6 +- ...Decorator.php => HttpSecuredDecorator.php} | 2 +- 26 files changed, 506 insertions(+), 21 deletions(-) create mode 100644 src/DependencyInjection/MessengerPass.php create mode 100644 src/Messenger/Middleware/HandleMessageMiddleware.php create mode 100644 tests/Integration/App/DecorateMessengerHandler/Controller/DefaultController.php create mode 100644 tests/Integration/App/DecorateMessengerHandler/Handler/GreetingHandler.php create mode 100644 tests/Integration/App/DecorateMessengerHandler/Message/Greeting.php create mode 100644 tests/Integration/App/DecorateMessengerHandler/config.yaml create mode 100644 tests/Integration/App/DecorateMessengerHandler/routes.yaml create mode 100644 tests/Integration/DecorateMessengerHandlerTest.php create mode 100644 tests/Integration/Fixtures/Decorator/FormatGreeting.php rename tests/Integration/Fixtures/Decorator/{Secured.php => HttpSecured.php} (89%) rename tests/Integration/Fixtures/Decorator/{SecuredSerialize.php => HttpSecuredApi.php} (83%) rename tests/Integration/Fixtures/Decorator/{SecuredDecorator.php => HttpSecuredDecorator.php} (93%) diff --git a/README.md b/README.md index 55bc161..147ba3e 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,13 @@ class MyController } ``` +## Supported Features for Using Decorators + +The following features currently support the use of decorators in your application: + + * Controllers + * Messenger Handlers (when `symfony/messenger` is installed) + ## License This software is published under the [MIT License](LICENSE) diff --git a/composer.json b/composer.json index a597c9f..0f33026 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "minimum-stability": "stable", "require": { "php": ">=8.2", - "yceruto/decorator": "^1.1", + "yceruto/decorator": "1.2.*", "symfony/framework-bundle": "^6.4|^7.0" }, "require-dev": { @@ -15,10 +15,11 @@ "doctrine/orm": "^3.2", "friendsofphp/php-cs-fixer": "^3.64", "phpunit/phpunit": "^11.3", - "symfony/browser-kit": "^7.1", - "symfony/mime": "^7.1", - "symfony/serializer": "^7.1", - "symfony/yaml": "^7.1" + "symfony/browser-kit": "^7.2", + "symfony/messenger": "^7.2", + "symfony/mime": "^7.2", + "symfony/serializer": "^7.2", + "symfony/yaml": "^7.2" }, "config": { "sort-packages": true diff --git a/src/DecoratorBundle.php b/src/DecoratorBundle.php index df20914..80f49c6 100644 --- a/src/DecoratorBundle.php +++ b/src/DecoratorBundle.php @@ -20,12 +20,14 @@ use Symfony\Component\Serializer\SerializerInterface; use Yceruto\Decorator\DecoratorInterface; use Yceruto\DecoratorBundle\DependencyInjection\DecoratorsPass; +use Yceruto\DecoratorBundle\DependencyInjection\MessengerPass; class DecoratorBundle extends AbstractBundle { public function build(ContainerBuilder $container): void { $container->addCompilerPass(new DecoratorsPass()); + $container->addCompilerPass(new MessengerPass()); } public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void diff --git a/src/DependencyInjection/MessengerPass.php b/src/DependencyInjection/MessengerPass.php new file mode 100644 index 0000000..b50011a --- /dev/null +++ b/src/DependencyInjection/MessengerPass.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Yceruto\DecoratorBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Yceruto\DecoratorBundle\Messenger\Middleware\HandleMessageMiddleware; + +class MessengerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('messenger.middleware.handle_message')) { + return; + } + + $middlewareDef = $container->getDefinition('messenger.middleware.handle_message'); + $middlewareDef->setClass(HandleMessageMiddleware::class); + $middlewareDef->setAutowired(true); + } +} diff --git a/src/Messenger/Middleware/HandleMessageMiddleware.php b/src/Messenger/Middleware/HandleMessageMiddleware.php new file mode 100644 index 0000000..fb0d9f6 --- /dev/null +++ b/src/Messenger/Middleware/HandleMessageMiddleware.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Yceruto\DecoratorBundle\Messenger\Middleware; + +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\HandlerFailedException; +use Symfony\Component\Messenger\Exception\LogicException; +use Symfony\Component\Messenger\Exception\NoHandlerForMessageException; +use Symfony\Component\Messenger\Handler\Acknowledger; +use Symfony\Component\Messenger\Handler\HandlerDescriptor; +use Symfony\Component\Messenger\Handler\HandlersLocatorInterface; +use Symfony\Component\Messenger\Middleware\MiddlewareInterface; +use Symfony\Component\Messenger\Middleware\StackInterface; +use Symfony\Component\Messenger\Stamp\AckStamp; +use Symfony\Component\Messenger\Stamp\FlushBatchHandlersStamp; +use Symfony\Component\Messenger\Stamp\HandledStamp; +use Symfony\Component\Messenger\Stamp\HandlerArgumentsStamp; +use Symfony\Component\Messenger\Stamp\NoAutoAckStamp; +use Yceruto\Decorator\DecoratorInterface; + +/** + * @author Samuel Roze + */ +class HandleMessageMiddleware implements MiddlewareInterface +{ + use LoggerAwareTrait; + + public function __construct( + private HandlersLocatorInterface $handlersLocator, + private bool $allowNoHandlers = false, + private ?DecoratorInterface $decorator = null, + ) { + } + + /** + * @throws NoHandlerForMessageException When no handler is found and $allowNoHandlers is false + */ + public function handle(Envelope $envelope, StackInterface $stack): Envelope + { + $handler = null; + $message = $envelope->getMessage(); + + $context = [ + 'class' => $message::class, + ]; + + $exceptions = []; + $alreadyHandled = false; + foreach ($this->handlersLocator->getHandlers($envelope) as $handlerDescriptor) { + if ($this->messageHasAlreadyBeenHandled($envelope, $handlerDescriptor)) { + $alreadyHandled = true; + continue; + } + + try { + $handler = $handlerDescriptor->getHandler(); + $batchHandler = $handlerDescriptor->getBatchHandler(); + + /** @var AckStamp $ackStamp */ + if ($batchHandler && $ackStamp = $envelope->last(AckStamp::class)) { + $ack = new Acknowledger(get_debug_type($batchHandler), static function (?\Throwable $e = null, $result = null) use ($envelope, $ackStamp, $handlerDescriptor) { + if (null !== $e) { + $e = new HandlerFailedException($envelope, [$handlerDescriptor->getName() => $e]); + } else { + $envelope = $envelope->with(HandledStamp::fromDescriptor($handlerDescriptor, $result)); + } + + $ackStamp->ack($envelope, $e); + }); + + $result = $this->callHandler($handler, $message, $ack, $envelope->last(HandlerArgumentsStamp::class)); + + if (!\is_int($result) || 0 > $result) { + throw new LogicException(\sprintf('A handler implementing BatchHandlerInterface must return the size of the current batch as a positive integer, "%s" returned from "%s".', \is_int($result) ? $result : get_debug_type($result), get_debug_type($batchHandler))); + } + + if (!$ack->isAcknowledged()) { + $envelope = $envelope->with(new NoAutoAckStamp($handlerDescriptor)); + } elseif ($ack->getError()) { + throw $ack->getError(); + } else { + $result = $ack->getResult(); + } + } else { + $result = $this->callHandler($handler, $message, null, $envelope->last(HandlerArgumentsStamp::class)); + } + + $handledStamp = HandledStamp::fromDescriptor($handlerDescriptor, $result); + $envelope = $envelope->with($handledStamp); + $this->logger?->info('Message {class} handled by {handler}', $context + ['handler' => $handledStamp->getHandlerName()]); + } catch (\Throwable $e) { + $exceptions[$handlerDescriptor->getName()] = $e; + } + } + + /** @var FlushBatchHandlersStamp $flushStamp */ + if ($flushStamp = $envelope->last(FlushBatchHandlersStamp::class)) { + /** @var NoAutoAckStamp $stamp */ + foreach ($envelope->all(NoAutoAckStamp::class) as $stamp) { + try { + $handler = $stamp->getHandlerDescriptor()->getBatchHandler(); + $handler->flush($flushStamp->force()); + } catch (\Throwable $e) { + $exceptions[$stamp->getHandlerDescriptor()->getName()] = $e; + } + } + } + + if (null === $handler && !$alreadyHandled) { + if (!$this->allowNoHandlers) { + throw new NoHandlerForMessageException(\sprintf('No handler for message "%s".', $context['class'])); + } + + $this->logger?->info('No handler for message {class}', $context); + } + + if (\count($exceptions)) { + throw new HandlerFailedException($envelope, $exceptions); + } + + return $stack->next()->handle($envelope, $stack); + } + + private function messageHasAlreadyBeenHandled(Envelope $envelope, HandlerDescriptor $handlerDescriptor): bool + { + /** @var HandledStamp $stamp */ + foreach ($envelope->all(HandledStamp::class) as $stamp) { + if ($stamp->getHandlerName() === $handlerDescriptor->getName()) { + return true; + } + } + + return false; + } + + private function callHandler(callable $handler, object $message, ?Acknowledger $ack, ?HandlerArgumentsStamp $handlerArgumentsStamp): mixed + { + $arguments = [$message]; + if (null !== $ack) { + $arguments[] = $ack; + } + if (null !== $handlerArgumentsStamp) { + $arguments = [...$arguments, ...$handlerArgumentsStamp->getAdditionalArguments()]; + } + + if ($this->decorator) { + $handler = $this->decorator->decorate($handler(...)); + } + + return $handler(...$arguments); + } +} diff --git a/tests/Integration/App/DecorateController/Controller/ControllerWithCompoundDecorator.php b/tests/Integration/App/DecorateController/Controller/ControllerWithCompoundDecorator.php index e1c70f2..53a2c49 100644 --- a/tests/Integration/App/DecorateController/Controller/ControllerWithCompoundDecorator.php +++ b/tests/Integration/App/DecorateController/Controller/ControllerWithCompoundDecorator.php @@ -1,14 +1,25 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Yceruto\DecoratorBundle\Tests\Integration\App\DecorateController\Controller; use Symfony\Component\Routing\Attribute\Route; -use Yceruto\DecoratorBundle\Tests\Integration\Fixtures\Decorator\SecuredSerialize; +use Yceruto\DecoratorBundle\Tests\Integration\Fixtures\Decorator\HttpSecuredApi; #[Route('/compound-decorators/default-options')] class ControllerWithCompoundDecorator { - #[SecuredSerialize] + #[HttpSecuredApi] public function __invoke(): array { return ['success' => true]; diff --git a/tests/Integration/App/DecorateController/Controller/ControllerWithSecurityAndSerializeDecorator.php b/tests/Integration/App/DecorateController/Controller/ControllerWithSecurityAndSerializeDecorator.php index 5f11349..e46f505 100644 --- a/tests/Integration/App/DecorateController/Controller/ControllerWithSecurityAndSerializeDecorator.php +++ b/tests/Integration/App/DecorateController/Controller/ControllerWithSecurityAndSerializeDecorator.php @@ -1,15 +1,26 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Yceruto\DecoratorBundle\Tests\Integration\App\DecorateController\Controller; use Symfony\Component\Routing\Attribute\Route; use Yceruto\DecoratorBundle\Decorator\Serializer\Serialize; -use Yceruto\DecoratorBundle\Tests\Integration\Fixtures\Decorator\Secured; +use Yceruto\DecoratorBundle\Tests\Integration\Fixtures\Decorator\HttpSecured; #[Route('/security-serialize-decorators/default-options')] class ControllerWithSecurityAndSerializeDecorator { - #[Secured] + #[HttpSecured] #[Serialize] public function __invoke(): array { diff --git a/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecorator.php b/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecorator.php index 23fbf16..0c42d01 100644 --- a/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecorator.php +++ b/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecorator.php @@ -1,5 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Yceruto\DecoratorBundle\Tests\Integration\App\DecorateController\Controller; use Symfony\Component\Routing\Attribute\Route; diff --git a/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecoratorAndEmptyResult.php b/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecoratorAndEmptyResult.php index 62fcf75..859a950 100644 --- a/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecoratorAndEmptyResult.php +++ b/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecoratorAndEmptyResult.php @@ -1,8 +1,18 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Yceruto\DecoratorBundle\Tests\Integration\App\DecorateController\Controller; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Yceruto\DecoratorBundle\Decorator\Serializer\Serialize; diff --git a/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecoratorIgnoredWhenRedirectResponse.php b/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecoratorIgnoredWhenRedirectResponse.php index 67d856e..a50142a 100644 --- a/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecoratorIgnoredWhenRedirectResponse.php +++ b/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecoratorIgnoredWhenRedirectResponse.php @@ -1,5 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Yceruto\DecoratorBundle\Tests\Integration\App\DecorateController\Controller; use Symfony\Component\HttpFoundation\RedirectResponse; diff --git a/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecoratorUnsupportedFormat.php b/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecoratorUnsupportedFormat.php index 15227b0..43ac530 100644 --- a/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecoratorUnsupportedFormat.php +++ b/tests/Integration/App/DecorateController/Controller/ControllerWithSerializeDecoratorUnsupportedFormat.php @@ -1,8 +1,18 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Yceruto\DecoratorBundle\Tests\Integration\App\DecorateController\Controller; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Yceruto\DecoratorBundle\Decorator\Serializer\Serialize; diff --git a/tests/Integration/App/DecorateController/Controller/ControllerWithSerializerDecoratorCustomOptions.php b/tests/Integration/App/DecorateController/Controller/ControllerWithSerializerDecoratorCustomOptions.php index 683a31a..668d604 100644 --- a/tests/Integration/App/DecorateController/Controller/ControllerWithSerializerDecoratorCustomOptions.php +++ b/tests/Integration/App/DecorateController/Controller/ControllerWithSerializerDecoratorCustomOptions.php @@ -1,8 +1,18 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Yceruto\DecoratorBundle\Tests\Integration\App\DecorateController\Controller; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Yceruto\DecoratorBundle\Decorator\Serializer\Serialize; diff --git a/tests/Integration/App/DecorateController/Controller/ControllerWithoutDecorator.php b/tests/Integration/App/DecorateController/Controller/ControllerWithoutDecorator.php index b19ce10..07b6b35 100644 --- a/tests/Integration/App/DecorateController/Controller/ControllerWithoutDecorator.php +++ b/tests/Integration/App/DecorateController/Controller/ControllerWithoutDecorator.php @@ -1,5 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Yceruto\DecoratorBundle\Tests\Integration\App\DecorateController\Controller; use Symfony\Component\HttpFoundation\Response; diff --git a/tests/Integration/App/DecorateController/config.yaml b/tests/Integration/App/DecorateController/config.yaml index af74fc9..eca1ae8 100644 --- a/tests/Integration/App/DecorateController/config.yaml +++ b/tests/Integration/App/DecorateController/config.yaml @@ -6,4 +6,4 @@ services: autowire: true autoconfigure: true - Yceruto\DecoratorBundle\Tests\Integration\Fixtures\Decorator\SecuredDecorator: ~ + Yceruto\DecoratorBundle\Tests\Integration\Fixtures\Decorator\HttpSecuredDecorator: ~ diff --git a/tests/Integration/App/DecorateMessengerHandler/Controller/DefaultController.php b/tests/Integration/App/DecorateMessengerHandler/Controller/DefaultController.php new file mode 100644 index 0000000..193bcc4 --- /dev/null +++ b/tests/Integration/App/DecorateMessengerHandler/Controller/DefaultController.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Yceruto\DecoratorBundle\Tests\Integration\App\DecorateMessengerHandler\Controller; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\AsController; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Messenger\Stamp\HandledStamp; +use Symfony\Component\Routing\Attribute\Route; +use Yceruto\DecoratorBundle\Tests\Integration\App\DecorateMessengerHandler\Message\Greeting; + +#[AsController] +#[Route('/messenger/handler/greeting')] +readonly class DefaultController +{ + public function __construct( + private MessageBusInterface $messageBus, + ) { + } + + public function __invoke(): Response + { + $result = $this->messageBus->dispatch(new Greeting('World!')) + ->last(HandledStamp::class) + ->getResult() + ; + + return new Response($result); + } +} diff --git a/tests/Integration/App/DecorateMessengerHandler/Handler/GreetingHandler.php b/tests/Integration/App/DecorateMessengerHandler/Handler/GreetingHandler.php new file mode 100644 index 0000000..3b97499 --- /dev/null +++ b/tests/Integration/App/DecorateMessengerHandler/Handler/GreetingHandler.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Yceruto\DecoratorBundle\Tests\Integration\App\DecorateMessengerHandler\Handler; + +use Symfony\Component\Messenger\Attribute\AsMessageHandler; +use Yceruto\DecoratorBundle\Tests\Integration\App\DecorateMessengerHandler\Message\Greeting; +use Yceruto\DecoratorBundle\Tests\Integration\Fixtures\Decorator\FormatGreeting; + +#[AsMessageHandler] +class GreetingHandler +{ + #[FormatGreeting('Casual')] + public function __invoke(Greeting $greeting): string + { + return "Hello $greeting->name"; + } +} diff --git a/tests/Integration/App/DecorateMessengerHandler/Message/Greeting.php b/tests/Integration/App/DecorateMessengerHandler/Message/Greeting.php new file mode 100644 index 0000000..b69c7a5 --- /dev/null +++ b/tests/Integration/App/DecorateMessengerHandler/Message/Greeting.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Yceruto\DecoratorBundle\Tests\Integration\App\DecorateMessengerHandler\Message; + +final readonly class Greeting +{ + public function __construct( + public string $name, + ) { + } +} diff --git a/tests/Integration/App/DecorateMessengerHandler/config.yaml b/tests/Integration/App/DecorateMessengerHandler/config.yaml new file mode 100644 index 0000000..591863b --- /dev/null +++ b/tests/Integration/App/DecorateMessengerHandler/config.yaml @@ -0,0 +1,18 @@ +imports: + - '../config.yaml' + +framework: + messenger: + transports: + sync: 'sync://' + + routing: + Yceruto\DecoratorBundle\Tests\Integration\App\DecorateMessengerHandler\Message\Greeting: sync + +services: + _defaults: + autowire: true + autoconfigure: true + + Yceruto\DecoratorBundle\Tests\Integration\App\DecorateMessengerHandler\Controller\DefaultController: ~ + Yceruto\DecoratorBundle\Tests\Integration\App\DecorateMessengerHandler\Handler\GreetingHandler: ~ diff --git a/tests/Integration/App/DecorateMessengerHandler/routes.yaml b/tests/Integration/App/DecorateMessengerHandler/routes.yaml new file mode 100644 index 0000000..a971b64 --- /dev/null +++ b/tests/Integration/App/DecorateMessengerHandler/routes.yaml @@ -0,0 +1,5 @@ +controllers: + resource: + path: ./Controller/ + namespace: Yceruto\DecoratorBundle\Tests\Integration\App\DecorateMessengerHandler\Controller + type: attribute diff --git a/tests/Integration/App/bundles.php b/tests/Integration/App/bundles.php index caeb16b..0be80a6 100644 --- a/tests/Integration/App/bundles.php +++ b/tests/Integration/App/bundles.php @@ -20,11 +20,10 @@ new FrameworkBundle(), new DoctrineBundle(), new DecoratorBundle(), - new class extends Bundle - { + new class extends Bundle { public function shutdown(): void { restore_exception_handler(); } - } + }, ]; diff --git a/tests/Integration/DecorateControllerTest.php b/tests/Integration/DecorateControllerTest.php index ccb6643..68fa016 100644 --- a/tests/Integration/DecorateControllerTest.php +++ b/tests/Integration/DecorateControllerTest.php @@ -1,5 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Yceruto\DecoratorBundle\Tests\Integration; class DecorateControllerTest extends AbstractWebTestCase diff --git a/tests/Integration/DecorateMessengerHandlerTest.php b/tests/Integration/DecorateMessengerHandlerTest.php new file mode 100644 index 0000000..73217cd --- /dev/null +++ b/tests/Integration/DecorateMessengerHandlerTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Yceruto\DecoratorBundle\Tests\Integration; + +class DecorateMessengerHandlerTest extends AbstractWebTestCase +{ + public function testDecorator(): void + { + $client = self::createClient(); + $client->request('GET', '/messenger/handler/greeting'); + + self::assertResponseIsSuccessful(); + self::assertResponseFormatSame('html'); + self::assertResponseStatusCodeSame(200); + self::assertResponseHeaderSame('Content-Type', 'text/html; charset=UTF-8'); + self::assertSame('hello world!', $client->getInternalResponse()->getContent()); + } +} diff --git a/tests/Integration/Fixtures/Decorator/FormatGreeting.php b/tests/Integration/Fixtures/Decorator/FormatGreeting.php new file mode 100644 index 0000000..dcae6c9 --- /dev/null +++ b/tests/Integration/Fixtures/Decorator/FormatGreeting.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Yceruto\DecoratorBundle\Tests\Integration\Fixtures\Decorator; + +use Yceruto\Decorator\Attribute\DecoratorAttribute; +use Yceruto\Decorator\DecoratorInterface; + +#[\Attribute(\Attribute::TARGET_METHOD)] +class FormatGreeting extends DecoratorAttribute implements DecoratorInterface +{ + public function __construct( + public string $style = 'Important', + ) { + } + + public function decorate(\Closure $func, self $format = new self()): \Closure + { + $styleFunc = match ($format->style) { + 'Important' => strtoupper(...), + 'Casual' => strtolower(...), + default => static fn (string $message) => $message, + }; + + return static function (mixed ...$args) use ($func, $styleFunc): mixed { + $message = $func(...$args); + + return $styleFunc($message); + }; + } +} diff --git a/tests/Integration/Fixtures/Decorator/Secured.php b/tests/Integration/Fixtures/Decorator/HttpSecured.php similarity index 89% rename from tests/Integration/Fixtures/Decorator/Secured.php rename to tests/Integration/Fixtures/Decorator/HttpSecured.php index 7ecfd3a..205e637 100644 --- a/tests/Integration/Fixtures/Decorator/Secured.php +++ b/tests/Integration/Fixtures/Decorator/HttpSecured.php @@ -16,6 +16,6 @@ use Yceruto\Decorator\Attribute\DecoratorAttribute; #[\Attribute(\Attribute::TARGET_METHOD)] -final class Secured extends DecoratorAttribute +final class HttpSecured extends DecoratorAttribute { } diff --git a/tests/Integration/Fixtures/Decorator/SecuredSerialize.php b/tests/Integration/Fixtures/Decorator/HttpSecuredApi.php similarity index 83% rename from tests/Integration/Fixtures/Decorator/SecuredSerialize.php rename to tests/Integration/Fixtures/Decorator/HttpSecuredApi.php index 42b29b7..0c407e5 100644 --- a/tests/Integration/Fixtures/Decorator/SecuredSerialize.php +++ b/tests/Integration/Fixtures/Decorator/HttpSecuredApi.php @@ -17,13 +17,13 @@ use Yceruto\DecoratorBundle\Decorator\Serializer\Serialize; #[\Attribute(\Attribute::TARGET_METHOD)] -class SecuredSerialize extends Compound +class HttpSecuredApi extends Compound { public function getDecorators(array $options): array { return [ - new Secured(), - new Serialize(), + new HttpSecured(), + new Serialize(format: 'json'), ]; } } diff --git a/tests/Integration/Fixtures/Decorator/SecuredDecorator.php b/tests/Integration/Fixtures/Decorator/HttpSecuredDecorator.php similarity index 93% rename from tests/Integration/Fixtures/Decorator/SecuredDecorator.php rename to tests/Integration/Fixtures/Decorator/HttpSecuredDecorator.php index fc02890..b111114 100644 --- a/tests/Integration/Fixtures/Decorator/SecuredDecorator.php +++ b/tests/Integration/Fixtures/Decorator/HttpSecuredDecorator.php @@ -18,7 +18,7 @@ use Yceruto\Decorator\DecoratorInterface; #[\Attribute(\Attribute::TARGET_METHOD)] -final readonly class SecuredDecorator implements DecoratorInterface +final readonly class HttpSecuredDecorator implements DecoratorInterface { public function __construct( private RequestStack $requestStack,