From f79533e8bc6d5ba86772ebbc86b4288236cbc0a6 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Sat, 30 Sep 2017 14:16:30 +0200 Subject: [PATCH 01/16] Create the Message component --- .../Command/MessageConsumeCommand.php | 80 ++++++ .../DependencyInjection/Configuration.php | 33 +++ .../FrameworkExtension.php | 18 ++ .../FrameworkBundle/FrameworkBundle.php | 2 + .../Resources/config/console.xml | 6 +- .../Resources/config/message.xml | 50 ++++ .../Message/Asynchronous/ConsumedMessage.php | 33 +++ .../WrapIntoConsumedMessageConsumer.php | 38 +++ .../SendMessageToProducersMiddleware.php | 52 ++++ .../Routing/ProducerForMessageResolver.php | 47 ++++ .../ProducerForMessageResolverInterface.php | 29 +++ .../Message/Debug/LoggingMiddleware.php | 58 +++++ .../DependencyInjection/MessagePass.php | 82 +++++++ .../Message/Exception/ExceptionInterface.php | 21 ++ .../NoHandlerForMessageException.php | 19 ++ .../Handler/CollectionOfMessageHandlers.php | 44 ++++ src/Symfony/Component/Message/MessageBus.php | 53 ++++ .../Component/Message/MessageBusInterface.php | 29 +++ .../Message/MessageBusMiddlewareInterface.php | 26 ++ .../Message/MessageConsumerInterface.php | 20 ++ .../Message/MessageHandlerResolver.php | 56 +++++ .../MessageHandlerResolverInterface.php | 31 +++ .../Message/MessageProducerInterface.php | 25 ++ .../CallMessageHandlerMiddleware.php | 44 ++++ src/Symfony/Component/Message/README.md | 229 ++++++++++++++++++ .../Transport/MessageDecoderInterface.php | 32 +++ .../Transport/MessageEncoderInterface.php | 32 +++ .../SerializeMessageWithTypeInHeaders.php | 57 +++++ src/Symfony/Component/Message/composer.json | 39 +++ .../Component/Message/phpunit.xml.dist | 30 +++ 30 files changed, 1314 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml create mode 100644 src/Symfony/Component/Message/Asynchronous/ConsumedMessage.php create mode 100644 src/Symfony/Component/Message/Asynchronous/Consumer/WrapIntoConsumedMessageConsumer.php create mode 100644 src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageToProducersMiddleware.php create mode 100644 src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolver.php create mode 100644 src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolverInterface.php create mode 100644 src/Symfony/Component/Message/Debug/LoggingMiddleware.php create mode 100644 src/Symfony/Component/Message/DependencyInjection/MessagePass.php create mode 100644 src/Symfony/Component/Message/Exception/ExceptionInterface.php create mode 100644 src/Symfony/Component/Message/Exception/NoHandlerForMessageException.php create mode 100644 src/Symfony/Component/Message/Handler/CollectionOfMessageHandlers.php create mode 100644 src/Symfony/Component/Message/MessageBus.php create mode 100644 src/Symfony/Component/Message/MessageBusInterface.php create mode 100644 src/Symfony/Component/Message/MessageBusMiddlewareInterface.php create mode 100644 src/Symfony/Component/Message/MessageConsumerInterface.php create mode 100644 src/Symfony/Component/Message/MessageHandlerResolver.php create mode 100644 src/Symfony/Component/Message/MessageHandlerResolverInterface.php create mode 100644 src/Symfony/Component/Message/MessageProducerInterface.php create mode 100644 src/Symfony/Component/Message/Middleware/CallMessageHandlerMiddleware.php create mode 100644 src/Symfony/Component/Message/README.md create mode 100644 src/Symfony/Component/Message/Transport/MessageDecoderInterface.php create mode 100644 src/Symfony/Component/Message/Transport/MessageEncoderInterface.php create mode 100644 src/Symfony/Component/Message/Transport/SerializeMessageWithTypeInHeaders.php create mode 100644 src/Symfony/Component/Message/composer.json create mode 100644 src/Symfony/Component/Message/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php new file mode 100644 index 0000000000000..f32a02636bf9f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Message\Asynchronous\ConsumedMessage; +use Symfony\Component\Message\MessageBusInterface; +use Symfony\Component\Message\MessageConsumerInterface; + +/** + * @author Samuel Roze + */ +class MessageConsumeCommand extends Command +{ + protected static $defaultName = 'message:consume'; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDefinition(array( + new InputArgument('consumer', InputArgument::REQUIRED, 'Name of the consumer'), + new InputOption('bus', 'b', InputOption::VALUE_REQUIRED, 'Name of the bus to dispatch the messages to', 'message_bus'), + )) + ->setDescription('Consume a message') + ->setHelp(<<<'EOF' +The %command.name% command consume a message and dispatch it to the message bus. + + %command.full_name% + +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + /** @var ContainerInterface $container */ + $container = $this->getApplication()->getKernel()->getContainer(); + + if (!$container->has($consumerName = $input->getArgument('consumer'))) { + throw new \RuntimeException(sprintf('Consumer "%s" do not exists', $consumerName)); + } elseif (!($consumer = $container->get($consumerName)) instanceof MessageConsumerInterface) { + throw new \RuntimeException(sprintf('Consumer "%s" is not a valid message consumer. It should implement the interface "%s"', $consumerName, MessageConsumerInterface::class)); + } + + if (!$container->has($busName = $input->getOption('bus'))) { + throw new \RuntimeException(sprintf('Bus "%s" do not exists', $busName)); + } elseif (!($messageBus = $container->get($busName)) instanceof MessageBusInterface) { + throw new \RuntimeException(sprintf('Bus "%s" is not a valid message bus. It should implement the interface "%s"', $busName, MessageBusInterface::class)); + } + + foreach ($consumer->consume() as $message) { + if (!$message instanceof ConsumedMessage) { + $message = new ConsumedMessage($message); + } + + $messageBus->handle($message); + } + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 8a2b10e8e4d92..208d9b7133a06 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -21,6 +21,7 @@ use Symfony\Component\Form\Form; use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\Store\SemaphoreStore; +use Symfony\Component\Message\MessageBusInterface; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\Translator; use Symfony\Component\Validator\Validation; @@ -100,6 +101,7 @@ public function getConfigTreeBuilder() $this->addPhpErrorsSection($rootNode); $this->addWebLinkSection($rootNode); $this->addLockSection($rootNode); + $this->addMessageSection($rootNode); return $treeBuilder; } @@ -894,4 +896,35 @@ private function addWebLinkSection(ArrayNodeDefinition $rootNode) ->end() ; } + + private function addMessageSection(ArrayNodeDefinition $rootNode) + { + $rootNode + ->children() + ->arrayNode('message') + ->info('Message configuration') + ->{!class_exists(FullStack::class) && class_exists(MessageBusInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->children() + ->arrayNode('routing') + ->useAttributeAsKey('message_class') + ->prototype('array') + ->beforeNormalization() + ->ifString() + ->then(function ($v) { + return array('producers' => array($v)); + }) + ->end() + ->children() + ->arrayNode('producers') + ->requiresAtLeastOneElement() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index f3b68fbc87f69..a38563930cd90 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -258,6 +258,10 @@ public function load(array $configs, ContainerBuilder $container) $this->registerLockConfiguration($config['lock'], $container, $loader); } + if ($this->isConfigEnabled($container, $config['message'])) { + $this->registerMessageConfiguration($config['message'], $container, $loader); + } + if ($this->isConfigEnabled($container, $config['web_link'])) { if (!class_exists(HttpHeaderSerializer::class)) { throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed.'); @@ -1359,6 +1363,20 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont } } + private function registerMessageConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + { + $loader->load('message.xml'); + + $messageToProducerMapping = array(); + foreach ($config['routing'] as $message => $messageConfiguration) { + $messageToProducerMapping[$message] = array_map(function (string $serviceName) { + return new Reference($serviceName); + }, $messageConfiguration['producers']); + } + + $container->getDefinition('message.asynchronous.routing.producer_for_message_resolver')->setArgument(0, $messageToProducerMapping); + } + private function registerCacheConfiguration(array $config, ContainerBuilder $container) { $version = substr(str_replace('/', '-', base64_encode(hash('sha256', uniqid(mt_rand(), true), true))), 0, 22); diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 0cebbc3f49ae0..12369cee787df 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -32,6 +32,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass; use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass; +use Symfony\Component\Message\DependencyInjection\MessagePass; use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass; use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass; use Symfony\Component\Serializer\DependencyInjection\SerializerPass; @@ -114,6 +115,7 @@ public function build(ContainerBuilder $container) $this->addCompilerPassIfExists($container, FormPass::class); $container->addCompilerPass(new WorkflowGuardListenerPass()); $container->addCompilerPass(new ResettableServicePass()); + $this->addCompilerPassIfExists($container, MessagePass::class); if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml index 34f47a0599b31..c780f621cd7cf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml @@ -64,7 +64,11 @@ - + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml new file mode 100644 index 0000000000000..3c96499d9481a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/Message/Asynchronous/ConsumedMessage.php b/src/Symfony/Component/Message/Asynchronous/ConsumedMessage.php new file mode 100644 index 0000000000000..5e517395987ad --- /dev/null +++ b/src/Symfony/Component/Message/Asynchronous/ConsumedMessage.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Asynchronous; + +/** + * Wraps a consumed message. This is mainly used by the `SendMessageToProducersMiddleware` middleware to identify + * a message should not be re-produced if it was just consumed. + * + * @author Samuel Roze + */ +final class ConsumedMessage +{ + private $message; + + public function __construct($message) + { + $this->message = $message; + } + + public function getMessage() + { + return $this->message; + } +} diff --git a/src/Symfony/Component/Message/Asynchronous/Consumer/WrapIntoConsumedMessageConsumer.php b/src/Symfony/Component/Message/Asynchronous/Consumer/WrapIntoConsumedMessageConsumer.php new file mode 100644 index 0000000000000..7283865d36cfe --- /dev/null +++ b/src/Symfony/Component/Message/Asynchronous/Consumer/WrapIntoConsumedMessageConsumer.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Asynchronous\Consumer; + +use Symfony\Component\Message\Asynchronous\ConsumedMessage; +use Symfony\Component\Message\MessageConsumerInterface; + +/** + * @author Samuel Roze + */ +class WrapIntoConsumedMessageConsumer implements MessageConsumerInterface +{ + /** + * @var MessageConsumerInterface + */ + private $decoratedConsumer; + + public function __construct(MessageConsumerInterface $decoratedConsumer) + { + $this->decoratedConsumer = $decoratedConsumer; + } + + public function consume(): \Generator + { + foreach ($this->decoratedConsumer->consume() as $message) { + yield new ConsumedMessage($message); + } + } +} diff --git a/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageToProducersMiddleware.php b/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageToProducersMiddleware.php new file mode 100644 index 0000000000000..112f8ab85b27c --- /dev/null +++ b/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageToProducersMiddleware.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Asynchronous\Middleware; + +use Symfony\Component\Message\Asynchronous\ConsumedMessage; +use Symfony\Component\Message\Asynchronous\Routing\ProducerForMessageResolverInterface; +use Symfony\Component\Message\MessageBusMiddlewareInterface; + +/** + * @author Samuel Roze + */ +class SendMessageToProducersMiddleware implements MessageBusMiddlewareInterface +{ + /** + * @var ProducerForMessageResolverInterface + */ + private $producerForMessageResolver; + + public function __construct(ProducerForMessageResolverInterface $producerForMessageResolver) + { + $this->producerForMessageResolver = $producerForMessageResolver; + } + + /** + * {@inheritdoc} + */ + public function handle($message, callable $next) + { + if ($message instanceof ConsumedMessage) { + $message = $message->getMessage(); + } elseif (!empty($producers = $this->producerForMessageResolver->getProducersForMessage($message))) { + foreach ($producers as $producer) { + $producer->produce($message); + } + + if (!in_array(null, $producers)) { + return; + } + } + + return $next($message); + } +} diff --git a/src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolver.php b/src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolver.php new file mode 100644 index 0000000000000..604dc591deb68 --- /dev/null +++ b/src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolver.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Asynchronous\Routing; + +/** + * @author Samuel Roze + */ +class ProducerForMessageResolver implements ProducerForMessageResolverInterface +{ + /** + * Mapping describing which producer should be used for which message. + * + * @var array + */ + private $messageToProducerMapping; + + public function __construct(array $messageToProducerMapping) + { + $this->messageToProducerMapping = $messageToProducerMapping; + } + + /** + * {@inheritdoc} + */ + public function getProducersForMessage($message): array + { + $messageKey = get_class($message); + if (array_key_exists($messageKey, $this->messageToProducerMapping)) { + return $this->messageToProducerMapping[$messageKey]; + } + + if (array_key_exists('*', $this->messageToProducerMapping)) { + return $this->messageToProducerMapping['*']; + } + + return array(); + } +} diff --git a/src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolverInterface.php b/src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolverInterface.php new file mode 100644 index 0000000000000..0199fefe456ee --- /dev/null +++ b/src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolverInterface.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\Message\Asynchronous\Routing; + +use Symfony\Component\Message\MessageProducerInterface; + +/** + * @author Samuel Roze + */ +interface ProducerForMessageResolverInterface +{ + /** + * Get the producer (if applicable) for the given message object. + * + * @param object $message + * + * @return MessageProducerInterface[] + */ + public function getProducersForMessage($message): array; +} diff --git a/src/Symfony/Component/Message/Debug/LoggingMiddleware.php b/src/Symfony/Component/Message/Debug/LoggingMiddleware.php new file mode 100644 index 0000000000000..6e1e80526780c --- /dev/null +++ b/src/Symfony/Component/Message/Debug/LoggingMiddleware.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Debug; + +use Symfony\Component\Message\MessageBusMiddlewareInterface; +use Psr\Log\LoggerInterface; + +/** + * @author Samuel Roze + */ +class LoggingMiddleware implements MessageBusMiddlewareInterface +{ + /** + * @var LoggerInterface + */ + private $logger; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function handle($message, callable $next) + { + $this->logger->debug('Starting processing message', array( + 'message' => $message, + )); + + try { + $result = $next($message); + } catch (\Throwable $e) { + $this->logger->warning('Something went wrong while processing message', array( + 'message' => $message, + 'exception' => $e, + )); + + throw $e; + } + + $this->logger->debug('Finished processing message', array( + 'message' => $message, + )); + + return $result; + } +} diff --git a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php new file mode 100644 index 0000000000000..2759319095c00 --- /dev/null +++ b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Samuel Roze + */ +class MessagePass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + private $messageHandlerResolverService; + private $handlerTag; + private $messageBusService; + private $middlewareTag; + + public function __construct( + string $messageBusService = 'message_bus', + string $middlewareTag = 'message_middleware', + string $messageHandlerResolverService = 'message.handler_resolver', + string $handlerTag = 'message_handler' + ) { + $this->messageHandlerResolverService = $messageHandlerResolverService; + $this->handlerTag = $handlerTag; + $this->messageBusService = $messageBusService; + $this->middlewareTag = $middlewareTag; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if (!$middlewares = $this->findAndSortTaggedServices($this->middlewareTag, $container)) { + throw new RuntimeException(sprintf('You must tag at least one service as "%s" to use the "%s" service.', $this->middlewareTag, $this->messageBusService)); + } + + $busDefinition = $container->getDefinition($this->messageBusService); + $busDefinition->replaceArgument(0, $middlewares); + + $handlerResolver = $container->getDefinition($this->messageHandlerResolverService); + $handlerResolver->replaceArgument(0, $this->findHandlers($container)); + } + + private function findHandlers(ContainerBuilder $container) + { + $handlersByMessage = array(); + + foreach ($container->findTaggedServiceIds($this->handlerTag, true) as $serviceId => $tags) { + foreach ($tags as $tag) { + if (!isset($tag['handles'])) { + throw new RuntimeException(sprintf('Tag "%s" on service "%s" should have an `handles` attribute', $this->handlerTag, $serviceId)); + } + + $priority = isset($tag['priority']) ? $tag['priority'] : 0; + $handlersByMessage[$tag['handles']][$priority][] = new Reference($serviceId); + } + } + + foreach ($handlersByMessage as $message => $handlers) { + krsort($handlersByMessage[$message]); + $handlersByMessage[$message] = call_user_func_array('array_merge', $handlersByMessage[$message]); + } + + return $handlersByMessage; + } +} diff --git a/src/Symfony/Component/Message/Exception/ExceptionInterface.php b/src/Symfony/Component/Message/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000..f816c0e54155f --- /dev/null +++ b/src/Symfony/Component/Message/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Exception; + +/** + * Base Message component's exception. + * + * @author Samuel Roze + */ +interface ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Message/Exception/NoHandlerForMessageException.php b/src/Symfony/Component/Message/Exception/NoHandlerForMessageException.php new file mode 100644 index 0000000000000..f1fd281eb3f15 --- /dev/null +++ b/src/Symfony/Component/Message/Exception/NoHandlerForMessageException.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\Message\Exception; + +/** + * @author Samuel Roze + */ +class NoHandlerForMessageException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Message/Handler/CollectionOfMessageHandlers.php b/src/Symfony/Component/Message/Handler/CollectionOfMessageHandlers.php new file mode 100644 index 0000000000000..66507b35e38e1 --- /dev/null +++ b/src/Symfony/Component/Message/Handler/CollectionOfMessageHandlers.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Handler; + +/** + * Represents a collection of message handlers. + * + * @author Samuel Roze + */ +class CollectionOfMessageHandlers +{ + /** + * @var callable[] + */ + private $handlers; + + /** + * @param callable[] $handlers + */ + public function __construct(array $handlers) + { + if (empty($handlers)) { + throw new \InvalidArgumentException('A collection of message handlers requires at least one handler'); + } + + $this->handlers = $handlers; + } + + public function __invoke($message) + { + return array_map(function ($handler) use ($message) { + return $handler($message); + }, $this->handlers); + } +} diff --git a/src/Symfony/Component/Message/MessageBus.php b/src/Symfony/Component/Message/MessageBus.php new file mode 100644 index 0000000000000..263bdd4d7b585 --- /dev/null +++ b/src/Symfony/Component/Message/MessageBus.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message; + +/** + * @author Samuel Roze + * @author Matthias Noback + */ +class MessageBus implements MessageBusInterface +{ + /** + * @var MessageBusMiddlewareInterface[] + */ + private $middlewares; + + /** + * @param MessageBusMiddlewareInterface[] $middlewares + */ + public function __construct(array $middlewares = array()) + { + $this->middlewares = $middlewares; + } + + /** + * {@inheritdoc} + */ + public function handle($message) + { + call_user_func($this->callableForNextMiddleware(0), $message); + } + + private function callableForNextMiddleware($index): callable + { + if (!isset($this->middlewares[$index])) { + return function () {}; + } + + $middleware = $this->middlewares[$index]; + + return function ($message) use ($middleware, $index) { + $middleware->handle($message, $this->callableForNextMiddleware($index + 1)); + }; + } +} diff --git a/src/Symfony/Component/Message/MessageBusInterface.php b/src/Symfony/Component/Message/MessageBusInterface.php new file mode 100644 index 0000000000000..fb38a97b50cfc --- /dev/null +++ b/src/Symfony/Component/Message/MessageBusInterface.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\Message; + +/** + * @author Samuel Roze + */ +interface MessageBusInterface +{ + /** + * Handle the message. + * + * The bus can return a value coming from handlers, but is not required to do so. + * + * @param object $message + * + * @return mixed + */ + public function handle($message); +} diff --git a/src/Symfony/Component/Message/MessageBusMiddlewareInterface.php b/src/Symfony/Component/Message/MessageBusMiddlewareInterface.php new file mode 100644 index 0000000000000..01fbdf9865084 --- /dev/null +++ b/src/Symfony/Component/Message/MessageBusMiddlewareInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message; + +/** + * @author Samuel Roze + */ +interface MessageBusMiddlewareInterface +{ + /** + * @param object $message + * @param callable $next + * + * @return mixed + */ + public function handle($message, callable $next); +} diff --git a/src/Symfony/Component/Message/MessageConsumerInterface.php b/src/Symfony/Component/Message/MessageConsumerInterface.php new file mode 100644 index 0000000000000..70c5747d3b5d3 --- /dev/null +++ b/src/Symfony/Component/Message/MessageConsumerInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message; + +/** + * @author Samuel Roze + */ +interface MessageConsumerInterface +{ + public function consume(): \Generator; +} diff --git a/src/Symfony/Component/Message/MessageHandlerResolver.php b/src/Symfony/Component/Message/MessageHandlerResolver.php new file mode 100644 index 0000000000000..259e04b3e7287 --- /dev/null +++ b/src/Symfony/Component/Message/MessageHandlerResolver.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message; + +use Symfony\Component\Message\Exception\NoHandlerForMessageException; +use Symfony\Component\Message\Handler\CollectionOfMessageHandlers; + +/** + * @author Samuel Roze + */ +class MessageHandlerResolver implements MessageHandlerResolverInterface +{ + /** + * Maps a message (its class) to a given handler. + * + * @var array + */ + private $messageToHandlerMapping; + + public function __construct(array $messageToHandlerMapping = array()) + { + $this->messageToHandlerMapping = $messageToHandlerMapping; + } + + public function resolve($message): callable + { + $messageKey = get_class($message); + + if (!array_key_exists($messageKey, $this->messageToHandlerMapping)) { + throw new NoHandlerForMessageException(sprintf('No handler for message "%s"', $messageKey)); + } + + $handler = $this->messageToHandlerMapping[$messageKey]; + if ($this->isCollectionOfHandlers($handler)) { + $handler = new CollectionOfMessageHandlers($handler); + } + + return $handler; + } + + private function isCollectionOfHandlers($handler): bool + { + return is_array($handler) && array_reduce($handler, function (bool $allHandlers, $handler) { + return $allHandlers && is_callable($handler); + }, true); + } +} diff --git a/src/Symfony/Component/Message/MessageHandlerResolverInterface.php b/src/Symfony/Component/Message/MessageHandlerResolverInterface.php new file mode 100644 index 0000000000000..d16ccfaa3dc3c --- /dev/null +++ b/src/Symfony/Component/Message/MessageHandlerResolverInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message; + +use Symfony\Component\Message\Exception\NoHandlerForMessageException; + +/** + * @author Samuel Roze + */ +interface MessageHandlerResolverInterface +{ + /** + * Return the handler for the given message. + * + * @param object $message + * + * @throws NoHandlerForMessageException + * + * @return callable + */ + public function resolve($message): callable; +} diff --git a/src/Symfony/Component/Message/MessageProducerInterface.php b/src/Symfony/Component/Message/MessageProducerInterface.php new file mode 100644 index 0000000000000..10b77828229cb --- /dev/null +++ b/src/Symfony/Component/Message/MessageProducerInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message; + +/** + * @author Samuel Roze + */ +interface MessageProducerInterface +{ + /** + * Produce the given message. + * + * @param object $message + */ + public function produce($message); +} diff --git a/src/Symfony/Component/Message/Middleware/CallMessageHandlerMiddleware.php b/src/Symfony/Component/Message/Middleware/CallMessageHandlerMiddleware.php new file mode 100644 index 0000000000000..8729f1d825390 --- /dev/null +++ b/src/Symfony/Component/Message/Middleware/CallMessageHandlerMiddleware.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Middleware; + +use Symfony\Component\Message\MessageBusMiddlewareInterface; +use Symfony\Component\Message\MessageHandlerResolverInterface; + +/** + * @author Samuel Roze + */ +class CallMessageHandlerMiddleware implements MessageBusMiddlewareInterface +{ + /** + * @var MessageHandlerResolverInterface + */ + private $messageHandlerResolver; + + public function __construct(MessageHandlerResolverInterface $messageHandlerResolver) + { + $this->messageHandlerResolver = $messageHandlerResolver; + } + + /** + * {@inheritdoc} + */ + public function handle($message, callable $next) + { + $handler = $this->messageHandlerResolver->resolve($message); + $result = $handler($message); + + $next($message); + + return $result; + } +} diff --git a/src/Symfony/Component/Message/README.md b/src/Symfony/Component/Message/README.md new file mode 100644 index 0000000000000..0479b80fe8c3b --- /dev/null +++ b/src/Symfony/Component/Message/README.md @@ -0,0 +1,229 @@ +Message Component +================= + +The Message component helps application to send and receive messages to/from other applications or via +message queues. + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + + +Documentation +------------- + +**Note:** this documentation is to be moved to symfony.com when merging the Component. + +### Bus + +The bus is used to dispatch and handle messages. MessageBus' behaviour is in its ordered middleware stack. When using +the message bus with Symfony's FrameworkBundle, the following middlewares are configured for you: + +1. `LogMessagesMiddleware` (log the processing of your messages) +2. `SendMessageOnProducersBasedOnRoutingMiddleware` (enable asynchronous processing) +3. `CallMessageHandlerMiddleware` (call the registered handle) + +```php +$result = $this->get('message_bus')->dispatch(new MyMessage(/* ... */)); +``` + +### Handlers + +Once dispatched to the bus, messages will be handled by a "message handler". A message handler is a PHP callable +(i.e. a function or an instance of a class) that will do the required processing for your message. It _might_ return a +result. + +```php +class MyMessageHandler +{ + public function __invoke(MyMessage $message) + { + // Message processing... + } +} +``` + +```xml + + + +``` + +### Asynchronous messages + +Using the Message Component is useful to decouple your application but it also very useful when you want to do some +asychronous processing. This means that your application will produce a message to a queuing system and consume this +message later in the background, using a _worker_. + +#### Adapters + +The communication with queuing system or 3rd parties is for delegated to libraries for now. You can use one of the +following adapters: + +- [PHP Enqueue bridge](https://github.com/sroze/enqueue-bridge) to use one of their 10+ compatible queues such as + RabbitMq, Amazon SQS or Google Pub/Sub. + +#### Routing + +When doing asynchronous processing, the key is to route the message to the right producer. As the routing is +application-specific and not message-specific, the configuration can be made within the `framework.yaml` +configuration file as well: + +```yaml +framework: + message: + routing: + 'My\Message\MessageAboutDoingOperationalWork': my_operations_producer +``` + +Such configuration would only route the `MessageAboutDoingOperationalWork` message to be asynchronous, the rest of the +messages would still be directly handled. + +If you want to do route all the messages to a queue by default, you can use such configuration: +```yaml +framework: + message: + routing: + 'My\Message\MessageAboutDoingOperationalWork': my_operations_producer + '*': my_default_producer +``` + +Note that you can also route a message to multiple producers at the same time: +```yaml +framework: + message: + routing: + 'My\Message\AnImportantMessage': [my_default_producer, my_audit_producer] +``` + +#### Same bus consumer and producer + +To allow us to consume and produce messages on the same bus and prevent a loop, the message bus is equipped with the +`SendMessageOnProducersBasedOnRoutingMiddleware` middleware and a `WrappedIntoConsumedMessageConsumer` consumer. +The consumer will wraps the received messages into `ConsumedMessage` objects and the middleware will not forward +these messages to the producers. + + +### Your own producer + +Using the `MessageProducerInterface`, you can easily create your own message producer. Let's say you already have an +`ImportantAction` message going through the message bus and handled by a handler. Now, you also want to produce this +message as an email. + +1. Create your producer + +```php +namespace App\MessageProducer; + +use Symfony\Component\Message\MessageProducerInterface; +use App\Message\ImportantAction; + +class ImportantActionToEmailProducer implements MessageProducerInterface +{ + private $toEmail; + private $mailer; + + public function __construct(\Swift_Mailer $mailer, string $toEmail) + { + $this->mailer = $mailer; + $this->toEmail = $toEmail; + } + + public function produce($message) + { + if (!$message instanceof ImportantAction) { + throw new \InvalidArgumentException(sprintf('Producer only supports "%s" messages', ImportantAction::class)); + } + + $this->mailer->send( + (new \Swift_Message('Important action made')) + ->setTo($this->toEmail) + ->setBody( + '

Important action

Made by '.$message->getUsername().'

', + 'text/html' + ) + ); + } +} +``` + +2. Register your producer service + +```xml + + + %to_email% + +``` + +3. Route your important message to the producer + +```yaml +framework: + message: + routing: + 'App\Message\ImportantAction': [app.message_producer.important_action_to_email, ~] +``` + +**Note:** this example shows you how you can at the same time produce your message and directly handle it using a `null` +(`~`) producer. + +### Your own consumer + +A consumer is responsible of reading messages from a source and dispatching them to the application. + +Let's say you already proceed some "orders" on your application using a `NewOrder` message. Now you want to integrate with +a 3rd party or a legacy application but you can't use an API and need to use a shared CSV file with new orders. + +You will read this CSV file and dispatch a `NewOrder` message. All you need to do is your custom CSV consumer and Symfony will do the rest. + +1. Create your consumer + +```php +namespace App\MessageConsumer; + +use Symfony\Component\Message\MessageConsumerInterface; +use Symfony\Component\Serializer\SerializerInterface; + +use App\Message\NewOrder; + +class NewOrdersFromCsvFile implements MessageConsumerInterface +{ + private $serializer; + private $filePath; + + public function __construct(SerializerInteface $serializer, string $filePath) + { + $this->serializer = $serializer; + $this->filePath = $filePath; + } + + public function consume() : \Generator + { + $ordersFromCsv = $this->serializer->deserialize(file_get_contents($this->filePath), 'csv'); + + foreach ($ordersFromCsv as $orderFromCsv) { + yield new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount']); + } + } +} +``` + +2. Register your consumer service + +```xml + + + %new_orders_csv_file_path% + +``` + +3. Use your consumer + +```bash +$ bin/console message:consume app.message_consumer.new_orders_from_csv_file +``` diff --git a/src/Symfony/Component/Message/Transport/MessageDecoderInterface.php b/src/Symfony/Component/Message/Transport/MessageDecoderInterface.php new file mode 100644 index 0000000000000..9613a329062e7 --- /dev/null +++ b/src/Symfony/Component/Message/Transport/MessageDecoderInterface.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 Symfony\Component\Message\Transport; + +/** + * @author Samuel Roze + */ +interface MessageDecoderInterface +{ + /** + * Decode the message from an encoded-form. The `$encodedMessage` parameter is a key-value array that + * describes the message, that will be used by the different adapters. + * + * The most common keys are: + * - `body` (string) - the message body + * - `headers` (string) - a key/value pair of headers + * + * @param array $encodedMessage + * + * @return object + */ + public function decode(array $encodedMessage); +} diff --git a/src/Symfony/Component/Message/Transport/MessageEncoderInterface.php b/src/Symfony/Component/Message/Transport/MessageEncoderInterface.php new file mode 100644 index 0000000000000..b0292b5bf2666 --- /dev/null +++ b/src/Symfony/Component/Message/Transport/MessageEncoderInterface.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 Symfony\Component\Message\Transport; + +/** + * @author Samuel Roze + */ +interface MessageEncoderInterface +{ + /** + * Encode a message to a common format understandable by adapters. The encoded array should only + * contain scalar and arrays. + * + * The most common keys of the encoded array are: + * - `body` (string) - the message body + * - `headers` (string) - a key/value pair of headers + * + * @param object $message + * + * @return array + */ + public function encode($message): array; +} diff --git a/src/Symfony/Component/Message/Transport/SerializeMessageWithTypeInHeaders.php b/src/Symfony/Component/Message/Transport/SerializeMessageWithTypeInHeaders.php new file mode 100644 index 0000000000000..799240e68cabd --- /dev/null +++ b/src/Symfony/Component/Message/Transport/SerializeMessageWithTypeInHeaders.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Transport; + +use Symfony\Component\Serializer\SerializerInterface; + +/** + * @author Samuel Roze + */ +class SerializeMessageWithTypeInHeaders implements MessageDecoderInterface, MessageEncoderInterface +{ + /** + * @var SerializerInterface + */ + private $serializer; + + public function __construct(SerializerInterface $serializer) + { + $this->serializer = $serializer; + } + + /** + * {@inheritdoc} + */ + public function decode(array $encodedMessage) + { + if (empty($encodedMessage['body']) || empty($encodedMessage['headers'])) { + throw new \InvalidArgumentException('Encoded message should have at least a `body` some `headers`'); + } elseif (empty($encodedMessage['headers']['type'])) { + throw new \InvalidArgumentException('Encoded message do not have a `type` header'); + } + + return $this->serializer->deserialize($encodedMessage['body'], $encodedMessage['headers']['type'], 'json'); + } + + /** + * {@inheritdoc} + */ + public function encode($message): array + { + return array( + 'body' => $this->serializer->serialize($message, 'json'), + 'headers' => array( + 'type' => get_class($message), + ), + ); + } +} diff --git a/src/Symfony/Component/Message/composer.json b/src/Symfony/Component/Message/composer.json new file mode 100644 index 0000000000000..de7ef6ac0d88a --- /dev/null +++ b/src/Symfony/Component/Message/composer.json @@ -0,0 +1,39 @@ +{ + "name": "symfony/message", + "type": "library", + "description": "Symfony Message Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "symfony/serializer": "~3.4|~4.0" + }, + "suggest": { + "sroze/enqueue-bridge": "For using the php-enqueue library as an adapater." + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Message\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + } +} diff --git a/src/Symfony/Component/Message/phpunit.xml.dist b/src/Symfony/Component/Message/phpunit.xml.dist new file mode 100644 index 0000000000000..9bc89a7ef9a21 --- /dev/null +++ b/src/Symfony/Component/Message/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + From 099b423ffd55d81791a1f7745a284685fa9f6696 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Fri, 1 Dec 2017 16:46:07 +0000 Subject: [PATCH 02/16] Rename consumers to receivers and producers to senders. And a lot of small comments on the PR. --- .../Command/MessageConsumeCommand.php | 6 +- .../Resources/config/message.xml | 8 +- .../WrapIntoConsumedMessageConsumer.php | 38 ------- .../Middleware/SendMessageMiddleware.php | 52 +++++++++ .../SendMessageToProducersMiddleware.php | 52 --------- .../Routing/ProducerForMessageResolver.php | 47 -------- .../Asynchronous/Routing/SenderLocator.php | 38 +++++++ ...terface.php => SenderLocatorInterface.php} | 8 +- .../ReceivedMessage.php} | 10 +- .../Transport/WrapIntoReceivedMessage.php | 37 +++++++ .../Message/Debug/LoggingMiddleware.php | 4 +- .../DependencyInjection/MessagePass.php | 10 +- ...dlers.php => MessageHandlerCollection.php} | 2 +- ...HandlerResolver.php => HandlerLocator.php} | 6 +- ...erface.php => HandlerLocatorInterface.php} | 2 +- src/Symfony/Component/Message/MessageBus.php | 6 +- .../Component/Message/MessageBusInterface.php | 4 +- ...leware.php => HandleMessageMiddleware.php} | 10 +- ...eInterface.php => MiddlewareInterface.php} | 2 +- src/Symfony/Component/Message/README.md | 103 +++++++++++------- .../Resources/doc/component-overview.png | Bin 0 -> 22425 bytes .../ReceiverInterface.php} | 6 +- .../SenderInterface.php} | 8 +- .../DecoderInterface.php} | 4 +- .../EncoderInterface.php} | 4 +- .../SymfonySerialization.php} | 4 +- 26 files changed, 240 insertions(+), 231 deletions(-) delete mode 100644 src/Symfony/Component/Message/Asynchronous/Consumer/WrapIntoConsumedMessageConsumer.php create mode 100644 src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageMiddleware.php delete mode 100644 src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageToProducersMiddleware.php delete mode 100644 src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolver.php create mode 100644 src/Symfony/Component/Message/Asynchronous/Routing/SenderLocator.php rename src/Symfony/Component/Message/Asynchronous/Routing/{ProducerForMessageResolverInterface.php => SenderLocatorInterface.php} (70%) rename src/Symfony/Component/Message/Asynchronous/{ConsumedMessage.php => Transport/ReceivedMessage.php} (59%) create mode 100644 src/Symfony/Component/Message/Asynchronous/Transport/WrapIntoReceivedMessage.php rename src/Symfony/Component/Message/Handler/{CollectionOfMessageHandlers.php => MessageHandlerCollection.php} (96%) rename src/Symfony/Component/Message/{MessageHandlerResolver.php => HandlerLocator.php} (87%) rename src/Symfony/Component/Message/{MessageHandlerResolverInterface.php => HandlerLocatorInterface.php} (93%) rename src/Symfony/Component/Message/Middleware/{CallMessageHandlerMiddleware.php => HandleMessageMiddleware.php} (68%) rename src/Symfony/Component/Message/{MessageBusMiddlewareInterface.php => MiddlewareInterface.php} (92%) create mode 100644 src/Symfony/Component/Message/Resources/doc/component-overview.png rename src/Symfony/Component/Message/{MessageConsumerInterface.php => Transport/ReceiverInterface.php} (71%) rename src/Symfony/Component/Message/{MessageProducerInterface.php => Transport/SenderInterface.php} (70%) rename src/Symfony/Component/Message/Transport/{MessageDecoderInterface.php => Serialization/DecoderInterface.php} (89%) rename src/Symfony/Component/Message/Transport/{MessageEncoderInterface.php => Serialization/EncoderInterface.php} (89%) rename src/Symfony/Component/Message/Transport/{SerializeMessageWithTypeInHeaders.php => Serialization/SymfonySerialization.php} (90%) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php index f32a02636bf9f..3a9877ff83802 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php @@ -17,7 +17,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\Message\Asynchronous\ConsumedMessage; +use Symfony\Component\Message\Asynchronous\Transport\ReceivedMessage; use Symfony\Component\Message\MessageBusInterface; use Symfony\Component\Message\MessageConsumerInterface; @@ -70,8 +70,8 @@ protected function execute(InputInterface $input, OutputInterface $output) } foreach ($consumer->consume() as $message) { - if (!$message instanceof ConsumedMessage) { - $message = new ConsumedMessage($message); + if (!$message instanceof ReceivedMessage) { + $message = new ReceivedMessage($message); } $messageBus->handle($message); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml index 3c96499d9481a..d728eda6d4d7c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml @@ -19,18 +19,18 @@
- + - + - + @@ -40,7 +40,7 @@ - + diff --git a/src/Symfony/Component/Message/Asynchronous/Consumer/WrapIntoConsumedMessageConsumer.php b/src/Symfony/Component/Message/Asynchronous/Consumer/WrapIntoConsumedMessageConsumer.php deleted file mode 100644 index 7283865d36cfe..0000000000000 --- a/src/Symfony/Component/Message/Asynchronous/Consumer/WrapIntoConsumedMessageConsumer.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Message\Asynchronous\Consumer; - -use Symfony\Component\Message\Asynchronous\ConsumedMessage; -use Symfony\Component\Message\MessageConsumerInterface; - -/** - * @author Samuel Roze - */ -class WrapIntoConsumedMessageConsumer implements MessageConsumerInterface -{ - /** - * @var MessageConsumerInterface - */ - private $decoratedConsumer; - - public function __construct(MessageConsumerInterface $decoratedConsumer) - { - $this->decoratedConsumer = $decoratedConsumer; - } - - public function consume(): \Generator - { - foreach ($this->decoratedConsumer->consume() as $message) { - yield new ConsumedMessage($message); - } - } -} diff --git a/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageMiddleware.php b/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageMiddleware.php new file mode 100644 index 0000000000000..9548c4fdf46e1 --- /dev/null +++ b/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageMiddleware.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Asynchronous\Middleware; + +use Symfony\Component\Message\Asynchronous\Routing\SenderLocatorInterface; +use Symfony\Component\Message\Asynchronous\Transport\ReceivedMessage; +use Symfony\Component\Message\MiddlewareInterface; + +/** + * @author Samuel Roze + */ +class SendMessageMiddleware implements MiddlewareInterface +{ + /** + * @var SenderLocatorInterface + */ + private $senderLocator; + + public function __construct(SenderLocatorInterface $senderLocator) + { + $this->senderLocator = $senderLocator; + } + + /** + * {@inheritdoc} + */ + public function handle($message, callable $next) + { + if ($message instanceof ReceivedMessage) { + $message = $message->getMessage(); + } elseif (!empty($senders = $this->senderLocator->getSendersForMessage($message))) { + foreach ($senders as $sender) { + $sender->send($message); + } + + if (!in_array(null, $senders)) { + return; + } + } + + return $next($message); + } +} diff --git a/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageToProducersMiddleware.php b/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageToProducersMiddleware.php deleted file mode 100644 index 112f8ab85b27c..0000000000000 --- a/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageToProducersMiddleware.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Message\Asynchronous\Middleware; - -use Symfony\Component\Message\Asynchronous\ConsumedMessage; -use Symfony\Component\Message\Asynchronous\Routing\ProducerForMessageResolverInterface; -use Symfony\Component\Message\MessageBusMiddlewareInterface; - -/** - * @author Samuel Roze - */ -class SendMessageToProducersMiddleware implements MessageBusMiddlewareInterface -{ - /** - * @var ProducerForMessageResolverInterface - */ - private $producerForMessageResolver; - - public function __construct(ProducerForMessageResolverInterface $producerForMessageResolver) - { - $this->producerForMessageResolver = $producerForMessageResolver; - } - - /** - * {@inheritdoc} - */ - public function handle($message, callable $next) - { - if ($message instanceof ConsumedMessage) { - $message = $message->getMessage(); - } elseif (!empty($producers = $this->producerForMessageResolver->getProducersForMessage($message))) { - foreach ($producers as $producer) { - $producer->produce($message); - } - - if (!in_array(null, $producers)) { - return; - } - } - - return $next($message); - } -} diff --git a/src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolver.php b/src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolver.php deleted file mode 100644 index 604dc591deb68..0000000000000 --- a/src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolver.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Message\Asynchronous\Routing; - -/** - * @author Samuel Roze - */ -class ProducerForMessageResolver implements ProducerForMessageResolverInterface -{ - /** - * Mapping describing which producer should be used for which message. - * - * @var array - */ - private $messageToProducerMapping; - - public function __construct(array $messageToProducerMapping) - { - $this->messageToProducerMapping = $messageToProducerMapping; - } - - /** - * {@inheritdoc} - */ - public function getProducersForMessage($message): array - { - $messageKey = get_class($message); - if (array_key_exists($messageKey, $this->messageToProducerMapping)) { - return $this->messageToProducerMapping[$messageKey]; - } - - if (array_key_exists('*', $this->messageToProducerMapping)) { - return $this->messageToProducerMapping['*']; - } - - return array(); - } -} diff --git a/src/Symfony/Component/Message/Asynchronous/Routing/SenderLocator.php b/src/Symfony/Component/Message/Asynchronous/Routing/SenderLocator.php new file mode 100644 index 0000000000000..4d58f72858591 --- /dev/null +++ b/src/Symfony/Component/Message/Asynchronous/Routing/SenderLocator.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Asynchronous\Routing; + +/** + * @author Samuel Roze + */ +class SenderLocator implements SenderLocatorInterface +{ + /** + * Mapping describing which sender should be used for which message. + * + * @var array + */ + private $messageToSenderMapping; + + public function __construct(array $messageToSenderMapping) + { + $this->messageToSenderMapping = $messageToSenderMapping; + } + + /** + * {@inheritdoc} + */ + public function getSendersForMessage($message): array + { + return $this->messageToSenderMapping[get_class($message)] ?? $this->messageToSenderMapping['*'] ?? array(); + } +} diff --git a/src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolverInterface.php b/src/Symfony/Component/Message/Asynchronous/Routing/SenderLocatorInterface.php similarity index 70% rename from src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolverInterface.php rename to src/Symfony/Component/Message/Asynchronous/Routing/SenderLocatorInterface.php index 0199fefe456ee..e0f7af8d811b2 100644 --- a/src/Symfony/Component/Message/Asynchronous/Routing/ProducerForMessageResolverInterface.php +++ b/src/Symfony/Component/Message/Asynchronous/Routing/SenderLocatorInterface.php @@ -11,19 +11,19 @@ namespace Symfony\Component\Message\Asynchronous\Routing; -use Symfony\Component\Message\MessageProducerInterface; +use Symfony\Component\Message\Transport\SenderInterface; /** * @author Samuel Roze */ -interface ProducerForMessageResolverInterface +interface SenderLocatorInterface { /** * Get the producer (if applicable) for the given message object. * * @param object $message * - * @return MessageProducerInterface[] + * @return SenderInterface[] */ - public function getProducersForMessage($message): array; + public function getSendersForMessage($message): array; } diff --git a/src/Symfony/Component/Message/Asynchronous/ConsumedMessage.php b/src/Symfony/Component/Message/Asynchronous/Transport/ReceivedMessage.php similarity index 59% rename from src/Symfony/Component/Message/Asynchronous/ConsumedMessage.php rename to src/Symfony/Component/Message/Asynchronous/Transport/ReceivedMessage.php index 5e517395987ad..54ac0e888ae71 100644 --- a/src/Symfony/Component/Message/Asynchronous/ConsumedMessage.php +++ b/src/Symfony/Component/Message/Asynchronous/Transport/ReceivedMessage.php @@ -9,15 +9,17 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Message\Asynchronous; +namespace Symfony\Component\Message\Asynchronous\Transport; /** - * Wraps a consumed message. This is mainly used by the `SendMessageToProducersMiddleware` middleware to identify - * a message should not be re-produced if it was just consumed. + * Wraps a received message. This is mainly used by the `SendMessageMiddleware` middleware to identify + * a message should not be sent if it was just received. + * + * @see \Symfony\Component\Message\Asynchronous\Middleware\SendMessageMiddleware * * @author Samuel Roze */ -final class ConsumedMessage +final class ReceivedMessage { private $message; diff --git a/src/Symfony/Component/Message/Asynchronous/Transport/WrapIntoReceivedMessage.php b/src/Symfony/Component/Message/Asynchronous/Transport/WrapIntoReceivedMessage.php new file mode 100644 index 0000000000000..e4d755750ce4c --- /dev/null +++ b/src/Symfony/Component/Message/Asynchronous/Transport/WrapIntoReceivedMessage.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Asynchronous\Transport; + +use Symfony\Component\Message\Transport\ReceiverInterface; + +/** + * @author Samuel Roze + */ +class WrapIntoReceivedMessage implements ReceiverInterface +{ + /** + * @var ReceiverInterface + */ + private $decoratedReceiver; + + public function __construct(ReceiverInterface $decoratedConsumer) + { + $this->decoratedReceiver = $decoratedConsumer; + } + + public function receive(): \Generator + { + foreach ($this->decoratedReceiver->receive() as $message) { + yield new ReceivedMessage($message); + } + } +} diff --git a/src/Symfony/Component/Message/Debug/LoggingMiddleware.php b/src/Symfony/Component/Message/Debug/LoggingMiddleware.php index 6e1e80526780c..9bc7f80164edc 100644 --- a/src/Symfony/Component/Message/Debug/LoggingMiddleware.php +++ b/src/Symfony/Component/Message/Debug/LoggingMiddleware.php @@ -11,13 +11,13 @@ namespace Symfony\Component\Message\Debug; -use Symfony\Component\Message\MessageBusMiddlewareInterface; +use Symfony\Component\Message\MiddlewareInterface; use Psr\Log\LoggerInterface; /** * @author Samuel Roze */ -class LoggingMiddleware implements MessageBusMiddlewareInterface +class LoggingMiddleware implements MiddlewareInterface { /** * @var LoggerInterface diff --git a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php index 2759319095c00..99420ff9ee6bc 100644 --- a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php +++ b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php @@ -29,12 +29,8 @@ class MessagePass implements CompilerPassInterface private $messageBusService; private $middlewareTag; - public function __construct( - string $messageBusService = 'message_bus', - string $middlewareTag = 'message_middleware', - string $messageHandlerResolverService = 'message.handler_resolver', - string $handlerTag = 'message_handler' - ) { + public function __construct(string $messageBusService = 'message_bus', string $middlewareTag = 'message_middleware', string $messageHandlerResolverService = 'message.handler_resolver', string $handlerTag = 'message_handler') + { $this->messageHandlerResolverService = $messageHandlerResolverService; $this->handlerTag = $handlerTag; $this->messageBusService = $messageBusService; @@ -57,7 +53,7 @@ public function process(ContainerBuilder $container) $handlerResolver->replaceArgument(0, $this->findHandlers($container)); } - private function findHandlers(ContainerBuilder $container) + private function findHandlers(ContainerBuilder $container): array { $handlersByMessage = array(); diff --git a/src/Symfony/Component/Message/Handler/CollectionOfMessageHandlers.php b/src/Symfony/Component/Message/Handler/MessageHandlerCollection.php similarity index 96% rename from src/Symfony/Component/Message/Handler/CollectionOfMessageHandlers.php rename to src/Symfony/Component/Message/Handler/MessageHandlerCollection.php index 66507b35e38e1..37cc0991842b3 100644 --- a/src/Symfony/Component/Message/Handler/CollectionOfMessageHandlers.php +++ b/src/Symfony/Component/Message/Handler/MessageHandlerCollection.php @@ -16,7 +16,7 @@ * * @author Samuel Roze */ -class CollectionOfMessageHandlers +class MessageHandlerCollection { /** * @var callable[] diff --git a/src/Symfony/Component/Message/MessageHandlerResolver.php b/src/Symfony/Component/Message/HandlerLocator.php similarity index 87% rename from src/Symfony/Component/Message/MessageHandlerResolver.php rename to src/Symfony/Component/Message/HandlerLocator.php index 259e04b3e7287..41da53929957d 100644 --- a/src/Symfony/Component/Message/MessageHandlerResolver.php +++ b/src/Symfony/Component/Message/HandlerLocator.php @@ -12,12 +12,12 @@ namespace Symfony\Component\Message; use Symfony\Component\Message\Exception\NoHandlerForMessageException; -use Symfony\Component\Message\Handler\CollectionOfMessageHandlers; +use Symfony\Component\Message\Handler\MessageHandlerCollection; /** * @author Samuel Roze */ -class MessageHandlerResolver implements MessageHandlerResolverInterface +class HandlerLocator implements HandlerLocatorInterface { /** * Maps a message (its class) to a given handler. @@ -41,7 +41,7 @@ public function resolve($message): callable $handler = $this->messageToHandlerMapping[$messageKey]; if ($this->isCollectionOfHandlers($handler)) { - $handler = new CollectionOfMessageHandlers($handler); + $handler = new MessageHandlerCollection($handler); } return $handler; diff --git a/src/Symfony/Component/Message/MessageHandlerResolverInterface.php b/src/Symfony/Component/Message/HandlerLocatorInterface.php similarity index 93% rename from src/Symfony/Component/Message/MessageHandlerResolverInterface.php rename to src/Symfony/Component/Message/HandlerLocatorInterface.php index d16ccfaa3dc3c..ac27078ad5c33 100644 --- a/src/Symfony/Component/Message/MessageHandlerResolverInterface.php +++ b/src/Symfony/Component/Message/HandlerLocatorInterface.php @@ -16,7 +16,7 @@ /** * @author Samuel Roze */ -interface MessageHandlerResolverInterface +interface HandlerLocatorInterface { /** * Return the handler for the given message. diff --git a/src/Symfony/Component/Message/MessageBus.php b/src/Symfony/Component/Message/MessageBus.php index 263bdd4d7b585..e218c550a4de2 100644 --- a/src/Symfony/Component/Message/MessageBus.php +++ b/src/Symfony/Component/Message/MessageBus.php @@ -18,12 +18,12 @@ class MessageBus implements MessageBusInterface { /** - * @var MessageBusMiddlewareInterface[] + * @var MiddlewareInterface[] */ private $middlewares; /** - * @param MessageBusMiddlewareInterface[] $middlewares + * @param MiddlewareInterface[] $middlewares */ public function __construct(array $middlewares = array()) { @@ -33,7 +33,7 @@ public function __construct(array $middlewares = array()) /** * {@inheritdoc} */ - public function handle($message) + public function dispatch($message) { call_user_func($this->callableForNextMiddleware(0), $message); } diff --git a/src/Symfony/Component/Message/MessageBusInterface.php b/src/Symfony/Component/Message/MessageBusInterface.php index fb38a97b50cfc..b66f070623a03 100644 --- a/src/Symfony/Component/Message/MessageBusInterface.php +++ b/src/Symfony/Component/Message/MessageBusInterface.php @@ -17,7 +17,7 @@ interface MessageBusInterface { /** - * Handle the message. + * Dispatch the given message. * * The bus can return a value coming from handlers, but is not required to do so. * @@ -25,5 +25,5 @@ interface MessageBusInterface * * @return mixed */ - public function handle($message); + public function dispatch($message); } diff --git a/src/Symfony/Component/Message/Middleware/CallMessageHandlerMiddleware.php b/src/Symfony/Component/Message/Middleware/HandleMessageMiddleware.php similarity index 68% rename from src/Symfony/Component/Message/Middleware/CallMessageHandlerMiddleware.php rename to src/Symfony/Component/Message/Middleware/HandleMessageMiddleware.php index 8729f1d825390..94d331d6100f8 100644 --- a/src/Symfony/Component/Message/Middleware/CallMessageHandlerMiddleware.php +++ b/src/Symfony/Component/Message/Middleware/HandleMessageMiddleware.php @@ -11,20 +11,20 @@ namespace Symfony\Component\Message\Middleware; -use Symfony\Component\Message\MessageBusMiddlewareInterface; -use Symfony\Component\Message\MessageHandlerResolverInterface; +use Symfony\Component\Message\MiddlewareInterface; +use Symfony\Component\Message\HandlerLocatorInterface; /** * @author Samuel Roze */ -class CallMessageHandlerMiddleware implements MessageBusMiddlewareInterface +class HandleMessageMiddleware implements MiddlewareInterface { /** - * @var MessageHandlerResolverInterface + * @var HandlerLocatorInterface */ private $messageHandlerResolver; - public function __construct(MessageHandlerResolverInterface $messageHandlerResolver) + public function __construct(HandlerLocatorInterface $messageHandlerResolver) { $this->messageHandlerResolver = $messageHandlerResolver; } diff --git a/src/Symfony/Component/Message/MessageBusMiddlewareInterface.php b/src/Symfony/Component/Message/MiddlewareInterface.php similarity index 92% rename from src/Symfony/Component/Message/MessageBusMiddlewareInterface.php rename to src/Symfony/Component/Message/MiddlewareInterface.php index 01fbdf9865084..db953f2097025 100644 --- a/src/Symfony/Component/Message/MessageBusMiddlewareInterface.php +++ b/src/Symfony/Component/Message/MiddlewareInterface.php @@ -14,7 +14,7 @@ /** * @author Samuel Roze */ -interface MessageBusMiddlewareInterface +interface MiddlewareInterface { /** * @param object $message diff --git a/src/Symfony/Component/Message/README.md b/src/Symfony/Component/Message/README.md index 0479b80fe8c3b..1c10560188487 100644 --- a/src/Symfony/Component/Message/README.md +++ b/src/Symfony/Component/Message/README.md @@ -18,17 +18,36 @@ Documentation **Note:** this documentation is to be moved to symfony.com when merging the Component. +### Concepts + +![Component overview](Resources/doc/component-overview.png) + +1. **Sender** + Responsible for serializing and sending the message to _something_. This something can be a message broker or a 3rd + party API for example. + +2. **Receiver** + Responsible for deserializing and forwarding the messages to handler(s). This can be a message queue puller or an API + endpoint for example. + +3. **Handler** + Given a received message, contains the user business logic related to the message. In practice, that is just a PHP + callable. + + ### Bus -The bus is used to dispatch and handle messages. MessageBus' behaviour is in its ordered middleware stack. When using +The bus is used to dispatch messages. MessageBus' behaviour is in its ordered middleware stack. When using the message bus with Symfony's FrameworkBundle, the following middlewares are configured for you: -1. `LogMessagesMiddleware` (log the processing of your messages) -2. `SendMessageOnProducersBasedOnRoutingMiddleware` (enable asynchronous processing) -3. `CallMessageHandlerMiddleware` (call the registered handle) +1. `LoggingMiddleware` (log the processing of your messages) +2. `SendMessageMiddleware` (enable asynchronous processing) +3. `HandleMessageMiddleware` (call the registered handle) ```php -$result = $this->get('message_bus')->dispatch(new MyMessage(/* ... */)); +use App\Message\MyMessage; + +$result = $this->get('message_bus')->handle(new MyMessage(/* ... */)); ``` ### Handlers @@ -38,6 +57,10 @@ Once dispatched to the bus, messages will be handled by a "message handler". A m result. ```php +namespace App\MessageHandler; + +use App\Message\MyMessage; + class MyMessageHandler { public function __invoke(MyMessage $message) @@ -49,7 +72,7 @@ class MyMessageHandler ```xml - + ``` @@ -69,7 +92,7 @@ following adapters: #### Routing -When doing asynchronous processing, the key is to route the message to the right producer. As the routing is +When doing asynchronous processing, the key is to route the message to the right sender. As the routing is application-specific and not message-specific, the configuration can be made within the `framework.yaml` configuration file as well: @@ -77,7 +100,7 @@ configuration file as well: framework: message: routing: - 'My\Message\MessageAboutDoingOperationalWork': my_operations_producer + 'My\Message\MessageAboutDoingOperationalWork': my_operations_queue_sender ``` Such configuration would only route the `MessageAboutDoingOperationalWork` message to be asynchronous, the rest of the @@ -88,41 +111,39 @@ If you want to do route all the messages to a queue by default, you can use such framework: message: routing: - 'My\Message\MessageAboutDoingOperationalWork': my_operations_producer - '*': my_default_producer + 'My\Message\MessageAboutDoingOperationalWork': my_operations_queue_sender + '*': my_default_sender ``` -Note that you can also route a message to multiple producers at the same time: +Note that you can also route a message to multiple senders at the same time: ```yaml framework: message: routing: - 'My\Message\AnImportantMessage': [my_default_producer, my_audit_producer] + 'My\Message\AnImportantMessage': [my_default_sender, my_audit_semder] ``` -#### Same bus consumer and producer - -To allow us to consume and produce messages on the same bus and prevent a loop, the message bus is equipped with the -`SendMessageOnProducersBasedOnRoutingMiddleware` middleware and a `WrappedIntoConsumedMessageConsumer` consumer. -The consumer will wraps the received messages into `ConsumedMessage` objects and the middleware will not forward -these messages to the producers. +#### Same bus received and sender +To allow us to receive and send messages on the same bus and prevent a loop, the message bus is equipped with the +`WrappedIntoReceivedMessage` received. It will wraps the received messages into `ReceivedMessage` objects and the +`SendMessageMiddleware` middleware will know it should not send these messages. -### Your own producer +### Your own sender -Using the `MessageProducerInterface`, you can easily create your own message producer. Let's say you already have an -`ImportantAction` message going through the message bus and handled by a handler. Now, you also want to produce this +Using the `SenderInterface`, you can easily create your own message sender. Let's say you already have an +`ImportantAction` message going through the message bus and handled by a handler. Now, you also want to send this message as an email. -1. Create your producer +1. Create your sender ```php -namespace App\MessageProducer; +namespace App\MessageSender; -use Symfony\Component\Message\MessageProducerInterface; +use Symfony\Component\Message\SenderInterface; use App\Message\ImportantAction; -class ImportantActionToEmailProducer implements MessageProducerInterface +class ImportantActionToEmailSender implements SenderInterface { private $toEmail; private $mailer; @@ -133,7 +154,7 @@ class ImportantActionToEmailProducer implements MessageProducerInterface $this->toEmail = $toEmail; } - public function produce($message) + public function send($message) { if (!$message instanceof ImportantAction) { throw new \InvalidArgumentException(sprintf('Producer only supports "%s" messages', ImportantAction::class)); @@ -151,47 +172,47 @@ class ImportantActionToEmailProducer implements MessageProducerInterface } ``` -2. Register your producer service +2. Register your sender service ```xml - + %to_email% ``` -3. Route your important message to the producer +3. Route your important message to the sender ```yaml framework: message: routing: - 'App\Message\ImportantAction': [app.message_producer.important_action_to_email, ~] + 'App\Message\ImportantAction': [App\MessageSender\ImportantActionToEmailSender, ~] ``` -**Note:** this example shows you how you can at the same time produce your message and directly handle it using a `null` -(`~`) producer. +**Note:** this example shows you how you can at the same time send your message and directly handle it using a `null` +(`~`) sender. -### Your own consumer +### Your own received -A consumer is responsible of reading messages from a source and dispatching them to the application. +A consumer is responsible of receiving messages from a source and dispatching them to the application. Let's say you already proceed some "orders" on your application using a `NewOrder` message. Now you want to integrate with a 3rd party or a legacy application but you can't use an API and need to use a shared CSV file with new orders. You will read this CSV file and dispatch a `NewOrder` message. All you need to do is your custom CSV consumer and Symfony will do the rest. -1. Create your consumer +1. Create your receiver ```php -namespace App\MessageConsumer; +namespace App\MessageReceiver; -use Symfony\Component\Message\MessageConsumerInterface; +use Symfony\Component\Message\ReceiverInterface; use Symfony\Component\Serializer\SerializerInterface; use App\Message\NewOrder; -class NewOrdersFromCsvFile implements MessageConsumerInterface +class NewOrdersFromCsvFile implements ReceiverInterface { private $serializer; private $filePath; @@ -202,7 +223,7 @@ class NewOrdersFromCsvFile implements MessageConsumerInterface $this->filePath = $filePath; } - public function consume() : \Generator + public function receive() : \Generator { $ordersFromCsv = $this->serializer->deserialize(file_get_contents($this->filePath), 'csv'); @@ -216,7 +237,7 @@ class NewOrdersFromCsvFile implements MessageConsumerInterface 2. Register your consumer service ```xml - + %new_orders_csv_file_path% @@ -225,5 +246,5 @@ class NewOrdersFromCsvFile implements MessageConsumerInterface 3. Use your consumer ```bash -$ bin/console message:consume app.message_consumer.new_orders_from_csv_file +$ bin/console message:consume App\MessageReceived\NewOrdersFromCsvFile ``` diff --git a/src/Symfony/Component/Message/Resources/doc/component-overview.png b/src/Symfony/Component/Message/Resources/doc/component-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..074255b4667f5d05b0b158de6817decf15f8e086 GIT binary patch literal 22425 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 BliC0P literal 0 HcmV?d00001 diff --git a/src/Symfony/Component/Message/MessageConsumerInterface.php b/src/Symfony/Component/Message/Transport/ReceiverInterface.php similarity index 71% rename from src/Symfony/Component/Message/MessageConsumerInterface.php rename to src/Symfony/Component/Message/Transport/ReceiverInterface.php index 70c5747d3b5d3..325501952a1da 100644 --- a/src/Symfony/Component/Message/MessageConsumerInterface.php +++ b/src/Symfony/Component/Message/Transport/ReceiverInterface.php @@ -9,12 +9,12 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Message; +namespace Symfony\Component\Message\Transport; /** * @author Samuel Roze */ -interface MessageConsumerInterface +interface ReceiverInterface { - public function consume(): \Generator; + public function receive(): \Generator; } diff --git a/src/Symfony/Component/Message/MessageProducerInterface.php b/src/Symfony/Component/Message/Transport/SenderInterface.php similarity index 70% rename from src/Symfony/Component/Message/MessageProducerInterface.php rename to src/Symfony/Component/Message/Transport/SenderInterface.php index 10b77828229cb..d5e2a6b5d9bcb 100644 --- a/src/Symfony/Component/Message/MessageProducerInterface.php +++ b/src/Symfony/Component/Message/Transport/SenderInterface.php @@ -9,17 +9,17 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Message; +namespace Symfony\Component\Message\Transport; /** * @author Samuel Roze */ -interface MessageProducerInterface +interface SenderInterface { /** - * Produce the given message. + * Send the given message. * * @param object $message */ - public function produce($message); + public function send($message); } diff --git a/src/Symfony/Component/Message/Transport/MessageDecoderInterface.php b/src/Symfony/Component/Message/Transport/Serialization/DecoderInterface.php similarity index 89% rename from src/Symfony/Component/Message/Transport/MessageDecoderInterface.php rename to src/Symfony/Component/Message/Transport/Serialization/DecoderInterface.php index 9613a329062e7..57e1504a101f1 100644 --- a/src/Symfony/Component/Message/Transport/MessageDecoderInterface.php +++ b/src/Symfony/Component/Message/Transport/Serialization/DecoderInterface.php @@ -9,12 +9,12 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Message\Transport; +namespace Symfony\Component\Message\Transport\Serialization; /** * @author Samuel Roze */ -interface MessageDecoderInterface +interface DecoderInterface { /** * Decode the message from an encoded-form. The `$encodedMessage` parameter is a key-value array that diff --git a/src/Symfony/Component/Message/Transport/MessageEncoderInterface.php b/src/Symfony/Component/Message/Transport/Serialization/EncoderInterface.php similarity index 89% rename from src/Symfony/Component/Message/Transport/MessageEncoderInterface.php rename to src/Symfony/Component/Message/Transport/Serialization/EncoderInterface.php index b0292b5bf2666..d6ae1906c518b 100644 --- a/src/Symfony/Component/Message/Transport/MessageEncoderInterface.php +++ b/src/Symfony/Component/Message/Transport/Serialization/EncoderInterface.php @@ -9,12 +9,12 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Message\Transport; +namespace Symfony\Component\Message\Transport\Serialization; /** * @author Samuel Roze */ -interface MessageEncoderInterface +interface EncoderInterface { /** * Encode a message to a common format understandable by adapters. The encoded array should only diff --git a/src/Symfony/Component/Message/Transport/SerializeMessageWithTypeInHeaders.php b/src/Symfony/Component/Message/Transport/Serialization/SymfonySerialization.php similarity index 90% rename from src/Symfony/Component/Message/Transport/SerializeMessageWithTypeInHeaders.php rename to src/Symfony/Component/Message/Transport/Serialization/SymfonySerialization.php index 799240e68cabd..dd8ad062a2c2b 100644 --- a/src/Symfony/Component/Message/Transport/SerializeMessageWithTypeInHeaders.php +++ b/src/Symfony/Component/Message/Transport/Serialization/SymfonySerialization.php @@ -9,14 +9,14 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Message\Transport; +namespace Symfony\Component\Message\Transport\Serialization; use Symfony\Component\Serializer\SerializerInterface; /** * @author Samuel Roze */ -class SerializeMessageWithTypeInHeaders implements MessageDecoderInterface, MessageEncoderInterface +class SymfonySerialization implements DecoderInterface, EncoderInterface { /** * @var SerializerInterface From 29a15e5af3c30a6025874099c74cc1579ead018c Mon Sep 17 00:00:00 2001 From: Miha Vrhovnik Date: Sat, 23 Dec 2017 15:01:51 +0100 Subject: [PATCH 03/16] Add return to dispatch method and no need for handles parameter. We can find automatically the Message handler is supposed to handle --- .../DependencyInjection/MessagePass.php | 23 ++++++++++++++++--- src/Symfony/Component/Message/MessageBus.php | 2 +- src/Symfony/Component/Message/README.md | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php index 99420ff9ee6bc..142a5ccd36b08 100644 --- a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php +++ b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php @@ -59,12 +59,29 @@ private function findHandlers(ContainerBuilder $container): array foreach ($container->findTaggedServiceIds($this->handlerTag, true) as $serviceId => $tags) { foreach ($tags as $tag) { - if (!isset($tag['handles'])) { - throw new RuntimeException(sprintf('Tag "%s" on service "%s" should have an `handles` attribute', $this->handlerTag, $serviceId)); + $reflection = new \ReflectionClass($container->getDefinition($serviceId)->getClass()); + + try { + $method = $reflection->getMethod('__invoke'); + } catch (\ReflectionException $e) { + throw new RuntimeException(sprintf('Service "%s" should have an `__invoke` function', $serviceId)); + } + + $parameters = $method->getParameters(); + if (count($parameters) !== 1) { + throw new RuntimeException(sprintf('`__invoke` function of service "%s" must have exactly one parameter', $serviceId)); + } + + $parameter = $parameters[0]; + if (null === $parameter->getClass()) { + throw new RuntimeException(sprintf('The parameter of `__invoke` function of service "%s" must type hint the Message class it handles', $serviceId)); + } + if (!class_exists($handles = $parameter->getClass()->getName())) { + throw new RuntimeException(sprintf('The message class "%s" declared in `__invoke` function of service "%s" does not exist', $handles, $serviceId)); } $priority = isset($tag['priority']) ? $tag['priority'] : 0; - $handlersByMessage[$tag['handles']][$priority][] = new Reference($serviceId); + $handlersByMessage[$handles][$priority][] = new Reference($serviceId); } } diff --git a/src/Symfony/Component/Message/MessageBus.php b/src/Symfony/Component/Message/MessageBus.php index e218c550a4de2..0b8241ff26bf7 100644 --- a/src/Symfony/Component/Message/MessageBus.php +++ b/src/Symfony/Component/Message/MessageBus.php @@ -35,7 +35,7 @@ public function __construct(array $middlewares = array()) */ public function dispatch($message) { - call_user_func($this->callableForNextMiddleware(0), $message); + return call_user_func($this->callableForNextMiddleware(0), $message); } private function callableForNextMiddleware($index): callable diff --git a/src/Symfony/Component/Message/README.md b/src/Symfony/Component/Message/README.md index 1c10560188487..593f3dbc3b004 100644 --- a/src/Symfony/Component/Message/README.md +++ b/src/Symfony/Component/Message/README.md @@ -72,7 +72,7 @@ class MyMessageHandler ```xml - + ``` From dd0d640d44bbeb5434edf4bf2d7376d6d479825e Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Sun, 24 Dec 2017 11:44:36 +0100 Subject: [PATCH 04/16] Rename Producer -> Sender in the FrameworkBundle as well --- .../FrameworkBundle/DependencyInjection/Configuration.php | 4 ++-- .../DependencyInjection/FrameworkExtension.php | 8 ++++---- .../Bundle/FrameworkBundle/Resources/config/message.xml | 6 +++--- .../Component/Message/DependencyInjection/MessagePass.php | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 208d9b7133a06..f62b3fb02aa90 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -911,11 +911,11 @@ private function addMessageSection(ArrayNodeDefinition $rootNode) ->beforeNormalization() ->ifString() ->then(function ($v) { - return array('producers' => array($v)); + return array('senders' => array($v)); }) ->end() ->children() - ->arrayNode('producers') + ->arrayNode('senders') ->requiresAtLeastOneElement() ->prototype('scalar')->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index a38563930cd90..0ebd571255d8f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1367,14 +1367,14 @@ private function registerMessageConfiguration(array $config, ContainerBuilder $c { $loader->load('message.xml'); - $messageToProducerMapping = array(); + $messageToSenderMapping = array(); foreach ($config['routing'] as $message => $messageConfiguration) { - $messageToProducerMapping[$message] = array_map(function (string $serviceName) { + $messageToSenderMapping[$message] = array_map(function (string $serviceName) { return new Reference($serviceName); - }, $messageConfiguration['producers']); + }, $messageConfiguration['senders']); } - $container->getDefinition('message.asynchronous.routing.producer_for_message_resolver')->setArgument(0, $messageToProducerMapping); + $container->getDefinition('message.asynchronous.routing.sender_locator')->setArgument(0, $messageToSenderMapping); } private function registerCacheConfiguration(array $config, ContainerBuilder $container) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml index d728eda6d4d7c..ba3323a0a8cc4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml @@ -30,11 +30,11 @@ - - + + - + diff --git a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php index 142a5ccd36b08..545bc3276a4b6 100644 --- a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php +++ b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php @@ -68,7 +68,7 @@ private function findHandlers(ContainerBuilder $container): array } $parameters = $method->getParameters(); - if (count($parameters) !== 1) { + if (1 !== count($parameters)) { throw new RuntimeException(sprintf('`__invoke` function of service "%s" must have exactly one parameter', $serviceId)); } From ac805299da536c1eaaff8d9175a117b2b6c1017a Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Sun, 24 Dec 2017 11:47:02 +0100 Subject: [PATCH 05/16] Do not pass anything if the message_bus service do not exists --- .../Component/Message/DependencyInjection/MessagePass.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php index 545bc3276a4b6..406491039e7bd 100644 --- a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php +++ b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php @@ -42,6 +42,10 @@ public function __construct(string $messageBusService = 'message_bus', string $m */ public function process(ContainerBuilder $container) { + if (!$container->hasDefinition($this->messageBusService)) { + return; + } + if (!$middlewares = $this->findAndSortTaggedServices($this->middlewareTag, $container)) { throw new RuntimeException(sprintf('You must tag at least one service as "%s" to use the "%s" service.', $this->middlewareTag, $this->messageBusService)); } From 923324bb90b49e65227ac0497551df796bef6f49 Mon Sep 17 00:00:00 2001 From: Miha Vrhovnik Date: Mon, 25 Dec 2017 08:10:19 +0100 Subject: [PATCH 06/16] service class typo fix. --- src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml index ba3323a0a8cc4..9abd38021808a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml @@ -33,7 +33,7 @@ - + From af34b5cdd9720d8848b73064041e8f33ff1ca406 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Mon, 25 Dec 2017 12:46:27 +0100 Subject: [PATCH 07/16] Various valid changes based on reviews --- .../Command/MessageConsumeCommand.php | 14 +++--- .../Resources/config/console.xml | 4 +- .../DependencyInjection/ConfigurationTest.php | 5 ++ .../Middleware/SendMessageMiddleware.php | 3 -- .../Transport/WrapIntoReceivedMessage.php | 2 +- .../Message/Debug/LoggingMiddleware.php | 9 ++-- .../DependencyInjection/MessagePass.php | 49 +++++++++++-------- .../Handler/MessageHandlerCollection.php | 6 +-- src/Symfony/Component/Message/README.md | 4 +- .../Message/Transport/ReceiverInterface.php | 2 +- .../Serialization/SymfonySerialization.php | 12 +++-- 11 files changed, 65 insertions(+), 45 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php index 3a9877ff83802..65a57e4202830 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php @@ -19,7 +19,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Message\Asynchronous\Transport\ReceivedMessage; use Symfony\Component\Message\MessageBusInterface; -use Symfony\Component\Message\MessageConsumerInterface; +use Symfony\Component\Message\Transport\ReceiverInterface; /** * @author Samuel Roze @@ -35,7 +35,7 @@ protected function configure() { $this ->setDefinition(array( - new InputArgument('consumer', InputArgument::REQUIRED, 'Name of the consumer'), + new InputArgument('receiver', InputArgument::REQUIRED, 'Name of the receiver'), new InputOption('bus', 'b', InputOption::VALUE_REQUIRED, 'Name of the bus to dispatch the messages to', 'message_bus'), )) ->setDescription('Consume a message') @@ -57,10 +57,10 @@ protected function execute(InputInterface $input, OutputInterface $output) /** @var ContainerInterface $container */ $container = $this->getApplication()->getKernel()->getContainer(); - if (!$container->has($consumerName = $input->getArgument('consumer'))) { - throw new \RuntimeException(sprintf('Consumer "%s" do not exists', $consumerName)); - } elseif (!($consumer = $container->get($consumerName)) instanceof MessageConsumerInterface) { - throw new \RuntimeException(sprintf('Consumer "%s" is not a valid message consumer. It should implement the interface "%s"', $consumerName, MessageConsumerInterface::class)); + if (!$container->has($receiverName = $input->getArgument('receiver'))) { + throw new \RuntimeException(sprintf('Receiver "%s" do not exists', $receiverName)); + } elseif (!($receiver = $container->get($receiverName)) instanceof ReceiverInterface) { + throw new \RuntimeException(sprintf('Receiver "%s" is not a valid message consumer. It should implement the interface "%s"', $receiverName, ReceiverInterface::class)); } if (!$container->has($busName = $input->getOption('bus'))) { @@ -69,7 +69,7 @@ protected function execute(InputInterface $input, OutputInterface $output) throw new \RuntimeException(sprintf('Bus "%s" is not a valid message bus. It should implement the interface "%s"', $busName, MessageBusInterface::class)); } - foreach ($consumer->consume() as $message) { + foreach ($receiver->receive() as $message) { if (!$message instanceof ReceivedMessage) { $message = new ReceivedMessage($message); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml index c780f621cd7cf..703ba3d3c239a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml @@ -64,11 +64,11 @@ - + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 84921d9737d60..a0f1747383717 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Config\Definition\Processor; use Symfony\Component\Lock\Store\SemaphoreStore; +use Symfony\Component\Message\MessageBusInterface; class ConfigurationTest extends TestCase { @@ -249,6 +250,10 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor ), ), ), + 'message' => array( + 'enabled' => !class_exists(FullStack::class) && class_exists(MessageBusInterface::class), + 'routing' => array(), + ), ); } } diff --git a/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageMiddleware.php b/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageMiddleware.php index 9548c4fdf46e1..5d656121bf3af 100644 --- a/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageMiddleware.php +++ b/src/Symfony/Component/Message/Asynchronous/Middleware/SendMessageMiddleware.php @@ -20,9 +20,6 @@ */ class SendMessageMiddleware implements MiddlewareInterface { - /** - * @var SenderLocatorInterface - */ private $senderLocator; public function __construct(SenderLocatorInterface $senderLocator) diff --git a/src/Symfony/Component/Message/Asynchronous/Transport/WrapIntoReceivedMessage.php b/src/Symfony/Component/Message/Asynchronous/Transport/WrapIntoReceivedMessage.php index e4d755750ce4c..8057d70b574fb 100644 --- a/src/Symfony/Component/Message/Asynchronous/Transport/WrapIntoReceivedMessage.php +++ b/src/Symfony/Component/Message/Asynchronous/Transport/WrapIntoReceivedMessage.php @@ -28,7 +28,7 @@ public function __construct(ReceiverInterface $decoratedConsumer) $this->decoratedReceiver = $decoratedConsumer; } - public function receive(): \Generator + public function receive(): \iterable { foreach ($this->decoratedReceiver->receive() as $message) { yield new ReceivedMessage($message); diff --git a/src/Symfony/Component/Message/Debug/LoggingMiddleware.php b/src/Symfony/Component/Message/Debug/LoggingMiddleware.php index 9bc7f80164edc..c0816311571a0 100644 --- a/src/Symfony/Component/Message/Debug/LoggingMiddleware.php +++ b/src/Symfony/Component/Message/Debug/LoggingMiddleware.php @@ -34,23 +34,26 @@ public function __construct(LoggerInterface $logger) */ public function handle($message, callable $next) { - $this->logger->debug('Starting processing message', array( + $this->logger->debug('Starting processing message {class}', array( 'message' => $message, + 'class' => get_class($message), )); try { $result = $next($message); } catch (\Throwable $e) { - $this->logger->warning('Something went wrong while processing message', array( + $this->logger->warning('An exception occurred while processing message {class}', array( 'message' => $message, 'exception' => $e, + 'class' => get_class($message), )); throw $e; } - $this->logger->debug('Finished processing message', array( + $this->logger->debug('Finished processing message {class}', array( 'message' => $message, + 'class' => get_class($message), )); return $result; diff --git a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php index 406491039e7bd..3426357a371e9 100644 --- a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php +++ b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php @@ -24,17 +24,17 @@ class MessagePass implements CompilerPassInterface { use PriorityTaggedServiceTrait; - private $messageHandlerResolverService; - private $handlerTag; private $messageBusService; private $middlewareTag; + private $messageHandlerResolverService; + private $handlerTag; public function __construct(string $messageBusService = 'message_bus', string $middlewareTag = 'message_middleware', string $messageHandlerResolverService = 'message.handler_resolver', string $handlerTag = 'message_handler') { - $this->messageHandlerResolverService = $messageHandlerResolverService; - $this->handlerTag = $handlerTag; $this->messageBusService = $messageBusService; $this->middlewareTag = $middlewareTag; + $this->messageHandlerResolverService = $messageHandlerResolverService; + $this->handlerTag = $handlerTag; } /** @@ -63,24 +63,9 @@ private function findHandlers(ContainerBuilder $container): array foreach ($container->findTaggedServiceIds($this->handlerTag, true) as $serviceId => $tags) { foreach ($tags as $tag) { - $reflection = new \ReflectionClass($container->getDefinition($serviceId)->getClass()); - - try { - $method = $reflection->getMethod('__invoke'); - } catch (\ReflectionException $e) { - throw new RuntimeException(sprintf('Service "%s" should have an `__invoke` function', $serviceId)); - } - - $parameters = $method->getParameters(); - if (1 !== count($parameters)) { - throw new RuntimeException(sprintf('`__invoke` function of service "%s" must have exactly one parameter', $serviceId)); - } + $handles = isset($tag['handles']) ? $tag['handles'] : $this->guessHandledClass($container, $serviceId); - $parameter = $parameters[0]; - if (null === $parameter->getClass()) { - throw new RuntimeException(sprintf('The parameter of `__invoke` function of service "%s" must type hint the Message class it handles', $serviceId)); - } - if (!class_exists($handles = $parameter->getClass()->getName())) { + if (!class_exists($handles)) { throw new RuntimeException(sprintf('The message class "%s" declared in `__invoke` function of service "%s" does not exist', $handles, $serviceId)); } @@ -96,4 +81,26 @@ private function findHandlers(ContainerBuilder $container): array return $handlersByMessage; } + + private function guessHandledClass(ContainerBuilder $container, string $serviceId): string + { + $reflection = new \ReflectionClass($container->getDefinition($serviceId)->getClass()); + try { + $method = $reflection->getMethod('__invoke'); + } catch (\ReflectionException $e) { + throw new RuntimeException(sprintf('Service "%s" should have an `__invoke` function', $serviceId)); + } + + $parameters = $method->getParameters(); + if (1 !== count($parameters)) { + throw new RuntimeException(sprintf('`__invoke` function of service "%s" must have exactly one parameter', $serviceId)); + } + + $parameter = $parameters[0]; + if (null === $parameter->getClass()) { + throw new RuntimeException(sprintf('The parameter of `__invoke` function of service "%s" must type hint the Message class it handles', $serviceId)); + } + + return $parameter->getClass()->getName(); + } } diff --git a/src/Symfony/Component/Message/Handler/MessageHandlerCollection.php b/src/Symfony/Component/Message/Handler/MessageHandlerCollection.php index 37cc0991842b3..421a3db991366 100644 --- a/src/Symfony/Component/Message/Handler/MessageHandlerCollection.php +++ b/src/Symfony/Component/Message/Handler/MessageHandlerCollection.php @@ -37,8 +37,8 @@ public function __construct(array $handlers) public function __invoke($message) { - return array_map(function ($handler) use ($message) { - return $handler($message); - }, $this->handlers); + foreach ($this->handlers as $handler) { + yield $handler($message); + } } } diff --git a/src/Symfony/Component/Message/README.md b/src/Symfony/Component/Message/README.md index 593f3dbc3b004..a00aeca96aa19 100644 --- a/src/Symfony/Component/Message/README.md +++ b/src/Symfony/Component/Message/README.md @@ -76,6 +76,8 @@ class MyMessageHandler ``` +**Note:** If the message cannot be guessed from the handler's type-hint, use the `handles` attribute on the tag. + ### Asynchronous messages Using the Message Component is useful to decouple your application but it also very useful when you want to do some @@ -126,7 +128,7 @@ framework: #### Same bus received and sender To allow us to receive and send messages on the same bus and prevent a loop, the message bus is equipped with the -`WrappedIntoReceivedMessage` received. It will wraps the received messages into `ReceivedMessage` objects and the +`WrapIntoReceivedMessage` received. It will wrap the received messages into `ReceivedMessage` objects and the `SendMessageMiddleware` middleware will know it should not send these messages. ### Your own sender diff --git a/src/Symfony/Component/Message/Transport/ReceiverInterface.php b/src/Symfony/Component/Message/Transport/ReceiverInterface.php index 325501952a1da..92cd8d83eb5f4 100644 --- a/src/Symfony/Component/Message/Transport/ReceiverInterface.php +++ b/src/Symfony/Component/Message/Transport/ReceiverInterface.php @@ -16,5 +16,5 @@ */ interface ReceiverInterface { - public function receive(): \Generator; + public function receive(): \iterable; } diff --git a/src/Symfony/Component/Message/Transport/Serialization/SymfonySerialization.php b/src/Symfony/Component/Message/Transport/Serialization/SymfonySerialization.php index dd8ad062a2c2b..f838881d36f2f 100644 --- a/src/Symfony/Component/Message/Transport/Serialization/SymfonySerialization.php +++ b/src/Symfony/Component/Message/Transport/Serialization/SymfonySerialization.php @@ -23,9 +23,15 @@ class SymfonySerialization implements DecoderInterface, EncoderInterface */ private $serializer; - public function __construct(SerializerInterface $serializer) + /** + * @var string + */ + private $format; + + public function __construct(SerializerInterface $serializer, string $format = 'json') { $this->serializer = $serializer; + $this->format = $format; } /** @@ -39,7 +45,7 @@ public function decode(array $encodedMessage) throw new \InvalidArgumentException('Encoded message do not have a `type` header'); } - return $this->serializer->deserialize($encodedMessage['body'], $encodedMessage['headers']['type'], 'json'); + return $this->serializer->deserialize($encodedMessage['body'], $encodedMessage['headers']['type'], $this->format); } /** @@ -48,7 +54,7 @@ public function decode(array $encodedMessage) public function encode($message): array { return array( - 'body' => $this->serializer->serialize($message, 'json'), + 'body' => $this->serializer->serialize($message, $this->format), 'headers' => array( 'type' => get_class($message), ), From 8c3c12e7d4d4fe9aeb4c503053b2b08ebf880371 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Mon, 25 Dec 2017 13:43:47 +0100 Subject: [PATCH 08/16] Mention tags with the README --- src/Symfony/Component/Message/README.md | 35 ++++++++++++++++--------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Message/README.md b/src/Symfony/Component/Message/README.md index a00aeca96aa19..7791e77372921 100644 --- a/src/Symfony/Component/Message/README.md +++ b/src/Symfony/Component/Message/README.md @@ -91,6 +91,9 @@ following adapters: - [PHP Enqueue bridge](https://github.com/sroze/enqueue-bridge) to use one of their 10+ compatible queues such as RabbitMq, Amazon SQS or Google Pub/Sub. +- [Swarrot adapter](https://github.com/sroze/swarrot-bridge) to use Swarrot, a library specialised in consuming + messages from AMQP brokers such as RabbitMq. +- [HTTP adapter](https://github.com/sroze/message-http-adapter) to receive and send messages through HTTP APIs. #### Routing @@ -176,11 +179,15 @@ class ImportantActionToEmailSender implements SenderInterface 2. Register your sender service -```xml - - - %to_email% - +```yaml +services: + App\MessageSender\ImportantActionToEmailSender: + arguments: + - "@mailer" + - "%to_email%" + + tags: + - message.sender ``` 3. Route your important message to the sender @@ -195,7 +202,7 @@ framework: **Note:** this example shows you how you can at the same time send your message and directly handle it using a `null` (`~`) sender. -### Your own received +### Your own receiver A consumer is responsible of receiving messages from a source and dispatching them to the application. @@ -236,13 +243,17 @@ class NewOrdersFromCsvFile implements ReceiverInterface } ``` -2. Register your consumer service +2. Register your receiver service -```xml - - - %new_orders_csv_file_path% - +```yaml +services: + App\MessageReceiver\NewOrdersFromCsvFile: + arguments: + - "@serializer" + - "%new_orders_csv_file_path%" + + tags: + - message.receiver ``` 3. Use your consumer From 697b72248aa18e4ad51e58a618120d560699b382 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Mon, 25 Dec 2017 21:34:54 +0100 Subject: [PATCH 09/16] The message bus is accessible via its interface and within the controllers --- .../FrameworkBundle/Controller/AbstractController.php | 2 ++ .../Bundle/FrameworkBundle/Resources/config/message.xml | 3 +++ .../Component/Message/Handler/MessageHandlerCollection.php | 6 +++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index 2f1b2a9352410..e71195a438cf6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -18,6 +18,7 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Message\MessageBusInterface; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; @@ -84,6 +85,7 @@ public static function getSubscribedServices() 'security.token_storage' => '?'.TokenStorageInterface::class, 'security.csrf.token_manager' => '?'.CsrfTokenManagerInterface::class, 'parameter_bag' => '?'.ContainerInterface::class, + 'message_bus' => '?'.MessageBusInterface::class, ); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml index 9abd38021808a..d6f32e3771399 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml @@ -12,6 +12,9 @@ + + + diff --git a/src/Symfony/Component/Message/Handler/MessageHandlerCollection.php b/src/Symfony/Component/Message/Handler/MessageHandlerCollection.php index 421a3db991366..37cc0991842b3 100644 --- a/src/Symfony/Component/Message/Handler/MessageHandlerCollection.php +++ b/src/Symfony/Component/Message/Handler/MessageHandlerCollection.php @@ -37,8 +37,8 @@ public function __construct(array $handlers) public function __invoke($message) { - foreach ($this->handlers as $handler) { - yield $handler($message); - } + return array_map(function ($handler) use ($message) { + return $handler($message); + }, $this->handlers); } } From 383f5aa575594f890b245ce7a6447490f2e22d42 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Fri, 6 Oct 2017 15:40:27 +0100 Subject: [PATCH 10/16] Logging middleware containers the message class and uses the `message` channel --- src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml index d6f32e3771399..e9b889c5975cf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml @@ -19,6 +19,7 @@ + From 7549ea5bcda52962e220bd70b496a04842d4c502 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Sat, 7 Oct 2017 12:09:23 +0100 Subject: [PATCH 11/16] Add messages in the profiler --- .../DataCollector/MessagesDataCollector.php | 95 +++++++++++++++++++ .../Resources/config/collectors.xml | 5 + .../views/Collector/messages.html.twig | 65 +++++++++++++ .../Resources/views/Icon/messages.svg | 10 ++ 4 files changed, 175 insertions(+) create mode 100644 src/Symfony/Bundle/FrameworkBundle/DataCollector/MessagesDataCollector.php create mode 100644 src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messages.html.twig create mode 100644 src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/messages.svg diff --git a/src/Symfony/Bundle/FrameworkBundle/DataCollector/MessagesDataCollector.php b/src/Symfony/Bundle/FrameworkBundle/DataCollector/MessagesDataCollector.php new file mode 100644 index 0000000000000..6bcbfdf3cfa55 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DataCollector/MessagesDataCollector.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\Message\MiddlewareInterface; + +/** + * @author Samuel Roze + */ +class MessagesDataCollector extends DataCollector implements MiddlewareInterface +{ + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + return $this->data; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'messages'; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + $this->data = array(); + } + + /** + * {@inheritdoc} + */ + public function handle($message, callable $next) + { + $debugRepresentation = array( + 'message' => array( + 'type' => get_class($message), + ), + ); + + try { + $result = $next($message); + + if (is_object($result)) { + $debugRepresentation['result'] = array( + 'type' => get_class($result), + ); + } else { + $debugRepresentation['result'] = array( + 'type' => gettype($result), + 'value' => $result, + ); + } + } catch (\Throwable $exception) { + $debugRepresentation['exception'] = array( + 'type' => get_class($exception), + 'message' => $exception->getMessage(), + ); + } + + $this->data[] = $debugRepresentation; + + if (isset($exception)) { + throw $exception; + } + + return $result; + } + + /** + * @return array + */ + public function getMessages() + { + return $this->data; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml index 95d13761ecf5c..11d01f8e52b81 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml @@ -51,5 +51,10 @@ + + + + + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messages.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messages.html.twig new file mode 100644 index 0000000000000..aa620f70305e8 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messages.html.twig @@ -0,0 +1,65 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% import _self as helper %} + +{% block menu %} + + {{ include('@WebProfiler/Icon/messages.svg') }} + Messages + + {% if collector.messages | length > 0 %} + + {{ collector.messages | length }} + + {% endif %} + +{% endblock %} + +{% block panel %} +

Messages

+ + {% if collector.messages is empty %} +

No messages

+ {% else %} + + + + + + + + + {% for message in collector.messages %} + + + + + {% endfor %} + +
MessageResult
{{ message.message.type }} + {% if message.result.type is defined %} + {{ message.result.type }} + {% endif %} + + {% if message.exception.type is defined %} + {{ message.exception.type }} + {% endif %} +
+ {% endif %} +{% endblock %} + +{% block toolbar %} + {% set color_code = 'normal' %} + {% set message_count = 0 %} + {% set icon %} + {% if profiler_markup_version == 1 %} + {{ include('@WebProfiler/Icon/messages.svg', { height: 28, color: '#3F3F3F' }) }} + {{ message_count }} + {% else %} + {{ include('@WebProfiler/Icon/messages.svg') }} + {{ message_count }} + {% endif %} + {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: 'messages', status: color_code }) }} +{% endblock %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/messages.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/messages.svg new file mode 100644 index 0000000000000..2fd49b55fe6d5 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/messages.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file From ee806ada8ffe050075c8e2b0cdfd742b13a8dcb3 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Mon, 25 Dec 2017 23:10:25 +0100 Subject: [PATCH 12/16] Message's collector is within the same XML file, not in `collectors` --- .../Resources/config/collectors.xml | 5 ----- .../Resources/config/message.xml | 21 ++++++++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml index 11d01f8e52b81..95d13761ecf5c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml @@ -51,10 +51,5 @@
- - - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml index e9b889c5975cf..731fd96b255cb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml @@ -14,14 +14,6 @@ - - - - - - - - @@ -50,5 +42,18 @@ + + + + + + + + + + + + + From 38479685b36f10119e4e5da8bf0ce222882b0869 Mon Sep 17 00:00:00 2001 From: Miha Vrhovnik Date: Mon, 25 Dec 2017 08:08:04 +0100 Subject: [PATCH 13/16] Handlers should be lazy loaded --- .../Resources/config/message.xml | 4 +- .../Message/ContainerHandlerLocator.php | 42 +++++++++++++++++++ .../DependencyInjection/MessagePass.php | 25 +++++++++-- 3 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 src/Symfony/Component/Message/ContainerHandlerLocator.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml index 731fd96b255cb..e2809b4e5d1f0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml @@ -15,8 +15,8 @@ - - + + diff --git a/src/Symfony/Component/Message/ContainerHandlerLocator.php b/src/Symfony/Component/Message/ContainerHandlerLocator.php new file mode 100644 index 0000000000000..557d1ee99dc68 --- /dev/null +++ b/src/Symfony/Component/Message/ContainerHandlerLocator.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Message\Exception\NoHandlerForMessageException; + +/** + * @author Miha Vrhovnik + */ +class ContainerHandlerLocator implements HandlerLocatorInterface +{ + /** + * @var ContainerInterface + */ + private $serviceLocator; + + public function __construct(ContainerInterface $serviceLocator) + { + $this->serviceLocator = $serviceLocator; + } + + public function resolve($message): callable + { + $messageKey = get_class($message); + + if (!$this->serviceLocator->has($messageKey)) { + throw new NoHandlerForMessageException(sprintf('No handler for message "%s"', $messageKey)); + } + + return $this->serviceLocator->get($messageKey); + } +} diff --git a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php index 3426357a371e9..17ce540675eb0 100644 --- a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php +++ b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php @@ -13,9 +13,12 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Message\Handler\MessageHandlerCollection; /** * @author Samuel Roze @@ -53,11 +56,10 @@ public function process(ContainerBuilder $container) $busDefinition = $container->getDefinition($this->messageBusService); $busDefinition->replaceArgument(0, $middlewares); - $handlerResolver = $container->getDefinition($this->messageHandlerResolverService); - $handlerResolver->replaceArgument(0, $this->findHandlers($container)); + $this->registerHandlers($container); } - private function findHandlers(ContainerBuilder $container): array + private function registerHandlers(ContainerBuilder $container) { $handlersByMessage = array(); @@ -79,7 +81,22 @@ private function findHandlers(ContainerBuilder $container): array $handlersByMessage[$message] = call_user_func_array('array_merge', $handlersByMessage[$message]); } - return $handlersByMessage; + $definitions = array(); + foreach ($handlersByMessage as $message => $handlers) { + if (1 === count($handlers)) { + $handlersByMessage[$message] = current($handlers); + } else { + $d = new Definition(MessageHandlerCollection::class, array($handlers)); + $d->setPrivate(true); + $serviceId = hash('sha1', $message); + $definitions[$serviceId] = $d; + $handlersByMessage[$message] = new Reference($serviceId); + } + } + $container->addDefinitions($definitions); + + $handlerResolver = $container->getDefinition($this->messageHandlerResolverService); + $handlerResolver->replaceArgument(0, ServiceLocatorTagPass::register($container, $handlersByMessage)); } private function guessHandledClass(ContainerBuilder $container, string $serviceId): string From 7e1d7b5947e81cb643569403c6143ea497f5e8e0 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Wed, 27 Dec 2017 11:43:03 +0100 Subject: [PATCH 14/16] Fix multiple typos from Javier's review --- .../FrameworkBundle/Command/MessageConsumeCommand.php | 8 ++++---- .../Resources/views/Collector/messages.html.twig | 4 ++-- .../Component/Message/ContainerHandlerLocator.php | 11 ++++++----- .../Transport/Serialization/SymfonySerialization.php | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php index 65a57e4202830..8d5dd9c0082c4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/MessageConsumeCommand.php @@ -58,15 +58,15 @@ protected function execute(InputInterface $input, OutputInterface $output) $container = $this->getApplication()->getKernel()->getContainer(); if (!$container->has($receiverName = $input->getArgument('receiver'))) { - throw new \RuntimeException(sprintf('Receiver "%s" do not exists', $receiverName)); + throw new \RuntimeException(sprintf('Receiver "%s" does not exist', $receiverName)); } elseif (!($receiver = $container->get($receiverName)) instanceof ReceiverInterface) { - throw new \RuntimeException(sprintf('Receiver "%s" is not a valid message consumer. It should implement the interface "%s"', $receiverName, ReceiverInterface::class)); + throw new \RuntimeException(sprintf('Receiver "%s" is not a valid message consumer. It must implement the interface "%s"', $receiverName, ReceiverInterface::class)); } if (!$container->has($busName = $input->getOption('bus'))) { - throw new \RuntimeException(sprintf('Bus "%s" do not exists', $busName)); + throw new \RuntimeException(sprintf('Bus "%s" does not exist', $busName)); } elseif (!($messageBus = $container->get($busName)) instanceof MessageBusInterface) { - throw new \RuntimeException(sprintf('Bus "%s" is not a valid message bus. It should implement the interface "%s"', $busName, MessageBusInterface::class)); + throw new \RuntimeException(sprintf('Bus "%s" is not a valid message bus. It must implement the interface "%s"', $busName, MessageBusInterface::class)); } foreach ($receiver->receive() as $message) { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messages.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messages.html.twig index aa620f70305e8..15382f42c6bfe 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messages.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messages.html.twig @@ -7,9 +7,9 @@ {{ include('@WebProfiler/Icon/messages.svg') }} Messages - {% if collector.messages | length > 0 %} + {% if collector.messages|length > 0 %} - {{ collector.messages | length }} + {{ collector.messages|length }} {% endif %} diff --git a/src/Symfony/Component/Message/ContainerHandlerLocator.php b/src/Symfony/Component/Message/ContainerHandlerLocator.php index 557d1ee99dc68..18a9120b2166c 100644 --- a/src/Symfony/Component/Message/ContainerHandlerLocator.php +++ b/src/Symfony/Component/Message/ContainerHandlerLocator.php @@ -16,27 +16,28 @@ /** * @author Miha Vrhovnik + * @author Samuel Roze */ class ContainerHandlerLocator implements HandlerLocatorInterface { /** * @var ContainerInterface */ - private $serviceLocator; + private $container; - public function __construct(ContainerInterface $serviceLocator) + public function __construct(ContainerInterface $container) { - $this->serviceLocator = $serviceLocator; + $this->container = $container; } public function resolve($message): callable { $messageKey = get_class($message); - if (!$this->serviceLocator->has($messageKey)) { + if (!$this->container->has($messageKey)) { throw new NoHandlerForMessageException(sprintf('No handler for message "%s"', $messageKey)); } - return $this->serviceLocator->get($messageKey); + return $this->container->get($messageKey); } } diff --git a/src/Symfony/Component/Message/Transport/Serialization/SymfonySerialization.php b/src/Symfony/Component/Message/Transport/Serialization/SymfonySerialization.php index f838881d36f2f..5f1821088728f 100644 --- a/src/Symfony/Component/Message/Transport/Serialization/SymfonySerialization.php +++ b/src/Symfony/Component/Message/Transport/Serialization/SymfonySerialization.php @@ -42,7 +42,7 @@ public function decode(array $encodedMessage) if (empty($encodedMessage['body']) || empty($encodedMessage['headers'])) { throw new \InvalidArgumentException('Encoded message should have at least a `body` some `headers`'); } elseif (empty($encodedMessage['headers']['type'])) { - throw new \InvalidArgumentException('Encoded message do not have a `type` header'); + throw new \InvalidArgumentException('Encoded message does not have a `type` header'); } return $this->serializer->deserialize($encodedMessage['body'], $encodedMessage['headers']['type'], $this->format); From 93fe7cfe368f30a7cbffefed9a8714254c0914f7 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Wed, 27 Dec 2017 11:56:14 +0100 Subject: [PATCH 15/16] Rename `MessageHandlerCollection` to `ChainHandler` --- .../Component/Message/DependencyInjection/MessagePass.php | 4 ++-- .../{MessageHandlerCollection.php => ChainHandler.php} | 2 +- src/Symfony/Component/Message/HandlerLocator.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/Symfony/Component/Message/Handler/{MessageHandlerCollection.php => ChainHandler.php} (96%) diff --git a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php index 17ce540675eb0..827be308a3832 100644 --- a/src/Symfony/Component/Message/DependencyInjection/MessagePass.php +++ b/src/Symfony/Component/Message/DependencyInjection/MessagePass.php @@ -18,7 +18,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\Message\Handler\MessageHandlerCollection; +use Symfony\Component\Message\Handler\ChainHandler; /** * @author Samuel Roze @@ -86,7 +86,7 @@ private function registerHandlers(ContainerBuilder $container) if (1 === count($handlers)) { $handlersByMessage[$message] = current($handlers); } else { - $d = new Definition(MessageHandlerCollection::class, array($handlers)); + $d = new Definition(ChainHandler::class, array($handlers)); $d->setPrivate(true); $serviceId = hash('sha1', $message); $definitions[$serviceId] = $d; diff --git a/src/Symfony/Component/Message/Handler/MessageHandlerCollection.php b/src/Symfony/Component/Message/Handler/ChainHandler.php similarity index 96% rename from src/Symfony/Component/Message/Handler/MessageHandlerCollection.php rename to src/Symfony/Component/Message/Handler/ChainHandler.php index 37cc0991842b3..1ca2dc3470dae 100644 --- a/src/Symfony/Component/Message/Handler/MessageHandlerCollection.php +++ b/src/Symfony/Component/Message/Handler/ChainHandler.php @@ -16,7 +16,7 @@ * * @author Samuel Roze */ -class MessageHandlerCollection +class ChainHandler { /** * @var callable[] diff --git a/src/Symfony/Component/Message/HandlerLocator.php b/src/Symfony/Component/Message/HandlerLocator.php index 41da53929957d..c10450bcb3383 100644 --- a/src/Symfony/Component/Message/HandlerLocator.php +++ b/src/Symfony/Component/Message/HandlerLocator.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Message; use Symfony\Component\Message\Exception\NoHandlerForMessageException; -use Symfony\Component\Message\Handler\MessageHandlerCollection; +use Symfony\Component\Message\Handler\ChainHandler; /** * @author Samuel Roze @@ -41,7 +41,7 @@ public function resolve($message): callable $handler = $this->messageToHandlerMapping[$messageKey]; if ($this->isCollectionOfHandlers($handler)) { - $handler = new MessageHandlerCollection($handler); + $handler = new ChainHandler($handler); } return $handler; From 36a170a165457c88bcdabbcb0cc760006bc66425 Mon Sep 17 00:00:00 2001 From: Miha Vrhovnik Date: Tue, 26 Dec 2017 14:41:43 +0100 Subject: [PATCH 16/16] Added EventBus --- .../FrameworkBundle/FrameworkBundle.php | 2 + .../Resources/config/message.xml | 29 +++++++++ .../Message/DependencyInjection/EventPass.php | 20 ++++++ src/Symfony/Component/Message/EventBus.php | 20 ++++++ .../Recorder/AggregatesRecordedMessages.php | 63 +++++++++++++++++++ .../Recorder/ContainsRecordedMessages.php | 28 +++++++++ .../HandlesRecordedMessagesMiddleware.php | 57 +++++++++++++++++ .../PrivateMessageRecorderCapabilities.php | 49 +++++++++++++++ .../Recorder/PublicMessageRecorder.php | 20 ++++++ .../Message/Recorder/RecordsMessages.php | 25 ++++++++ 10 files changed, 313 insertions(+) create mode 100644 src/Symfony/Component/Message/DependencyInjection/EventPass.php create mode 100644 src/Symfony/Component/Message/EventBus.php create mode 100644 src/Symfony/Component/Message/Recorder/AggregatesRecordedMessages.php create mode 100644 src/Symfony/Component/Message/Recorder/ContainsRecordedMessages.php create mode 100644 src/Symfony/Component/Message/Recorder/HandlesRecordedMessagesMiddleware.php create mode 100644 src/Symfony/Component/Message/Recorder/PrivateMessageRecorderCapabilities.php create mode 100644 src/Symfony/Component/Message/Recorder/PublicMessageRecorder.php create mode 100644 src/Symfony/Component/Message/Recorder/RecordsMessages.php diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 12369cee787df..9a77531c1ce4b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -32,6 +32,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass; use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass; +use Symfony\Component\Message\DependencyInjection\EventPass; use Symfony\Component\Message\DependencyInjection\MessagePass; use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass; use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass; @@ -116,6 +117,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new WorkflowGuardListenerPass()); $container->addCompilerPass(new ResettableServicePass()); $this->addCompilerPassIfExists($container, MessagePass::class); + $this->addCompilerPassIfExists($container, EventPass::class); if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml index e2809b4e5d1f0..fe252ab21944c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/message.xml @@ -55,5 +55,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/Message/DependencyInjection/EventPass.php b/src/Symfony/Component/Message/DependencyInjection/EventPass.php new file mode 100644 index 0000000000000..9a901c30a5076 --- /dev/null +++ b/src/Symfony/Component/Message/DependencyInjection/EventPass.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\DependencyInjection; + +class EventPass extends MessagePass +{ + public function __construct(string $eventBusService = 'event_bus', string $middlewareTag = 'event_middleware', string $eventHandlerResolverService = 'message.event_handler_resolver', string $handlerTag = 'event_handler') + { + parent::__construct($eventBusService, $middlewareTag, $eventHandlerResolverService, $handlerTag); + } +} diff --git a/src/Symfony/Component/Message/EventBus.php b/src/Symfony/Component/Message/EventBus.php new file mode 100644 index 0000000000000..a2333d3bf11e6 --- /dev/null +++ b/src/Symfony/Component/Message/EventBus.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message; + +/** + * @author Samuel Roze + * @author Matthias Noback + */ +class EventBus extends MessageBus +{ +} diff --git a/src/Symfony/Component/Message/Recorder/AggregatesRecordedMessages.php b/src/Symfony/Component/Message/Recorder/AggregatesRecordedMessages.php new file mode 100644 index 0000000000000..f7e8e1d1482b1 --- /dev/null +++ b/src/Symfony/Component/Message/Recorder/AggregatesRecordedMessages.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Recorder; + +/** + * @author Matthias Noback + */ +class AggregatesRecordedMessages implements ContainsRecordedMessages +{ + /** + * @var ContainsRecordedMessages[] + */ + private $messageRecorders; + + public function __construct(array $messageRecorders) + { + foreach ($messageRecorders as $messageRecorder) { + $this->addMessageRecorder($messageRecorder); + } + } + + /** + * Get messages recorded by all known message recorders. + * + * {@inheritdoc} + */ + public function recordedMessages(): array + { + $allRecordedMessages = []; + + foreach ($this->messageRecorders as $messageRecorder) { + $allRecordedMessages = array_merge($allRecordedMessages, $messageRecorder->recordedMessages()); + } + + return $allRecordedMessages; + } + + /** + * Erase messages recorded by all known message recorders. + * + * {@inheritdoc} + */ + public function eraseMessages(): void + { + foreach ($this->messageRecorders as $messageRecorder) { + $messageRecorder->eraseMessages(); + } + } + + private function addMessageRecorder(ContainsRecordedMessages $messageRecorder) + { + $this->messageRecorders[] = $messageRecorder; + } +} diff --git a/src/Symfony/Component/Message/Recorder/ContainsRecordedMessages.php b/src/Symfony/Component/Message/Recorder/ContainsRecordedMessages.php new file mode 100644 index 0000000000000..9b480a775caf5 --- /dev/null +++ b/src/Symfony/Component/Message/Recorder/ContainsRecordedMessages.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 Symfony\Component\Message\Recorder; + +/** + * @author Matthias Noback + */ +interface ContainsRecordedMessages +{ + /** + * Fetch recorded messages. + */ + public function recordedMessages(): array; + + /** + * Erase messages that were recorded since the last call to eraseMessages(). + */ + public function eraseMessages(): void; +} diff --git a/src/Symfony/Component/Message/Recorder/HandlesRecordedMessagesMiddleware.php b/src/Symfony/Component/Message/Recorder/HandlesRecordedMessagesMiddleware.php new file mode 100644 index 0000000000000..872a8506c6642 --- /dev/null +++ b/src/Symfony/Component/Message/Recorder/HandlesRecordedMessagesMiddleware.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Recorder; + +use Exception; +use Symfony\Component\Message\MessageBusInterface; +use Symfony\Component\Message\MiddlewareInterface; + +/** + * @author Matthias Noback + */ +class HandlesRecordedMessagesMiddleware implements MiddlewareInterface +{ + /** + * @var ContainsRecordedMessages + */ + private $messageRecorder; + + /** + * @var MessageBusInterface + */ + private $messageBus; + + public function __construct(ContainsRecordedMessages $messageRecorder, MessageBusInterface $messageBus) + { + $this->messageRecorder = $messageRecorder; + $this->messageBus = $messageBus; + } + + public function handle($message, callable $next) + { + try { + $next($message); + } catch (Exception $exception) { + $this->messageRecorder->eraseMessages(); + + throw $exception; + } + + $recordedMessages = $this->messageRecorder->recordedMessages(); + + $this->messageRecorder->eraseMessages(); + + foreach ($recordedMessages as $recordedMessage) { + $this->messageBus->dispatch($recordedMessage); + } + } +} diff --git a/src/Symfony/Component/Message/Recorder/PrivateMessageRecorderCapabilities.php b/src/Symfony/Component/Message/Recorder/PrivateMessageRecorderCapabilities.php new file mode 100644 index 0000000000000..428c15006f061 --- /dev/null +++ b/src/Symfony/Component/Message/Recorder/PrivateMessageRecorderCapabilities.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Recorder; + +/** + * @author Matthias Noback + * + * Use this trait in classes which implement ContainsRecordedMessages to privately record and later release Message + * instances, like events. + */ +trait PrivateMessageRecorderCapabilities +{ + private $messages = []; + + /** + * {@inheritdoc} + */ + public function recordedMessages(): array + { + return $this->messages; + } + + /** + * {@inheritdoc} + */ + public function eraseMessages(): void + { + $this->messages = []; + } + + /** + * Record a message. + * + * @param object $message + */ + protected function record($message): void + { + $this->messages[] = $message; + } +} diff --git a/src/Symfony/Component/Message/Recorder/PublicMessageRecorder.php b/src/Symfony/Component/Message/Recorder/PublicMessageRecorder.php new file mode 100644 index 0000000000000..6497ce92202f0 --- /dev/null +++ b/src/Symfony/Component/Message/Recorder/PublicMessageRecorder.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Recorder; + +/** + * @author Matthias Noback + */ +class PublicMessageRecorder implements RecordsMessages +{ + use PrivateMessageRecorderCapabilities { record as public; } +} diff --git a/src/Symfony/Component/Message/Recorder/RecordsMessages.php b/src/Symfony/Component/Message/Recorder/RecordsMessages.php new file mode 100644 index 0000000000000..a5384e00de7e9 --- /dev/null +++ b/src/Symfony/Component/Message/Recorder/RecordsMessages.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Message\Recorder; + +/** + * @author Matthias Noback + */ +interface RecordsMessages extends ContainsRecordedMessages +{ + /** + * Record a message. + * + * @param object $message + */ + public function record($message); +}