diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php index 556affd070c6f..9f612b260ef22 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php @@ -82,6 +82,8 @@ ->abstract() ->args([ abstract_arg('bus handler resolver'), + false, + service('event_dispatcher'), ]) ->tag('monolog.logger', ['channel' => 'messenger']) ->call('setLogger', [service('logger')->ignoreOnInvalid()]) diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index 6be9bee7fadcf..eba0b06634829 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + +* New events: `HandlerStartingEvent`, `HandlerSuccessEvent`, `HandlerFailureEvent` + 7.0 --- diff --git a/src/Symfony/Component/Messenger/Event/AbstractHandlerEvent.php b/src/Symfony/Component/Messenger/Event/AbstractHandlerEvent.php new file mode 100644 index 0000000000000..95e1bf8ec4afd --- /dev/null +++ b/src/Symfony/Component/Messenger/Event/AbstractHandlerEvent.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Event; + +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Handler\HandlerDescriptor; + +abstract class AbstractHandlerEvent +{ + public function __construct( + public readonly Envelope $envelope, + public readonly HandlerDescriptor $handlerDescriptor, + ) { + } +} diff --git a/src/Symfony/Component/Messenger/Event/HandlerFailureEvent.php b/src/Symfony/Component/Messenger/Event/HandlerFailureEvent.php new file mode 100644 index 0000000000000..5df88c43bdc67 --- /dev/null +++ b/src/Symfony/Component/Messenger/Event/HandlerFailureEvent.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 Symfony\Component\Messenger\Event; + +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Handler\HandlerDescriptor; + +/** + * Event dispatched after a handler fails. + */ +final class HandlerFailureEvent extends AbstractHandlerEvent +{ + public function __construct( + Envelope $envelope, + HandlerDescriptor $handlerDescriptor, + public readonly \Throwable $exception, + ) { + parent::__construct($envelope, $handlerDescriptor); + } +} diff --git a/src/Symfony/Component/Messenger/Event/HandlerStartingEvent.php b/src/Symfony/Component/Messenger/Event/HandlerStartingEvent.php new file mode 100644 index 0000000000000..3c78cf0750d7a --- /dev/null +++ b/src/Symfony/Component/Messenger/Event/HandlerStartingEvent.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Event; + +/** + * Event dispatched before a handler is called. + */ +final class HandlerStartingEvent extends AbstractHandlerEvent +{ +} diff --git a/src/Symfony/Component/Messenger/Event/HandlerSuccessEvent.php b/src/Symfony/Component/Messenger/Event/HandlerSuccessEvent.php new file mode 100644 index 0000000000000..92d01e1e864b6 --- /dev/null +++ b/src/Symfony/Component/Messenger/Event/HandlerSuccessEvent.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Event; + +/** + * Event dispatched after a handler succeeds. + */ +final class HandlerSuccessEvent extends AbstractHandlerEvent +{ +} diff --git a/src/Symfony/Component/Messenger/Middleware/HandleMessageMiddleware.php b/src/Symfony/Component/Messenger/Middleware/HandleMessageMiddleware.php index c4e4a2d02b676..1e6a9b1ee988d 100644 --- a/src/Symfony/Component/Messenger/Middleware/HandleMessageMiddleware.php +++ b/src/Symfony/Component/Messenger/Middleware/HandleMessageMiddleware.php @@ -11,8 +11,12 @@ namespace Symfony\Component\Messenger\Middleware; +use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerAwareTrait; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Event\HandlerFailureEvent; +use Symfony\Component\Messenger\Event\HandlerStartingEvent; +use Symfony\Component\Messenger\Event\HandlerSuccessEvent; use Symfony\Component\Messenger\Exception\HandlerFailedException; use Symfony\Component\Messenger\Exception\LogicException; use Symfony\Component\Messenger\Exception\NoHandlerForMessageException; @@ -35,6 +39,7 @@ class HandleMessageMiddleware implements MiddlewareInterface public function __construct( private HandlersLocatorInterface $handlersLocator, private bool $allowNoHandlers = false, + private ?EventDispatcherInterface $eventDispatcher = null, ) { } @@ -58,6 +63,10 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope continue; } + $this->eventDispatcher?->dispatch(new HandlerStartingEvent($envelope, $handlerDescriptor)); + + $e = null; + try { $handler = $handlerDescriptor->getHandler(); $batchHandler = $handlerDescriptor->getBatchHandler(); @@ -97,6 +106,14 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope } catch (\Throwable $e) { $exceptions[$handlerDescriptor->getName()] = $e; } + + if (null !== $this->eventDispatcher) { + $event = (null !== $e) + ? new HandlerFailureEvent($envelope, $handlerDescriptor, $e) + : new HandlerSuccessEvent($envelope, $handlerDescriptor); + + $this->eventDispatcher->dispatch($event); + } } /** @var FlushBatchHandlersStamp $flushStamp */ diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/HandleMessageMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/HandleMessageMiddlewareTest.php index 13b0bb856de19..85b82b6eb6ea2 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/HandleMessageMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/HandleMessageMiddlewareTest.php @@ -11,7 +11,11 @@ namespace Symfony\Component\Messenger\Tests\Middleware; +use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Event\HandlerFailureEvent; +use Symfony\Component\Messenger\Event\HandlerStartingEvent; +use Symfony\Component\Messenger\Event\HandlerSuccessEvent; use Symfony\Component\Messenger\Exception\HandlerFailedException; use Symfony\Component\Messenger\Exception\LogicException; use Symfony\Component\Messenger\Exception\NoHandlerForMessageException; @@ -356,6 +360,45 @@ public function testHandlerArgumentsStampNamedArgument() $middleware->handle($envelope, $this->getStackMock()); } + + public function testDispatchHandlerEvents() + { + $message = new DummyMessage('Hey'); + $envelope = new Envelope($message); + + $successHandler = $this->createMock(HandleMessageMiddlewareTestCallable::class); + $successHandler->expects($this->once())->method('__invoke'); + + $failureHandler = $this->createMock(HandleMessageMiddlewareTestCallable::class); + $failureHandler->expects($this->once())->method('__invoke')->willThrowException( + $exception = new \RuntimeException('Handler failed'), + ); + + $handlersLocator = new HandlersLocator([ + DummyMessage::class => [ + $successHandlerDescriptor = new HandlerDescriptor($successHandler, ['alias' => 'successHandler']), + $failureHandlerDescriptor = new HandlerDescriptor($failureHandler, ['alias' => 'failureHandler']), + ], + ]); + + $dispatcher = $this->createMock(EventDispatcherInterface::class); + + $handledStamp = new HandledStamp(null, $successHandlerDescriptor->getName()); + + $dispatcher->expects($this->exactly(4)) + ->method('dispatch') + ->withConsecutive( + [new HandlerStartingEvent($envelope, $successHandlerDescriptor)], + [new HandlerSuccessEvent($envelope->with($handledStamp), $successHandlerDescriptor)], + [new HandlerStartingEvent($envelope->with($handledStamp), $failureHandlerDescriptor)], + [new HandlerFailureEvent($envelope->with($handledStamp), $failureHandlerDescriptor, $exception)], + ); + + $middleware = new HandleMessageMiddleware($handlersLocator, eventDispatcher: $dispatcher); + + $this->expectException(HandlerFailedException::class); + $middleware->handle($envelope, new StackMiddleware()); + } } class HandleMessageMiddlewareTestCallable