From faf9382223ad6ac3e8139ceffd8bc105f9d8a656 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Tue, 8 May 2018 22:22:44 +0100 Subject: [PATCH 01/18] Add more tests around the AMQP transport --- .../FrameworkExtensionTest.php | 3 + .../AmqpExt/AmqpTransportFactoryTest.php | 48 ++++++++++++++ .../Transport/AmqpExt/AmqpTransportTest.php | 66 +++++++++++++++++++ .../Transport/AmqpExt/AmqpTransport.php | 18 ++--- .../AmqpExt/AmqpTransportFactory.php | 2 +- 5 files changed, 122 insertions(+), 15 deletions(-) create mode 100644 src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportFactoryTest.php create mode 100644 src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index eebf2b0f1b..8d94d32aa8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -36,6 +36,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage; +use Symfony\Component\Messenger\Transport\TransportFactory; use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer; @@ -528,6 +529,8 @@ abstract class FrameworkExtensionTest extends TestCase $container = $this->createContainerFromFile('messenger'); $this->assertTrue($container->hasAlias('message_bus')); $this->assertFalse($container->hasDefinition('messenger.transport.amqp.factory')); + $this->assertTrue($container->hasDefinition('messenger.transport_factory')); + $this->assertSame(TransportFactory::class, $container->getDefinition('messenger.transport_factory')->getClass()); } public function testMessengerTransports() diff --git a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportFactoryTest.php b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportFactoryTest.php new file mode 100644 index 0000000000..53a98e2263 --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportFactoryTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Tests\Transport\AmqpExt; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport; +use Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory; +use Symfony\Component\Messenger\Transport\AmqpExt\Connection; +use Symfony\Component\Messenger\Transport\Serialization\DecoderInterface; +use Symfony\Component\Messenger\Transport\Serialization\EncoderInterface; + +class AmqpTransportFactoryTest extends TestCase +{ + public function testSupportsOnlyAmqpTransports() + { + $factory = new AmqpTransportFactory( + $this->getMockBuilder(EncoderInterface::class)->getMock(), + $this->getMockBuilder(DecoderInterface::class)->getMock(), + true + ); + + $this->assertTrue($factory->supports('amqp://localhost', array())); + $this->assertFalse($factory->supports('sqs://localhost', array())); + $this->assertFalse($factory->supports('invalid-dsn', array())); + } + + public function testItCreatesTheTransport() + { + $factory = new AmqpTransportFactory( + $encoder = $this->getMockBuilder(EncoderInterface::class)->getMock(), + $decoder = $this->getMockBuilder(DecoderInterface::class)->getMock(), + true + ); + + $expectedTransport = new AmqpTransport($encoder, $decoder, Connection::fromDsn('amqp://localhost', array('foo' => 'bar'), true), array('foo' => 'bar'), true); + + $this->assertEquals($expectedTransport, $factory->createTransport('amqp://localhost', array('foo' => 'bar'))); + } +} diff --git a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportTest.php b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportTest.php new file mode 100644 index 0000000000..26f3261577 --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Tests\Transport\AmqpExt; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; +use Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport; +use Symfony\Component\Messenger\Transport\AmqpExt\Connection; +use Symfony\Component\Messenger\Transport\Serialization\DecoderInterface; +use Symfony\Component\Messenger\Transport\Serialization\EncoderInterface; +use Symfony\Component\Messenger\Transport\TransportInterface; + +/** + * @requires extension amqp + */ +class AmqpTransportTest extends TestCase +{ + public function testItIsATransport() + { + $transport = $this->getTransport(); + + $this->assertInstanceOf(TransportInterface::class, $transport); + } + + public function testReceivesMessages() + { + $transport = $this->getTransport( + null, + $decoder = $this->getMockBuilder(DecoderInterface::class)->getMock(), + $connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock() + ); + + $decodedMessage = new DummyMessage('Decoded.'); + + $amqpEnvelope = $this->getMockBuilder(\AMQPEnvelope::class)->getMock(); + $amqpEnvelope->method('getBody')->willReturn('body'); + $amqpEnvelope->method('getHeaders')->willReturn(array('my' => 'header')); + + $decoder->method('decode')->with(array('body' => 'body', 'headers' => array('my' => 'header')))->willReturn($decodedMessage); + $connection->method('get')->willReturn($amqpEnvelope); + + $transport->receive(function ($message) use ($transport, $decodedMessage) { + $this->assertSame($decodedMessage, $message); + + $transport->stop(); + }); + } + + private function getTransport(EncoderInterface $encoder = null, DecoderInterface $decoder = null, Connection $connection = null) + { + $encoder = $encoder ?: $this->getMockBuilder(EncoderInterface::class)->getMock(); + $decoder = $decoder ?: $this->getMockBuilder(DecoderInterface::class)->getMock(); + $connection = $connection ?: $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock(); + + return new AmqpTransport($encoder, $decoder, $connection); + } +} diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php index cd5fcdb489..583ec03bae 100644 --- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php +++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php @@ -22,20 +22,15 @@ class AmqpTransport implements TransportInterface { private $encoder; private $decoder; - private $dsn; - private $options; - private $debug; private $connection; private $receiver; private $sender; - public function __construct(EncoderInterface $encoder, DecoderInterface $decoder, string $dsn, array $options, bool $debug) + public function __construct(EncoderInterface $encoder, DecoderInterface $decoder, Connection $connection) { $this->encoder = $encoder; $this->decoder = $decoder; - $this->dsn = $dsn; - $this->options = $options; - $this->debug = $debug; + $this->connection = $connection; } /** @@ -64,16 +59,11 @@ class AmqpTransport implements TransportInterface private function getReceiver() { - return $this->receiver = new AmqpReceiver($this->decoder, $this->connection ?? $this->getConnection()); + return $this->receiver = new AmqpReceiver($this->decoder, $this->connection); } private function getSender() { - return $this->sender = new AmqpSender($this->encoder, $this->connection ?? $this->getConnection()); - } - - private function getConnection() - { - return $this->connection = Connection::fromDsn($this->dsn, $this->options, $this->debug); + return $this->sender = new AmqpSender($this->encoder, $this->connection); } } diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php index 3ffbb56159..29fb4ae4aa 100644 --- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php +++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php @@ -34,7 +34,7 @@ class AmqpTransportFactory implements TransportFactoryInterface public function createTransport(string $dsn, array $options): TransportInterface { - return new AmqpTransport($this->encoder, $this->decoder, $dsn, $options, $this->debug); + return new AmqpTransport($this->encoder, $this->decoder, Connection::fromDsn($dsn, $options, $this->debug)); } public function supports(string $dsn, array $options): bool From 7c33cb27abd1f809e220f1d7ec622f918de3e353 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Mon, 7 May 2018 16:30:59 +0100 Subject: [PATCH 02/18] feature #26945 [Messenger] Support configuring messages when dispatching (ogizanagi) This reverts commit 76b17b0e0f082b67236a9ba1aa61a8092d38d350. --- .../Middleware/SendMessageMiddleware.php | 14 ++-- .../Transport/ReceivedMessage.php | 16 ++-- .../Transport/WrapIntoReceivedMessage.php | 9 +- src/Symfony/Component/Messenger/Envelope.php | 80 ++++++++++++++++++ .../Messenger/EnvelopeAwareInterface.php | 23 ++++++ .../Messenger/EnvelopeItemInterface.php | 24 ++++++ .../Component/Messenger/MessageBus.php | 6 ++ .../Messenger/MessageBusInterface.php | 2 +- .../Configuration/ValidationConfiguration.php | 58 +++++++++++++ .../Middleware/LoggingMiddleware.php | 5 -- .../Middleware/ValidationMiddleware.php | 17 +++- .../Middleware/SendMessageMiddlewareTest.php | 28 +++++-- .../SerializerConfigurationTest.php | 30 +++++++ .../DependencyInjection/MessengerPassTest.php | 3 +- .../Messenger/Tests/EnvelopeTest.php | 82 +++++++++++++++++++ .../ValidationConfigurationTest.php | 38 +++++++++ .../Middleware/ValidationMiddlewareTest.php | 26 ++++++ .../AmqpExt/AmqpExtIntegrationTest.php | 26 ++++-- .../Transport/AmqpExt/AmqpReceiverTest.php | 5 +- .../Transport/AmqpExt/AmqpSenderTest.php | 7 +- .../AmqpExt/Fixtures/long_receiver.php | 12 +-- ...pWhenMemoryUsageIsExceededReceiverTest.php | 5 +- ...WhenMessageCountIsExceededReceiverTest.php | 9 +- .../Serialization/SerializerTest.php | 49 +++++++++-- .../Component/Messenger/Tests/WorkerTest.php | 25 +++--- .../Transport/AmqpExt/AmqpReceiver.php | 22 ++--- .../Transport/AmqpExt/AmqpSender.php | 11 +-- .../StopWhenMemoryUsageIsExceededReceiver.php | 5 +- ...StopWhenMessageCountIsExceededReceiver.php | 7 +- .../Messenger/Transport/ReceiverInterface.php | 6 +- .../Messenger/Transport/SenderInterface.php | 8 +- .../Serialization/DecoderInterface.php | 12 +-- .../Serialization/EncoderInterface.php | 9 +- .../Transport/Serialization/Serializer.php | 40 +++++++-- .../Serialization/SerializerConfiguration.php | 46 +++++++++++ src/Symfony/Component/Messenger/Worker.php | 10 +-- 36 files changed, 649 insertions(+), 126 deletions(-) create mode 100644 src/Symfony/Component/Messenger/Envelope.php create mode 100644 src/Symfony/Component/Messenger/EnvelopeAwareInterface.php create mode 100644 src/Symfony/Component/Messenger/EnvelopeItemInterface.php create mode 100644 src/Symfony/Component/Messenger/Middleware/Configuration/ValidationConfiguration.php create mode 100644 src/Symfony/Component/Messenger/Tests/Asynchronous/Transport/Serialization/SerializerConfigurationTest.php create mode 100644 src/Symfony/Component/Messenger/Tests/EnvelopeTest.php create mode 100644 src/Symfony/Component/Messenger/Tests/Middleware/Configuration/ValidationConfigurationTest.php create mode 100644 src/Symfony/Component/Messenger/Transport/Serialization/SerializerConfiguration.php diff --git a/src/Symfony/Component/Messenger/Asynchronous/Middleware/SendMessageMiddleware.php b/src/Symfony/Component/Messenger/Asynchronous/Middleware/SendMessageMiddleware.php index eeba363cc8..9aabe794a1 100644 --- a/src/Symfony/Component/Messenger/Asynchronous/Middleware/SendMessageMiddleware.php +++ b/src/Symfony/Component/Messenger/Asynchronous/Middleware/SendMessageMiddleware.php @@ -13,12 +13,14 @@ namespace Symfony\Component\Messenger\Asynchronous\Middleware; use Symfony\Component\Messenger\Asynchronous\Routing\SenderLocatorInterface; use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\EnvelopeAwareInterface; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; /** * @author Samuel Roze */ -class SendMessageMiddleware implements MiddlewareInterface +class SendMessageMiddleware implements MiddlewareInterface, EnvelopeAwareInterface { private $senderLocator; @@ -32,17 +34,19 @@ class SendMessageMiddleware implements MiddlewareInterface */ public function handle($message, callable $next) { - if ($message instanceof ReceivedMessage) { - return $next($message->getMessage()); + $envelope = Envelope::wrap($message); + if ($envelope->get(ReceivedMessage::class)) { + // It's a received message. Do not send it back: + return $next($message); } - if (!empty($senders = $this->senderLocator->getSendersForMessage($message))) { + if (!empty($senders = $this->senderLocator->getSendersForMessage($envelope->getMessage()))) { foreach ($senders as $sender) { if (null === $sender) { continue; } - $sender->send($message); + $sender->send($envelope); } if (!\in_array(null, $senders, true)) { diff --git a/src/Symfony/Component/Messenger/Asynchronous/Transport/ReceivedMessage.php b/src/Symfony/Component/Messenger/Asynchronous/Transport/ReceivedMessage.php index 1b1298da63..c713a589ad 100644 --- a/src/Symfony/Component/Messenger/Asynchronous/Transport/ReceivedMessage.php +++ b/src/Symfony/Component/Messenger/Asynchronous/Transport/ReceivedMessage.php @@ -12,26 +12,26 @@ namespace Symfony\Component\Messenger\Asynchronous\Transport; use Symfony\Component\Messenger\Asynchronous\Middleware\SendMessageMiddleware; +use Symfony\Component\Messenger\EnvelopeItemInterface; /** - * Wraps a received message. This is mainly used by the `SendMessageMiddleware` middleware to identify + * Marker config for 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 SendMessageMiddleware * * @author Samuel Roze */ -final class ReceivedMessage +final class ReceivedMessage implements EnvelopeItemInterface { - private $message; - - public function __construct($message) + public function serialize() { - $this->message = $message; + return ''; } - public function getMessage() + public function unserialize($serialized) { - return $this->message; + // noop } } diff --git a/src/Symfony/Component/Messenger/Asynchronous/Transport/WrapIntoReceivedMessage.php b/src/Symfony/Component/Messenger/Asynchronous/Transport/WrapIntoReceivedMessage.php index 8af87a2a45..4b10d6445f 100644 --- a/src/Symfony/Component/Messenger/Asynchronous/Transport/WrapIntoReceivedMessage.php +++ b/src/Symfony/Component/Messenger/Asynchronous/Transport/WrapIntoReceivedMessage.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Messenger\Asynchronous\Transport; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Transport\ReceiverInterface; /** @@ -27,12 +28,12 @@ class WrapIntoReceivedMessage implements ReceiverInterface public function receive(callable $handler): void { - $this->decoratedReceiver->receive(function ($message) use ($handler) { - if (null !== $message) { - $message = new ReceivedMessage($message); + $this->decoratedReceiver->receive(function (?Envelope $envelope) use ($handler) { + if (null !== $envelope) { + $envelope = $envelope->with(new ReceivedMessage()); } - $handler($message); + $handler($envelope); }); } diff --git a/src/Symfony/Component/Messenger/Envelope.php b/src/Symfony/Component/Messenger/Envelope.php new file mode 100644 index 0000000000..14778593c4 --- /dev/null +++ b/src/Symfony/Component/Messenger/Envelope.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\Component\Messenger; + +/** + * A message wrapped in an envelope with items (configurations, markers, ...). + * + * @author Maxime Steinhausser + * + * @experimental in 4.1 + */ +final class Envelope +{ + private $items = array(); + private $message; + + /** + * @param object $message + * @param EnvelopeItemInterface[] $items + */ + public function __construct($message, array $items = array()) + { + $this->message = $message; + foreach ($items as $item) { + $this->items[\get_class($item)] = $item; + } + } + + /** + * Wrap a message into an envelope if not already wrapped. + * + * @param Envelope|object $message + */ + public static function wrap($message): self + { + return $message instanceof self ? $message : new self($message); + } + + /** + * @return Envelope a new Envelope instance with additional item + */ + public function with(EnvelopeItemInterface $item): self + { + $cloned = clone $this; + + $cloned->items[\get_class($item)] = $item; + + return $cloned; + } + + public function get(string $itemFqcn): ?EnvelopeItemInterface + { + return $this->items[$itemFqcn] ?? null; + } + + /** + * @return EnvelopeItemInterface[] indexed by fqcn + */ + public function all(): array + { + return $this->items; + } + + /** + * @return object The original message contained in the envelope + */ + public function getMessage() + { + return $this->message; + } +} diff --git a/src/Symfony/Component/Messenger/EnvelopeAwareInterface.php b/src/Symfony/Component/Messenger/EnvelopeAwareInterface.php new file mode 100644 index 0000000000..c19bc84362 --- /dev/null +++ b/src/Symfony/Component/Messenger/EnvelopeAwareInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger; + +/** + * A Messenger protagonist aware of the message envelope and its content. + * + * @author Maxime Steinhausser + * + * @experimental in 4.1 + */ +interface EnvelopeAwareInterface +{ +} diff --git a/src/Symfony/Component/Messenger/EnvelopeItemInterface.php b/src/Symfony/Component/Messenger/EnvelopeItemInterface.php new file mode 100644 index 0000000000..2561a12754 --- /dev/null +++ b/src/Symfony/Component/Messenger/EnvelopeItemInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger; + +/** + * An envelope item related to a message. + * This item must be serializable for transport. + * + * @author Maxime Steinhausser + * + * @experimental in 4.1 + */ +interface EnvelopeItemInterface extends \Serializable +{ +} diff --git a/src/Symfony/Component/Messenger/MessageBus.php b/src/Symfony/Component/Messenger/MessageBus.php index f0779b1844..c467a6f79d 100644 --- a/src/Symfony/Component/Messenger/MessageBus.php +++ b/src/Symfony/Component/Messenger/MessageBus.php @@ -60,6 +60,12 @@ class MessageBus implements MessageBusInterface $middleware = $this->indexedMiddlewareHandlers[$index]; return function ($message) use ($middleware, $index) { + $message = Envelope::wrap($message); + if (!$middleware instanceof EnvelopeAwareInterface) { + // Do not provide the envelope if the middleware cannot read it: + $message = $message->getMessage(); + } + return $middleware->handle($message, $this->callableForNextMiddleware($index + 1)); }; } diff --git a/src/Symfony/Component/Messenger/MessageBusInterface.php b/src/Symfony/Component/Messenger/MessageBusInterface.php index 1d441ea568..8c2b91d1af 100644 --- a/src/Symfony/Component/Messenger/MessageBusInterface.php +++ b/src/Symfony/Component/Messenger/MessageBusInterface.php @@ -23,7 +23,7 @@ interface MessageBusInterface * * The bus can return a value coming from handlers, but is not required to do so. * - * @param object $message + * @param object|Envelope $message The message or the message pre-wrapped in an envelope * * @return mixed */ diff --git a/src/Symfony/Component/Messenger/Middleware/Configuration/ValidationConfiguration.php b/src/Symfony/Component/Messenger/Middleware/Configuration/ValidationConfiguration.php new file mode 100644 index 0000000000..1b6180857b --- /dev/null +++ b/src/Symfony/Component/Messenger/Middleware/Configuration/ValidationConfiguration.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\Messenger\Middleware\Configuration; + +use Symfony\Component\Messenger\EnvelopeItemInterface; +use Symfony\Component\Validator\Constraints\GroupSequence; + +/** + * @author Maxime Steinhausser + * + * @experimental in 4.1 + */ +final class ValidationConfiguration implements EnvelopeItemInterface +{ + private $groups; + + /** + * @param string[]|GroupSequence $groups + */ + public function __construct($groups) + { + $this->groups = $groups; + } + + public function getGroups() + { + return $this->groups; + } + + public function serialize() + { + $isGroupSequence = $this->groups instanceof GroupSequence; + + return serialize(array( + 'groups' => $isGroupSequence ? $this->groups->groups : $this->groups, + 'is_group_sequence' => $isGroupSequence, + )); + } + + public function unserialize($serialized) + { + list( + 'groups' => $groups, + 'is_group_sequence' => $isGroupSequence + ) = unserialize($serialized, array('allowed_classes' => false)); + + $this->__construct($isGroupSequence ? new GroupSequence($groups) : $groups); + } +} diff --git a/src/Symfony/Component/Messenger/Middleware/LoggingMiddleware.php b/src/Symfony/Component/Messenger/Middleware/LoggingMiddleware.php index 4de6e42575..ebaf8525c0 100644 --- a/src/Symfony/Component/Messenger/Middleware/LoggingMiddleware.php +++ b/src/Symfony/Component/Messenger/Middleware/LoggingMiddleware.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Messenger\Middleware; -use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage; use Psr\Log\LoggerInterface; /** @@ -51,10 +50,6 @@ class LoggingMiddleware implements MiddlewareInterface private function createContext($message): array { - if ($message instanceof ReceivedMessage) { - $message = $message->getMessage(); - } - return array( 'message' => $message, 'class' => \get_class($message), diff --git a/src/Symfony/Component/Messenger/Middleware/ValidationMiddleware.php b/src/Symfony/Component/Messenger/Middleware/ValidationMiddleware.php index 3b168367cd..e588d9256b 100644 --- a/src/Symfony/Component/Messenger/Middleware/ValidationMiddleware.php +++ b/src/Symfony/Component/Messenger/Middleware/ValidationMiddleware.php @@ -11,13 +11,16 @@ namespace Symfony\Component\Messenger\Middleware; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\EnvelopeAwareInterface; use Symfony\Component\Messenger\Exception\ValidationFailedException; +use Symfony\Component\Messenger\Middleware\Configuration\ValidationConfiguration; use Symfony\Component\Validator\Validator\ValidatorInterface; /** * @author Tobias Nyholm */ -class ValidationMiddleware implements MiddlewareInterface +class ValidationMiddleware implements MiddlewareInterface, EnvelopeAwareInterface { private $validator; @@ -28,9 +31,17 @@ class ValidationMiddleware implements MiddlewareInterface public function handle($message, callable $next) { - $violations = $this->validator->validate($message); + $envelope = Envelope::wrap($message); + $subject = $envelope->getMessage(); + $groups = null; + /** @var ValidationConfiguration|null $validationConfig */ + if ($validationConfig = $envelope->get(ValidationConfiguration::class)) { + $groups = $validationConfig->getGroups(); + } + + $violations = $this->validator->validate($subject, null, $groups); if (\count($violations)) { - throw new ValidationFailedException($message, $violations); + throw new ValidationFailedException($subject, $violations); } return $next($message); diff --git a/src/Symfony/Component/Messenger/Tests/Asynchronous/Middleware/SendMessageMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Asynchronous/Middleware/SendMessageMiddlewareTest.php index 6398aff361..aa22741c39 100644 --- a/src/Symfony/Component/Messenger/Tests/Asynchronous/Middleware/SendMessageMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Asynchronous/Middleware/SendMessageMiddlewareTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Asynchronous\Middleware\SendMessageMiddleware; use Symfony\Component\Messenger\Asynchronous\Routing\SenderLocatorInterface; use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Transport\SenderInterface; @@ -30,12 +31,28 @@ class SendMessageMiddlewareTest extends TestCase $sender, ))); - $sender->expects($this->once())->method('send')->with($message); + $sender->expects($this->once())->method('send')->with(Envelope::wrap($message)); $next->expects($this->never())->method($this->anything()); $middleware->handle($message, $next); } + public function testItSendsTheMessageToAssignedSenderWithPreWrappedMessage() + { + $envelope = Envelope::wrap(new DummyMessage('Hey')); + $sender = $this->getMockBuilder(SenderInterface::class)->getMock(); + $next = $this->createPartialMock(\stdClass::class, array('__invoke')); + + $middleware = new SendMessageMiddleware(new InMemorySenderLocator(array( + $sender, + ))); + + $sender->expects($this->once())->method('send')->with($envelope); + $next->expects($this->never())->method($this->anything()); + + $middleware->handle($envelope, $next); + } + public function testItAlsoCallsTheNextMiddlewareIfASenderIsNull() { $message = new DummyMessage('Hey'); @@ -47,7 +64,7 @@ class SendMessageMiddlewareTest extends TestCase null, ))); - $sender->expects($this->once())->method('send')->with($message); + $sender->expects($this->once())->method('send')->with(Envelope::wrap($message)); $next->expects($this->once())->method($this->anything()); $middleware->handle($message, $next); @@ -67,8 +84,7 @@ class SendMessageMiddlewareTest extends TestCase public function testItSkipsReceivedMessages() { - $innerMessage = new DummyMessage('Hey'); - $message = new ReceivedMessage($innerMessage); + $envelope = Envelope::wrap(new DummyMessage('Hey'))->with(new ReceivedMessage()); $sender = $this->getMockBuilder(SenderInterface::class)->getMock(); $next = $this->createPartialMock(\stdClass::class, array('__invoke')); @@ -78,9 +94,9 @@ class SendMessageMiddlewareTest extends TestCase ))); $sender->expects($this->never())->method('send'); - $next->expects($this->once())->method('__invoke')->with($innerMessage); + $next->expects($this->once())->method('__invoke')->with($envelope); - $middleware->handle($message, $next); + $middleware->handle($envelope, $next); } } diff --git a/src/Symfony/Component/Messenger/Tests/Asynchronous/Transport/Serialization/SerializerConfigurationTest.php b/src/Symfony/Component/Messenger/Tests/Asynchronous/Transport/Serialization/SerializerConfigurationTest.php new file mode 100644 index 0000000000..6ebcc8ddfa --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Asynchronous/Transport/Serialization/SerializerConfigurationTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Tests\Asynchronous\Serialization; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Transport\Serialization\SerializerConfiguration; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; + +/** + * @author Maxime Steinhausser + */ +class SerializerConfigurationTest extends TestCase +{ + public function testSerialiazable() + { + $config = new SerializerConfiguration(array(ObjectNormalizer::GROUPS => array('Default', 'Extra'))); + + $this->assertTrue(is_subclass_of(SerializerConfiguration::class, \Serializable::class, true)); + $this->assertEquals($config, unserialize(serialize($config))); + } +} diff --git a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php index 37c9aefed4..48217ba731 100644 --- a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php +++ b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\Messenger\Command\ConsumeMessagesCommand; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver; use Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender; use Symfony\Component\Messenger\Handler\Locator\ContainerHandlerLocator; @@ -357,7 +358,7 @@ class DummyReceiver implements ReceiverInterface public function receive(callable $handler): void { for ($i = 0; $i < 3; ++$i) { - $handler(new DummyMessage("Dummy $i")); + $handler(Envelope::wrap(new DummyMessage("Dummy $i"))); } } diff --git a/src/Symfony/Component/Messenger/Tests/EnvelopeTest.php b/src/Symfony/Component/Messenger/Tests/EnvelopeTest.php new file mode 100644 index 0000000000..053275b6e7 --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/EnvelopeTest.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\Messenger\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\EnvelopeAwareInterface; +use Symfony\Component\Messenger\Middleware\Configuration\ValidationConfiguration; +use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; + +/** + * @author Maxime Steinhausser + */ +class EnvelopeTest extends TestCase +{ + public function testConstruct() + { + $envelope = new Envelope($dummy = new DummyMessage('dummy'), array( + $receivedConfig = new ReceivedMessage(), + )); + + $this->assertSame($dummy, $envelope->getMessage()); + $this->assertArrayHasKey(ReceivedMessage::class, $configs = $envelope->all()); + $this->assertSame($receivedConfig, $configs[ReceivedMessage::class]); + } + + public function testWrap() + { + $first = Envelope::wrap($dummy = new DummyMessage('dummy')); + + $this->assertInstanceOf(Envelope::class, $first); + $this->assertSame($dummy, $first->getMessage()); + + $envelope = Envelope::wrap($first); + $this->assertSame($first, $envelope); + } + + public function testWithReturnsNewInstance() + { + $envelope = Envelope::wrap($dummy = new DummyMessage('dummy')); + + $this->assertNotSame($envelope, $envelope->with(new ReceivedMessage())); + } + + public function testGet() + { + $envelope = Envelope::wrap($dummy = new DummyMessage('dummy')) + ->with($config = new ReceivedMessage()) + ; + + $this->assertSame($config, $envelope->get(ReceivedMessage::class)); + $this->assertNull($envelope->get(ValidationConfiguration::class)); + } + + public function testAll() + { + $envelope = Envelope::wrap($dummy = new DummyMessage('dummy')) + ->with($receivedConfig = new ReceivedMessage()) + ->with($validationConfig = new ValidationConfiguration(array('foo'))) + ; + + $configs = $envelope->all(); + $this->assertArrayHasKey(ReceivedMessage::class, $configs); + $this->assertSame($receivedConfig, $configs[ReceivedMessage::class]); + $this->assertArrayHasKey(ValidationConfiguration::class, $configs); + $this->assertSame($validationConfig, $configs[ValidationConfiguration::class]); + } +} + +class FooConfigurationConsumer implements EnvelopeAwareInterface +{ +} diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/Configuration/ValidationConfigurationTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/Configuration/ValidationConfigurationTest.php new file mode 100644 index 0000000000..6fd6962994 --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Middleware/Configuration/ValidationConfigurationTest.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\Messenger\Tests\Middleware\Configuration; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Middleware\Configuration\ValidationConfiguration; +use Symfony\Component\Validator\Constraints\GroupSequence; + +/** + * @author Maxime Steinhausser + */ +class ValidationConfigurationTest extends TestCase +{ + public function testConfig() + { + $config = new ValidationConfiguration($groups = array('Default', 'Extra')); + $this->assertSame($groups, $config->getGroups()); + + $config = new ValidationConfiguration($groups = new GroupSequence(array('Default', 'Then'))); + $this->assertSame($groups, $config->getGroups()); + } + + public function testSerialiazable() + { + $this->assertTrue(is_subclass_of(ValidationConfiguration::class, \Serializable::class, true)); + $this->assertEquals($config = new ValidationConfiguration(array('Default', 'Extra')), unserialize(serialize($config))); + $this->assertEquals($config = new ValidationConfiguration(new GroupSequence(array('Default', 'Then'))), unserialize(serialize($config))); + } +} diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/ValidationMiddlewareTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/ValidationMiddlewareTest.php index 2bd2ba8af2..b2a44b5fa6 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/ValidationMiddlewareTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/ValidationMiddlewareTest.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Messenger\Tests\Middleware; use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Middleware\Configuration\ValidationConfiguration; use Symfony\Component\Messenger\Middleware\ValidationMiddleware; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Validator\ConstraintViolationListInterface; @@ -43,6 +45,30 @@ class ValidationMiddlewareTest extends TestCase $this->assertSame('Hello', $result); } + public function testValidateWithConfigurationAndNextMiddleware() + { + $envelope = Envelope::wrap($message = new DummyMessage('Hey'))->with(new ValidationConfiguration($groups = array('Default', 'Extra'))); + + $validator = $this->createMock(ValidatorInterface::class); + $validator + ->expects($this->once()) + ->method('validate') + ->with($message, null, $groups) + ->willReturn($this->createMock(ConstraintViolationListInterface::class)) + ; + $next = $this->createPartialMock(\stdClass::class, array('__invoke')); + $next + ->expects($this->once()) + ->method('__invoke') + ->with($envelope) + ->willReturn('Hello') + ; + + $result = (new ValidationMiddleware($validator))->handle($envelope, $next); + + $this->assertSame('Hello', $result); + } + /** * @expectedException \Symfony\Component\Messenger\Exception\ValidationFailedException * @expectedExceptionMessage Message of type "Symfony\Component\Messenger\Tests\Fixtures\DummyMessage" failed validation. diff --git a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpExtIntegrationTest.php b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpExtIntegrationTest.php index 5ce9c457e9..c01ffdadb7 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpExtIntegrationTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpExtIntegrationTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\AmqpExt; use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver; use Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender; use Symfony\Component\Messenger\Transport\AmqpExt\Connection; @@ -50,12 +51,12 @@ class AmqpExtIntegrationTest extends TestCase $sender = new AmqpSender($serializer, $connection); $receiver = new AmqpReceiver($serializer, $connection); - $sender->send($firstMessage = new DummyMessage('First')); - $sender->send($secondMessage = new DummyMessage('Second')); + $sender->send($first = Envelope::wrap(new DummyMessage('First'))); + $sender->send($second = Envelope::wrap(new DummyMessage('Second'))); $receivedMessages = 0; - $receiver->receive(function ($message) use ($receiver, &$receivedMessages, $firstMessage, $secondMessage) { - $this->assertEquals(0 == $receivedMessages ? $firstMessage : $secondMessage, $message); + $receiver->receive(function (?Envelope $envelope) use ($receiver, &$receivedMessages, $first, $second) { + $this->assertEquals(0 == $receivedMessages ? $first : $second, $envelope); if (2 === ++$receivedMessages) { $receiver->stop(); @@ -74,7 +75,7 @@ class AmqpExtIntegrationTest extends TestCase $connection->queue()->purge(); $sender = new AmqpSender($serializer, $connection); - $sender->send(new DummyMessage('Hello')); + $sender->send(Envelope::wrap(new DummyMessage('Hello'))); $amqpReadTimeout = 30; $dsn = getenv('MESSENGER_AMQP_DSN').'?read_timeout='.$amqpReadTimeout; @@ -98,7 +99,15 @@ class AmqpExtIntegrationTest extends TestCase $this->assertFalse($process->isRunning()); $this->assertLessThan($amqpReadTimeout, microtime(true) - $signalTime); - $this->assertEquals($expectedOutput."Get message: Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage\nDone.\n", $process->getOutput()); + $this->assertSame($expectedOutput.<<<'TXT' +Get envelope with message: Symfony\Component\Messenger\Tests\Fixtures\DummyMessage +with items: [ + "Symfony\\Component\\Messenger\\Asynchronous\\Transport\\ReceivedMessage" +] +Done. + +TXT + , $process->getOutput()); } /** @@ -114,12 +123,11 @@ class AmqpExtIntegrationTest extends TestCase $connection->setup(); $connection->queue()->purge(); - $sender = new AmqpSender($serializer, $connection); $receiver = new AmqpReceiver($serializer, $connection); $receivedMessages = 0; - $receiver->receive(function ($message) use ($receiver, &$receivedMessages) { - $this->assertNull($message); + $receiver->receive(function (?Envelope $envelope) use ($receiver, &$receivedMessages) { + $this->assertNull($envelope); if (2 === ++$receivedMessages) { $receiver->stop(); diff --git a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpReceiverTest.php b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpReceiverTest.php index 2045764216..4d0b779e3b 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpReceiverTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpReceiverTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\AmqpExt; use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver; use Symfony\Component\Messenger\Transport\AmqpExt\Connection; use Symfony\Component\Messenger\Transport\AmqpExt\Exception\RejectMessageExceptionInterface; @@ -44,8 +45,8 @@ class AmqpReceiverTest extends TestCase $connection->expects($this->once())->method('ack')->with($envelope); $receiver = new AmqpReceiver($serializer, $connection); - $receiver->receive(function ($message) use ($receiver) { - $this->assertEquals(new DummyMessage('Hi'), $message); + $receiver->receive(function (?Envelope $envelope) use ($receiver) { + $this->assertEquals(new DummyMessage('Hi'), $envelope->getMessage()); $receiver->stop(); }); } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpSenderTest.php b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpSenderTest.php index 54859a58ac..8848b022f6 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpSenderTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpSenderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\AmqpExt; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender; use Symfony\Component\Messenger\Transport\AmqpExt\Connection; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; @@ -24,16 +25,16 @@ class AmqpSenderTest extends TestCase { public function testItSendsTheEncodedMessage() { - $message = new DummyMessage('Oy'); + $envelope = Envelope::wrap(new DummyMessage('Oy')); $encoded = array('body' => '...', 'headers' => array('type' => DummyMessage::class)); $encoder = $this->getMockBuilder(EncoderInterface::class)->getMock(); - $encoder->method('encode')->with($message)->willReturnOnConsecutiveCalls($encoded); + $encoder->method('encode')->with($envelope)->willReturnOnConsecutiveCalls($encoded); $connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock(); $connection->expects($this->once())->method('publish')->with($encoded['body'], $encoded['headers']); $sender = new AmqpSender($encoder, $connection); - $sender->send($message); + $sender->send($envelope); } } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/Fixtures/long_receiver.php b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/Fixtures/long_receiver.php index 2115b1a81d..b7231a5d47 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/Fixtures/long_receiver.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/Fixtures/long_receiver.php @@ -12,10 +12,9 @@ if (!file_exists($autoload)) { require_once $autoload; -use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver; -use Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender; -use Symfony\Component\Messenger\Transport\AmqpExt\Connection; use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver; +use Symfony\Component\Messenger\Transport\AmqpExt\Connection; use Symfony\Component\Messenger\Transport\Serialization\Serializer; use Symfony\Component\Messenger\Worker; use Symfony\Component\Serializer as SerializerComponent; @@ -27,13 +26,14 @@ $serializer = new Serializer( ); $connection = Connection::fromDsn(getenv('DSN')); -$sender = new AmqpSender($serializer, $connection); $receiver = new AmqpReceiver($serializer, $connection); $worker = new Worker($receiver, new class() implements MessageBusInterface { - public function dispatch($message) + public function dispatch($envelope) { - echo 'Get message: '.get_class($message)."\n"; + echo 'Get envelope with message: '.get_class($envelope->getMessage())."\n"; + echo sprintf("with items: %s\n", json_encode(array_keys($envelope->all()), JSON_PRETTY_PRINT)); + sleep(30); echo "Done.\n"; } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Enhancers/StopWhenMemoryUsageIsExceededReceiverTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Enhancers/StopWhenMemoryUsageIsExceededReceiverTest.php index f317c6a5bf..a34be3bfc2 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Enhancers/StopWhenMemoryUsageIsExceededReceiverTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Enhancers/StopWhenMemoryUsageIsExceededReceiverTest.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\Enhancers; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Tests\Fixtures\CallbackReceiver; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Transport\Enhancers\StopWhenMemoryUsageIsExceededReceiver; @@ -25,7 +26,7 @@ class StopWhenMemoryUsageIsExceededReceiverTest extends TestCase public function testReceiverStopsWhenMemoryLimitExceeded(int $memoryUsage, int $memoryLimit, bool $shouldStop) { $callable = function ($handler) { - $handler(new DummyMessage('API')); + $handler(Envelope::wrap(new DummyMessage('API'))); }; $decoratedReceiver = $this->getMockBuilder(CallbackReceiver::class) @@ -58,7 +59,7 @@ class StopWhenMemoryUsageIsExceededReceiverTest extends TestCase public function testReceiverLogsMemoryExceededWhenLoggerIsGiven() { $callable = function ($handler) { - $handler(new DummyMessage('API')); + $handler(Envelope::wrap(new DummyMessage('API'))); }; $decoratedReceiver = $this->getMockBuilder(CallbackReceiver::class) diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Enhancers/StopWhenMessageCountIsExceededReceiverTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Enhancers/StopWhenMessageCountIsExceededReceiverTest.php index 7a516744e1..e5c51335b3 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Enhancers/StopWhenMessageCountIsExceededReceiverTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Enhancers/StopWhenMessageCountIsExceededReceiverTest.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\Enhancers; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Tests\Fixtures\CallbackReceiver; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Transport\Enhancers\StopWhenMessageCountIsExceededReceiver; @@ -25,9 +26,9 @@ class StopWhenMessageCountIsExceededReceiverTest extends TestCase public function testReceiverStopsWhenMaximumCountExceeded($max, $shouldStop) { $callable = function ($handler) { - $handler(new DummyMessage('First message')); - $handler(new DummyMessage('Second message')); - $handler(new DummyMessage('Third message')); + $handler(Envelope::wrap(new DummyMessage('First message'))); + $handler(Envelope::wrap(new DummyMessage('Second message'))); + $handler(Envelope::wrap(new DummyMessage('Third message'))); }; $decoratedReceiver = $this->getMockBuilder(CallbackReceiver::class) @@ -78,7 +79,7 @@ class StopWhenMessageCountIsExceededReceiverTest extends TestCase public function testReceiverLogsMaximumCountExceededWhenLoggerIsGiven() { $callable = function ($handler) { - $handler(new DummyMessage('First message')); + $handler(Envelope::wrap(new DummyMessage('First message'))); }; $decoratedReceiver = $this->getMockBuilder(CallbackReceiver::class) diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php index 2e227c0f2f..214b773092 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php @@ -12,8 +12,11 @@ namespace Symfony\Component\Messenger\Tests\Transport\Serialization; use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Middleware\Configuration\ValidationConfiguration; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Transport\Serialization\Serializer; +use Symfony\Component\Messenger\Transport\Serialization\SerializerConfiguration; use Symfony\Component\Serializer as SerializerComponent; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; @@ -26,9 +29,23 @@ class SerializerTest extends TestCase new SerializerComponent\Serializer(array(new ObjectNormalizer()), array('json' => new JsonEncoder())) ); - $message = new DummyMessage('Hello'); + $envelope = Envelope::wrap(new DummyMessage('Hello')); - $this->assertEquals($message, $serializer->decode($serializer->encode($message))); + $this->assertEquals($envelope, $serializer->decode($serializer->encode($envelope))); + } + + public function testEncodedWithConfigurationIsDecodable() + { + $serializer = new Serializer( + new SerializerComponent\Serializer(array(new ObjectNormalizer()), array('json' => new JsonEncoder())) + ); + + $envelope = Envelope::wrap(new DummyMessage('Hello')) + ->with(new SerializerConfiguration(array(ObjectNormalizer::GROUPS => array('foo')))) + ->with(new ValidationConfiguration(array('foo', 'bar'))) + ; + + $this->assertEquals($envelope, $serializer->decode($serializer->encode($envelope))); } public function testEncodedIsHavingTheBodyAndTypeHeader() @@ -37,11 +54,12 @@ class SerializerTest extends TestCase new SerializerComponent\Serializer(array(new ObjectNormalizer()), array('json' => new JsonEncoder())) ); - $encoded = $serializer->encode(new DummyMessage('Hello')); + $encoded = $serializer->encode(Envelope::wrap(new DummyMessage('Hello'))); $this->assertArrayHasKey('body', $encoded); $this->assertArrayHasKey('headers', $encoded); $this->assertArrayHasKey('type', $encoded['headers']); + $this->assertArrayNotHasKey('X-Message-Envelope-Items', $encoded['headers']); $this->assertEquals(DummyMessage::class, $encoded['headers']['type']); } @@ -55,10 +73,31 @@ class SerializerTest extends TestCase $encoder = new Serializer($serializer, 'csv', array('foo' => 'bar')); - $encoded = $encoder->encode($message); + $encoded = $encoder->encode(Envelope::wrap($message)); $decoded = $encoder->decode($encoded); $this->assertSame('Yay', $encoded['body']); - $this->assertSame($message, $decoded); + $this->assertSame($message, $decoded->getMessage()); + } + + public function testEncodedWithSerializationConfiguration() + { + $serializer = new Serializer( + new SerializerComponent\Serializer(array(new ObjectNormalizer()), array('json' => new JsonEncoder())) + ); + + $envelope = Envelope::wrap(new DummyMessage('Hello')) + ->with(new SerializerConfiguration(array(ObjectNormalizer::GROUPS => array('foo')))) + ->with(new ValidationConfiguration(array('foo', 'bar'))) + ; + + $encoded = $serializer->encode($envelope); + + $this->assertArrayHasKey('body', $encoded); + $this->assertArrayHasKey('headers', $encoded); + $this->assertArrayHasKey('type', $encoded['headers']); + $this->assertEquals(DummyMessage::class, $encoded['headers']['type']); + $this->assertArrayHasKey('X-Message-Envelope-Items', $encoded['headers']); + $this->assertSame('a:2:{s:75:"Symfony\Component\Messenger\Transport\Serialization\SerializerConfiguration";C:75:"Symfony\Component\Messenger\Transport\Serialization\SerializerConfiguration":59:{a:1:{s:7:"context";a:1:{s:6:"groups";a:1:{i:0;s:3:"foo";}}}}s:76:"Symfony\Component\Messenger\Middleware\Configuration\ValidationConfiguration";C:76:"Symfony\Component\Messenger\Middleware\Configuration\ValidationConfiguration":82:{a:2:{s:6:"groups";a:2:{i:0;s:3:"foo";i:1;s:3:"bar";}s:17:"is_group_sequence";b:0;}}}', $encoded['headers']['X-Message-Envelope-Items']); } } diff --git a/src/Symfony/Component/Messenger/Tests/WorkerTest.php b/src/Symfony/Component/Messenger/Tests/WorkerTest.php index 7599d8dadc..bdfa6fe188 100644 --- a/src/Symfony/Component/Messenger/Tests/WorkerTest.php +++ b/src/Symfony/Component/Messenger/Tests/WorkerTest.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Messenger\Tests; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Tests\Fixtures\CallbackReceiver; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; @@ -22,29 +23,33 @@ class WorkerTest extends TestCase { public function testWorkerDispatchTheReceivedMessage() { - $receiver = new CallbackReceiver(function ($handler) { - $handler(new DummyMessage('API')); - $handler(new DummyMessage('IPA')); + $apiMessage = new DummyMessage('API'); + $ipaMessage = new DummyMessage('IPA'); + + $receiver = new CallbackReceiver(function ($handler) use ($apiMessage, $ipaMessage) { + $handler(Envelope::wrap($apiMessage)); + $handler(Envelope::wrap($ipaMessage)); }); $bus = $this->getMockBuilder(MessageBusInterface::class)->getMock(); - $bus->expects($this->at(0))->method('dispatch')->with(new ReceivedMessage(new DummyMessage('API'))); - $bus->expects($this->at(1))->method('dispatch')->with(new ReceivedMessage(new DummyMessage('IPA'))); + $bus->expects($this->at(0))->method('dispatch')->with(Envelope::wrap($apiMessage)->with(new ReceivedMessage())); + $bus->expects($this->at(1))->method('dispatch')->with(Envelope::wrap($ipaMessage)->with(new ReceivedMessage())); $worker = new Worker($receiver, $bus); $worker->run(); } - public function testWorkerDoesNotWrapMessagesAlreadyWrappedInReceivedMessages() + public function testWorkerDoesNotWrapMessagesAlreadyWrappedWithReceivedMessage() { - $receiver = new CallbackReceiver(function ($handler) { - $handler(new ReceivedMessage(new DummyMessage('API'))); + $envelop = Envelope::wrap(new DummyMessage('API'))->with(new ReceivedMessage()); + $receiver = new CallbackReceiver(function ($handler) use ($envelop) { + $handler($envelop); }); $bus = $this->getMockBuilder(MessageBusInterface::class)->getMock(); - $bus->expects($this->at(0))->method('dispatch')->with(new ReceivedMessage(new DummyMessage('API'))); + $bus->expects($this->at(0))->method('dispatch')->with($envelop); $worker = new Worker($receiver, $bus); $worker->run(); @@ -54,7 +59,7 @@ class WorkerTest extends TestCase { $receiver = new CallbackReceiver(function ($handler) { try { - $handler(new DummyMessage('Hello')); + $handler(Envelope::wrap(new DummyMessage('Hello'))); $this->assertTrue(false, 'This should not be called because the exception is sent back to the generator.'); } catch (\InvalidArgumentException $e) { diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceiver.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceiver.php index e61ef03895..0e6fbff8ee 100644 --- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceiver.php +++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceiver.php @@ -22,13 +22,13 @@ use Symfony\Component\Messenger\Transport\Serialization\DecoderInterface; */ class AmqpReceiver implements ReceiverInterface { - private $messageDecoder; + private $decoder; private $connection; private $shouldStop; - public function __construct(DecoderInterface $messageDecoder, Connection $connection) + public function __construct(DecoderInterface $decoder, Connection $connection) { - $this->messageDecoder = $messageDecoder; + $this->decoder = $decoder; $this->connection = $connection; } @@ -38,8 +38,8 @@ class AmqpReceiver implements ReceiverInterface public function receive(callable $handler): void { while (!$this->shouldStop) { - $message = $this->connection->get(); - if (null === $message) { + $AMQPEnvelope = $this->connection->get(); + if (null === $AMQPEnvelope) { $handler(null); usleep($this->connection->getConnectionCredentials()['loop_sleep'] ?? 200000); @@ -51,18 +51,18 @@ class AmqpReceiver implements ReceiverInterface } try { - $handler($this->messageDecoder->decode(array( - 'body' => $message->getBody(), - 'headers' => $message->getHeaders(), + $handler($this->decoder->decode(array( + 'body' => $AMQPEnvelope->getBody(), + 'headers' => $AMQPEnvelope->getHeaders(), ))); - $this->connection->ack($message); + $this->connection->ack($AMQPEnvelope); } catch (RejectMessageExceptionInterface $e) { - $this->connection->reject($message); + $this->connection->reject($AMQPEnvelope); throw $e; } catch (\Throwable $e) { - $this->connection->nack($message, AMQP_REQUEUE); + $this->connection->nack($AMQPEnvelope, AMQP_REQUEUE); throw $e; } finally { diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpSender.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpSender.php index 523c35db0f..397c52dec2 100644 --- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpSender.php +++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpSender.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Messenger\Transport\AmqpExt; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Transport\SenderInterface; use Symfony\Component\Messenger\Transport\Serialization\EncoderInterface; @@ -21,21 +22,21 @@ use Symfony\Component\Messenger\Transport\Serialization\EncoderInterface; */ class AmqpSender implements SenderInterface { - private $messageEncoder; + private $encoder; private $connection; - public function __construct(EncoderInterface $messageEncoder, Connection $connection) + public function __construct(EncoderInterface $encoder, Connection $connection) { - $this->messageEncoder = $messageEncoder; + $this->encoder = $encoder; $this->connection = $connection; } /** * {@inheritdoc} */ - public function send($message): void + public function send(Envelope $envelope) { - $encodedMessage = $this->messageEncoder->encode($message); + $encodedMessage = $this->encoder->encode($envelope); $this->connection->publish($encodedMessage['body'], $encodedMessage['headers']); } diff --git a/src/Symfony/Component/Messenger/Transport/Enhancers/StopWhenMemoryUsageIsExceededReceiver.php b/src/Symfony/Component/Messenger/Transport/Enhancers/StopWhenMemoryUsageIsExceededReceiver.php index 4a05afe770..37b1978233 100644 --- a/src/Symfony/Component/Messenger/Transport/Enhancers/StopWhenMemoryUsageIsExceededReceiver.php +++ b/src/Symfony/Component/Messenger/Transport/Enhancers/StopWhenMemoryUsageIsExceededReceiver.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Messenger\Transport\Enhancers; use Psr\Log\LoggerInterface; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Transport\ReceiverInterface; /** @@ -36,8 +37,8 @@ class StopWhenMemoryUsageIsExceededReceiver implements ReceiverInterface public function receive(callable $handler): void { - $this->decoratedReceiver->receive(function ($message) use ($handler) { - $handler($message); + $this->decoratedReceiver->receive(function (?Envelope $envelope) use ($handler) { + $handler($envelope); $memoryResolver = $this->memoryResolver; if ($memoryResolver() > $this->memoryLimit) { diff --git a/src/Symfony/Component/Messenger/Transport/Enhancers/StopWhenMessageCountIsExceededReceiver.php b/src/Symfony/Component/Messenger/Transport/Enhancers/StopWhenMessageCountIsExceededReceiver.php index dc61466bbf..420eb8fa63 100644 --- a/src/Symfony/Component/Messenger/Transport/Enhancers/StopWhenMessageCountIsExceededReceiver.php +++ b/src/Symfony/Component/Messenger/Transport/Enhancers/StopWhenMessageCountIsExceededReceiver.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Messenger\Transport\Enhancers; use Psr\Log\LoggerInterface; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Transport\ReceiverInterface; /** @@ -34,10 +35,10 @@ class StopWhenMessageCountIsExceededReceiver implements ReceiverInterface { $receivedMessages = 0; - $this->decoratedReceiver->receive(function ($message) use ($handler, &$receivedMessages) { - $handler($message); + $this->decoratedReceiver->receive(function (?Envelope $envelope) use ($handler, &$receivedMessages) { + $handler($envelope); - if (null !== $message && ++$receivedMessages >= $this->maximumNumberOfMessages) { + if (null !== $envelope && ++$receivedMessages >= $this->maximumNumberOfMessages) { $this->stop(); if (null !== $this->logger) { $this->logger->info('Receiver stopped due to maximum count of {count} exceeded', array('count' => $this->maximumNumberOfMessages)); diff --git a/src/Symfony/Component/Messenger/Transport/ReceiverInterface.php b/src/Symfony/Component/Messenger/Transport/ReceiverInterface.php index 1c29fbe43a..0f61fe3428 100644 --- a/src/Symfony/Component/Messenger/Transport/ReceiverInterface.php +++ b/src/Symfony/Component/Messenger/Transport/ReceiverInterface.php @@ -21,10 +21,10 @@ interface ReceiverInterface /** * Receive some messages to the given handler. * - * The handler will have, as argument, the received message. Note that this message - * can be `null` if the timeout to receive something has expired. + * The handler will have, as argument, the received {@link \Symfony\Component\Messenger\Envelope} containing the message. + * Note that this envelope can be `null` if the timeout to receive something has expired. */ - public function receive(callable $handler) : void; + public function receive(callable $handler): void; /** * Stop receiving some messages. diff --git a/src/Symfony/Component/Messenger/Transport/SenderInterface.php b/src/Symfony/Component/Messenger/Transport/SenderInterface.php index 93b1bd7cba..ba36b80fe2 100644 --- a/src/Symfony/Component/Messenger/Transport/SenderInterface.php +++ b/src/Symfony/Component/Messenger/Transport/SenderInterface.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Messenger\Transport; +use Symfony\Component\Messenger\Envelope; + /** * @author Samuel Roze * @@ -19,9 +21,9 @@ namespace Symfony\Component\Messenger\Transport; interface SenderInterface { /** - * Sends the given message. + * Sends the given envelope. * - * @param object $message + * @param Envelope $envelope */ - public function send($message): void; + public function send(Envelope $envelope); } diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/DecoderInterface.php b/src/Symfony/Component/Messenger/Transport/Serialization/DecoderInterface.php index a232dfbf10..026a321033 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/DecoderInterface.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/DecoderInterface.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Messenger\Transport\Serialization; +use Symfony\Component\Messenger\Envelope; + /** * @author Samuel Roze * @@ -19,16 +21,16 @@ namespace Symfony\Component\Messenger\Transport\Serialization; interface DecoderInterface { /** - * Decodes the message from an encoded-form. + * Decodes an envelope and its message from an encoded-form. * - * The `$encodedMessage` parameter is a key-value array that - * describes the message, that will be used by the different transports. + * The `$encodedEnvelope` parameter is a key-value array that + * describes the envelope and its content, that will be used by the different transports. * * The most common keys are: * - `body` (string) - the message body * - `headers` (string) - a key/value pair of headers * - * @return object + * @return Envelope */ - public function decode(array $encodedMessage); + public function decode(array $encodedEnvelope): Envelope; } diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/EncoderInterface.php b/src/Symfony/Component/Messenger/Transport/Serialization/EncoderInterface.php index 658b9e5b5e..1dce6fdae3 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/EncoderInterface.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/EncoderInterface.php @@ -10,6 +10,7 @@ */ namespace Symfony\Component\Messenger\Transport\Serialization; +use Symfony\Component\Messenger\Envelope; /** * @author Samuel Roze @@ -19,14 +20,14 @@ namespace Symfony\Component\Messenger\Transport\Serialization; interface EncoderInterface { /** - * Encodes a message to a common format understandable by transports. The encoded array should only - * contain scalar and arrays. + * Encodes an envelope content (message & items) to a common format understandable by transports. + * 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 The object that is put on the MessageBus by the user + * @param Envelope $envelope The envelop containing the message put on the MessageBus by the user */ - public function encode($message): array; + public function encode(Envelope $envelope): array; } diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php index 65c2ac55e8..cfb30090e5 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Messenger\Transport\Serialization; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Serializer\SerializerInterface; /** @@ -32,27 +33,48 @@ class Serializer implements DecoderInterface, EncoderInterface /** * {@inheritdoc} */ - public function decode(array $encodedMessage) + public function decode(array $encodedEnvelope): Envelope { - if (empty($encodedMessage['body']) || empty($encodedMessage['headers'])) { - throw new \InvalidArgumentException('Encoded message should have at least a `body` and some `headers`.'); + if (empty($encodedEnvelope['body']) || empty($encodedEnvelope['headers'])) { + throw new \InvalidArgumentException('Encoded envelope should have at least a `body` and some `headers`.'); } - if (empty($encodedMessage['headers']['type'])) { - throw new \InvalidArgumentException('Encoded message does not have a `type` header.'); + if (empty($encodedEnvelope['headers']['type'])) { + throw new \InvalidArgumentException('Encoded envelope does not have a `type` header.'); } - return $this->serializer->deserialize($encodedMessage['body'], $encodedMessage['headers']['type'], $this->format, $this->context); + $envelopeItems = isset($encodedEnvelope['headers']['X-Message-Envelope-Items']) ? unserialize($encodedEnvelope['headers']['X-Message-Envelope-Items']) : array(); + + $context = $this->context; + /** @var SerializerConfiguration|null $serializerConfig */ + if ($serializerConfig = $envelopeItems[SerializerConfiguration::class] ?? null) { + $context = $serializerConfig->getContext() + $context; + } + + $message = $this->serializer->deserialize($encodedEnvelope['body'], $encodedEnvelope['headers']['type'], $this->format, $context); + + return new Envelope($message, $envelopeItems); } /** * {@inheritdoc} */ - public function encode($message): array + public function encode(Envelope $envelope): array { + $context = $this->context; + /** @var SerializerConfiguration|null $serializerConfig */ + if ($serializerConfig = $envelope->get(SerializerConfiguration::class)) { + $context = $serializerConfig->getContext() + $context; + } + + $headers = array('type' => \get_class($envelope->getMessage())); + if ($configurations = $envelope->all()) { + $headers['X-Message-Envelope-Items'] = serialize($configurations); + } + return array( - 'body' => $this->serializer->serialize($message, $this->format, $this->context), - 'headers' => array('type' => \get_class($message)), + 'body' => $this->serializer->serialize($envelope->getMessage(), $this->format, $context), + 'headers' => $headers, ); } } diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/SerializerConfiguration.php b/src/Symfony/Component/Messenger/Transport/Serialization/SerializerConfiguration.php new file mode 100644 index 0000000000..478e197080 --- /dev/null +++ b/src/Symfony/Component/Messenger/Transport/Serialization/SerializerConfiguration.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Transport\Serialization; + +use Symfony\Component\Messenger\EnvelopeItemInterface; + +/** + * @author Maxime Steinhausser + * + * @experimental in 4.1 + */ +final class SerializerConfiguration implements EnvelopeItemInterface +{ + private $context; + + public function __construct(array $context) + { + $this->context = $context; + } + + public function getContext(): array + { + return $this->context; + } + + public function serialize() + { + return serialize(array('context' => $this->context)); + } + + public function unserialize($serialized) + { + list('context' => $context) = unserialize($serialized, array('allowed_classes' => false)); + + $this->__construct($context); + } +} diff --git a/src/Symfony/Component/Messenger/Worker.php b/src/Symfony/Component/Messenger/Worker.php index 2033f3a770..918b61a52a 100644 --- a/src/Symfony/Component/Messenger/Worker.php +++ b/src/Symfony/Component/Messenger/Worker.php @@ -41,16 +41,12 @@ class Worker }); } - $this->receiver->receive(function ($message) { - if (null === $message) { + $this->receiver->receive(function (?Envelope $envelope) { + if (null === $envelope) { return; } - if (!$message instanceof ReceivedMessage) { - $message = new ReceivedMessage($message); - } - - $this->bus->dispatch($message); + $this->bus->dispatch($envelope->with(new ReceivedMessage())); }); } } From 599f32c0852242d782cf491d7d78ad586e8af5f3 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Mon, 7 May 2018 16:54:04 +0100 Subject: [PATCH 03/18] Ensure the envelope is passed back and can be altered Ensure that the middlewares can also update the message within the envelope --- src/Symfony/Component/Messenger/Envelope.php | 9 ++ .../Component/Messenger/MessageBus.php | 15 ++- .../Messenger/Tests/MessageBusTest.php | 117 ++++++++++++++++++ 3 files changed, 136 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Messenger/Envelope.php b/src/Symfony/Component/Messenger/Envelope.php index 14778593c4..4d5f7a02a9 100644 --- a/src/Symfony/Component/Messenger/Envelope.php +++ b/src/Symfony/Component/Messenger/Envelope.php @@ -57,6 +57,15 @@ final class Envelope return $cloned; } + public function withMessage($message): self + { + $cloned = clone $this; + + $cloned->message = $message; + + return $cloned; + } + public function get(string $itemFqcn): ?EnvelopeItemInterface { return $this->items[$itemFqcn] ?? null; diff --git a/src/Symfony/Component/Messenger/MessageBus.php b/src/Symfony/Component/Messenger/MessageBus.php index c467a6f79d..89fa8e04fa 100644 --- a/src/Symfony/Component/Messenger/MessageBus.php +++ b/src/Symfony/Component/Messenger/MessageBus.php @@ -44,10 +44,10 @@ class MessageBus implements MessageBusInterface throw new InvalidArgumentException(sprintf('Invalid type for message argument. Expected object, but got "%s".', \gettype($message))); } - return \call_user_func($this->callableForNextMiddleware(0), $message); + return \call_user_func($this->callableForNextMiddleware(0, Envelope::wrap($message)), $message); } - private function callableForNextMiddleware(int $index): callable + private function callableForNextMiddleware(int $index, Envelope $currentEnvelope): callable { if (null === $this->indexedMiddlewareHandlers) { $this->indexedMiddlewareHandlers = \is_array($this->middlewareHandlers) ? array_values($this->middlewareHandlers) : iterator_to_array($this->middlewareHandlers, false); @@ -59,14 +59,19 @@ class MessageBus implements MessageBusInterface $middleware = $this->indexedMiddlewareHandlers[$index]; - return function ($message) use ($middleware, $index) { - $message = Envelope::wrap($message); + return function ($message) use ($middleware, $index, $currentEnvelope) { + if ($message instanceof Envelope) { + $currentEnvelope = $message; + } else { + $message = $currentEnvelope->withMessage($message); + } + if (!$middleware instanceof EnvelopeAwareInterface) { // Do not provide the envelope if the middleware cannot read it: $message = $message->getMessage(); } - return $middleware->handle($message, $this->callableForNextMiddleware($index + 1)); + return $middleware->handle($message, $this->callableForNextMiddleware($index + 1, $currentEnvelope)); }; } } diff --git a/src/Symfony/Component/Messenger/Tests/MessageBusTest.php b/src/Symfony/Component/Messenger/Tests/MessageBusTest.php index 40eecf9c54..ba40eb3f9a 100644 --- a/src/Symfony/Component/Messenger/Tests/MessageBusTest.php +++ b/src/Symfony/Component/Messenger/Tests/MessageBusTest.php @@ -12,6 +12,10 @@ namespace Symfony\Component\Messenger\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\EnvelopeAwareInterface; +use Symfony\Component\Messenger\EnvelopeItemInterface; use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; @@ -61,4 +65,117 @@ class MessageBusTest extends TestCase $this->assertEquals($responseFromDepthMiddleware, $bus->dispatch($message)); } + + public function testItKeepsTheEnvelopeEvenThroughAMiddlewareThatIsNotEnvelopeAware() + { + $message = new DummyMessage('Hello'); + $envelope = new Envelope($message, array(new ReceivedMessage())); + + $firstMiddleware = $this->getMockBuilder(MiddlewareInterface::class)->getMock(); + $firstMiddleware->expects($this->once()) + ->method('handle') + ->with($message, $this->anything()) + ->will($this->returnCallback(function ($message, $next) { + return $next($message); + })); + + $secondMiddleware = $this->getMockBuilder(array(MiddlewareInterface::class, EnvelopeAwareInterface::class))->getMock(); + $secondMiddleware->expects($this->once()) + ->method('handle') + ->with($envelope, $this->anything()) + ; + + $bus = new MessageBus(array( + $firstMiddleware, + $secondMiddleware, + )); + + $bus->dispatch($envelope); + } + + public function testThatAMiddlewareCanAddSomeItemsToTheEnvelope() + { + $message = new DummyMessage('Hello'); + $envelope = new Envelope($message, array(new ReceivedMessage())); + $envelopeWithAnotherItem = $envelope->with(new AnEnvelopeItem()); + + $firstMiddleware = $this->getMockBuilder(array(MiddlewareInterface::class, EnvelopeAwareInterface::class))->getMock(); + $firstMiddleware->expects($this->once()) + ->method('handle') + ->with($envelope, $this->anything()) + ->will($this->returnCallback(function ($message, $next) { + return $next($message->with(new AnEnvelopeItem())); + })); + + $secondMiddleware = $this->getMockBuilder(MiddlewareInterface::class)->getMock(); + $secondMiddleware->expects($this->once()) + ->method('handle') + ->with($message, $this->anything()) + ->will($this->returnCallback(function ($message, $next) { + return $next($message); + })); + + $thirdMiddleware = $this->getMockBuilder(array(MiddlewareInterface::class, EnvelopeAwareInterface::class))->getMock(); + $thirdMiddleware->expects($this->once()) + ->method('handle') + ->with($envelopeWithAnotherItem, $this->anything()) + ; + + $bus = new MessageBus(array( + $firstMiddleware, + $secondMiddleware, + $thirdMiddleware, + )); + + $bus->dispatch($envelope); + } + + public function testThatAMiddlewareCanUpdateTheMessageWhileKeepingTheEnvelopeItems() + { + $message = new DummyMessage('Hello'); + $envelope = new Envelope($message, $items = array(new ReceivedMessage())); + + $changedMessage = new DummyMessage('Changed'); + $expectedEnvelope = new Envelope($changedMessage, $items); + + $firstMiddleware = $this->getMockBuilder(MiddlewareInterface::class)->getMock(); + $firstMiddleware->expects($this->once()) + ->method('handle') + ->with($message, $this->anything()) + ->will($this->returnCallback(function ($message, $next) use ($changedMessage) { + return $next($changedMessage); + })); + + $secondMiddleware = $this->getMockBuilder(array(MiddlewareInterface::class, EnvelopeAwareInterface::class))->getMock(); + $secondMiddleware->expects($this->once()) + ->method('handle') + ->with($expectedEnvelope, $this->anything()) + ; + + $bus = new MessageBus(array( + $firstMiddleware, + $secondMiddleware, + )); + + $bus->dispatch($envelope); + } +} + +class AnEnvelopeItem implements EnvelopeItemInterface +{ + /** + * {@inheritdoc} + */ + public function serialize() + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + return new self(); + } } From 21e49d21d88133d9998398091695c3cca28d220a Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Mon, 7 May 2018 20:03:43 +0200 Subject: [PATCH 04/18] [Messenger] Fix TraceableBus with envelope --- .../Messenger/Tests/TraceableMessageBusTest.php | 13 +++++++++++++ .../Component/Messenger/TraceableMessageBus.php | 6 ++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php b/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php index 8e4895d3ba..4da1e5a630 100644 --- a/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php +++ b/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Messenger\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\TraceableMessageBus; @@ -30,6 +31,18 @@ class TraceableMessageBusTest extends TestCase $this->assertSame(array(array('message' => $message, 'result' => $result)), $traceableBus->getDispatchedMessages()); } + public function testItTracesResultWithEnvelope() + { + $envelope = Envelope::wrap($message = new DummyMessage('Hello')); + + $bus = $this->getMockBuilder(MessageBusInterface::class)->getMock(); + $bus->expects($this->once())->method('dispatch')->with($envelope)->willReturn($result = array('foo' => 'bar')); + + $traceableBus = new TraceableMessageBus($bus); + $this->assertSame($result, $traceableBus->dispatch($envelope)); + $this->assertSame(array(array('message' => $message, 'result' => $result)), $traceableBus->getDispatchedMessages()); + } + public function testItTracesExceptions() { $message = new DummyMessage('Hello'); diff --git a/src/Symfony/Component/Messenger/TraceableMessageBus.php b/src/Symfony/Component/Messenger/TraceableMessageBus.php index ecf0c5658c..9620e0189d 100644 --- a/src/Symfony/Component/Messenger/TraceableMessageBus.php +++ b/src/Symfony/Component/Messenger/TraceableMessageBus.php @@ -29,18 +29,20 @@ class TraceableMessageBus implements MessageBusInterface */ public function dispatch($message) { + $messageToTrace = $message instanceof Envelope ? $message->getMessage() : $message; + try { $result = $this->decoratedBus->dispatch($message); $this->dispatchedMessages[] = array( - 'message' => $message, + 'message' => $messageToTrace, 'result' => $result, ); return $result; } catch (\Throwable $e) { $this->dispatchedMessages[] = array( - 'message' => $message, + 'message' => $messageToTrace, 'exception' => $e, ); From ffa5d1ca9441ef9b45a4b33039990680b1a074f5 Mon Sep 17 00:00:00 2001 From: Xavier HAUSHERR Date: Wed, 9 May 2018 12:00:53 +0200 Subject: [PATCH 05/18] =?UTF-8?q?[Workflow]=C2=A0add=20is=20deprecated=20s?= =?UTF-8?q?ince=20Symfony=204.1.=20Use=20addWorkflow()=20instead?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DependencyInjection/FrameworkExtension.php | 4 ++-- src/Symfony/Bundle/FrameworkBundle/composer.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index ff6351e707..bbdf232fef 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -572,10 +572,10 @@ class FrameworkExtension extends Extension foreach ($workflow['supports'] as $supportedClassName) { $strategyDefinition = new Definition(Workflow\SupportStrategy\InstanceOfSupportStrategy::class, array($supportedClassName)); $strategyDefinition->setPublic(false); - $registryDefinition->addMethodCall('add', array(new Reference($workflowId), $strategyDefinition)); + $registryDefinition->addMethodCall('addWorkflow', array(new Reference($workflowId), $strategyDefinition)); } } elseif (isset($workflow['support_strategy'])) { - $registryDefinition->addMethodCall('add', array(new Reference($workflowId), new Reference($workflow['support_strategy']))); + $registryDefinition->addMethodCall('addWorkflow', array(new Reference($workflowId), new Reference($workflow['support_strategy']))); } // Enable the AuditTrail diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 8c1c32194f..08e2f4ead0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -51,7 +51,7 @@ "symfony/templating": "~3.4|~4.0", "symfony/validator": "^4.1", "symfony/var-dumper": "~3.4|~4.0", - "symfony/workflow": "~3.4|~4.0", + "symfony/workflow": "^4.1", "symfony/yaml": "~3.4|~4.0", "symfony/property-info": "~3.4|~4.0", "symfony/lock": "~3.4|~4.0", @@ -72,7 +72,7 @@ "symfony/stopwatch": "<3.4", "symfony/translation": "<3.4", "symfony/validator": "<4.1", - "symfony/workflow": "<3.4" + "symfony/workflow": "<4.1" }, "suggest": { "ext-apcu": "For best performance of the system caches", From 301ce5f8391a6b5dc4fd1b365cfa289d8e0a060d Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Mon, 7 May 2018 12:16:37 -0400 Subject: [PATCH 06/18] [Messenger] Make sure Sender and Receiver locators have valid services --- .../Command/ConsumeMessagesCommand.php | 12 +++++------ .../DependencyInjection/MessengerPass.php | 12 +++++++++++ .../DependencyInjection/MessengerPassTest.php | 21 +++++++++++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php index 72dc2aa907..62892c59f0 100644 --- a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php +++ b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Command; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -22,7 +23,6 @@ use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Transport\Enhancers\StopWhenMemoryUsageIsExceededReceiver; use Symfony\Component\Messenger\Transport\Enhancers\StopWhenMessageCountIsExceededReceiver; use Symfony\Component\Messenger\Transport\Enhancers\StopWhenTimeLimitIsReachedReceiver; -use Symfony\Component\Messenger\Transport\ReceiverInterface; use Symfony\Component\Messenger\Worker; /** @@ -89,12 +89,10 @@ EOF protected function execute(InputInterface $input, OutputInterface $output): void { if (!$this->receiverLocator->has($receiverName = $input->getArgument('receiver'))) { - throw new \RuntimeException(sprintf('Receiver "%s" does not exist.', $receiverName)); + throw new RuntimeException(sprintf('Receiver "%s" does not exist.', $receiverName)); } - if (!($receiver = $this->receiverLocator->get($receiverName)) instanceof ReceiverInterface) { - throw new \RuntimeException(sprintf('Receiver "%s" is not a valid message consumer. It must implement the "%s" interface.', $receiverName, ReceiverInterface::class)); - } + $receiver = $this->receiverLocator->get($receiverName); if ($limit = $input->getOption('limit')) { $receiver = new StopWhenMessageCountIsExceededReceiver($receiver, $limit, $this->logger); @@ -117,9 +115,9 @@ EOF $memoryLimit = strtolower($memoryLimit); $max = strtolower(ltrim($memoryLimit, '+')); if (0 === strpos($max, '0x')) { - $max = intval($max, 16); + $max = \intval($max, 16); } elseif (0 === strpos($max, '0')) { - $max = intval($max, 8); + $max = \intval($max, 8); } else { $max = (int) $max; } diff --git a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php index 1947728143..9789ec038c 100644 --- a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php +++ b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php @@ -23,6 +23,8 @@ use Symfony\Component\Messenger\Handler\ChainHandler; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; use Symfony\Component\Messenger\Handler\MessageSubscriberInterface; use Symfony\Component\Messenger\TraceableMessageBus; +use Symfony\Component\Messenger\Transport\ReceiverInterface; +use Symfony\Component\Messenger\Transport\SenderInterface; /** * @author Samuel Roze @@ -167,6 +169,11 @@ class MessengerPass implements CompilerPassInterface $taggedReceivers = $container->findTaggedServiceIds($this->receiverTag); foreach ($taggedReceivers as $id => $tags) { + $receiverClass = $container->findDefinition($id)->getClass(); + if (!is_subclass_of($receiverClass, ReceiverInterface::class)) { + throw new RuntimeException(sprintf('Invalid receiver "%s": class "%s" must implement interface "%s".', $id, $receiverClass, ReceiverInterface::class)); + } + $receiverMapping[$id] = new Reference($id); foreach ($tags as $tag) { @@ -187,6 +194,11 @@ class MessengerPass implements CompilerPassInterface { $senderLocatorMapping = array(); foreach ($container->findTaggedServiceIds($this->senderTag) as $id => $tags) { + $senderClass = $container->findDefinition($id)->getClass(); + if (!is_subclass_of($senderClass, SenderInterface::class)) { + throw new RuntimeException(sprintf('Invalid sender "%s": class "%s" must implement interface "%s".', $id, $senderClass, SenderInterface::class)); + } + $senderLocatorMapping[$id] = new Reference($id); foreach ($tags as $tag) { diff --git a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php index 37c9aefed4..46983486d7 100644 --- a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php +++ b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php @@ -172,6 +172,19 @@ class MessengerPassTest extends TestCase $this->assertEquals(array(AmqpSender::class => new Reference(AmqpSender::class)), $container->getDefinition('messenger.sender_locator')->getArgument(0)); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage Invalid sender "app.messenger.sender": class "Symfony\Component\Messenger\Tests\DependencyInjection\InvalidSender" must implement interface "Symfony\Component\Messenger\Transport\SenderInterface". + */ + public function testItDoesNotRegisterInvalidSender() + { + $container = $this->getContainerBuilder(); + $container->register('app.messenger.sender', InvalidSender::class) + ->addTag('messenger.sender'); + + (new MessengerPass())->process($container); + } + /** * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException * @expectedExceptionMessage Invalid handler service "Symfony\Component\Messenger\Tests\DependencyInjection\UndefinedMessageHandler": message class "Symfony\Component\Messenger\Tests\DependencyInjection\UndefinedMessage" used as argument type in method "Symfony\Component\Messenger\Tests\DependencyInjection\UndefinedMessageHandler::__invoke()" does not exist. @@ -366,6 +379,14 @@ class DummyReceiver implements ReceiverInterface } } +class InvalidReceiver +{ +} + +class InvalidSender +{ +} + class UndefinedMessageHandler { public function __invoke(UndefinedMessage $message) From 41e25abf8cdf2cf7e91a1b1828cffc3aeca9c96e Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Mon, 7 May 2018 13:31:27 -0400 Subject: [PATCH 07/18] Fixed return senders based on the message parents/interfaces --- .../Asynchronous/Routing/SenderLocator.php | 22 ++++++-- .../Routing/SenderLocatorTest.php | 52 +++++++++++++++++-- .../Messenger/Tests/Fixtures/DummyMessage.php | 2 +- .../Tests/Fixtures/DummyMessageInterface.php | 7 +++ 4 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 src/Symfony/Component/Messenger/Tests/Fixtures/DummyMessageInterface.php diff --git a/src/Symfony/Component/Messenger/Asynchronous/Routing/SenderLocator.php b/src/Symfony/Component/Messenger/Asynchronous/Routing/SenderLocator.php index 9b62457626..506fe234c6 100644 --- a/src/Symfony/Component/Messenger/Asynchronous/Routing/SenderLocator.php +++ b/src/Symfony/Component/Messenger/Asynchronous/Routing/SenderLocator.php @@ -32,13 +32,29 @@ class SenderLocator implements SenderLocatorInterface */ public function getSendersForMessage($message): array { - $senderIds = $this->messageToSenderIdsMapping[\get_class($message)] ?? $this->messageToSenderIdsMapping['*'] ?? array(); - $senders = array(); - foreach ($senderIds as $senderId) { + foreach ($this->getSenderIds($message) as $senderId) { $senders[] = $this->senderServiceLocator->get($senderId); } return $senders; } + + private function getSenderIds($message): array + { + if (isset($this->messageToSenderIdsMapping[\get_class($message)])) { + return $this->messageToSenderIdsMapping[\get_class($message)]; + } + if ($messageToSenderIdsMapping = array_intersect_key($this->messageToSenderIdsMapping, class_parents($message))) { + return current($messageToSenderIdsMapping); + } + if ($messageToSenderIdsMapping = array_intersect_key($this->messageToSenderIdsMapping, class_implements($message))) { + return current($messageToSenderIdsMapping); + } + if (isset($this->messageToSenderIdsMapping['*'])) { + return $this->messageToSenderIdsMapping['*']; + } + + return array(); + } } diff --git a/src/Symfony/Component/Messenger/Tests/Asynchronous/Routing/SenderLocatorTest.php b/src/Symfony/Component/Messenger/Tests/Asynchronous/Routing/SenderLocatorTest.php index caf9525264..6278be85c5 100644 --- a/src/Symfony/Component/Messenger/Tests/Asynchronous/Routing/SenderLocatorTest.php +++ b/src/Symfony/Component/Messenger/Tests/Asynchronous/Routing/SenderLocatorTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Messenger\Asynchronous\Routing\SenderLocator; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; +use Symfony\Component\Messenger\Tests\Fixtures\DummyMessageInterface; use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage; use Symfony\Component\Messenger\Transport\SenderInterface; @@ -32,8 +33,47 @@ class SenderLocatorTest extends TestCase ), )); - $this->assertEquals(array($sender), $locator->getSendersForMessage(new DummyMessage('Hello'))); - $this->assertEquals(array(), $locator->getSendersForMessage(new SecondMessage())); + $this->assertSame(array($sender), $locator->getSendersForMessage(new DummyMessage('Hello'))); + $this->assertSame(array(), $locator->getSendersForMessage(new SecondMessage())); + } + + public function testItReturnsTheSenderBasedOnTheMessageParentClass() + { + $container = new Container(); + + $sender = $this->getMockBuilder(SenderInterface::class)->getMock(); + $container->set('my_amqp_sender', $sender); + + $apiSender = $this->getMockBuilder(SenderInterface::class)->getMock(); + $container->set('my_api_sender', $apiSender); + + $locator = new SenderLocator($container, array( + DummyMessageInterface::class => array( + 'my_api_sender', + ), + DummyMessage::class => array( + 'my_amqp_sender', + ), + )); + $this->assertSame(array($sender), $locator->getSendersForMessage(new ChildDummyMessage('Hello'))); + $this->assertSame(array(), $locator->getSendersForMessage(new SecondMessage())); + } + + public function testItReturnsTheSenderBasedOnTheMessageInterface() + { + $container = new Container(); + + $sender = $this->getMockBuilder(SenderInterface::class)->getMock(); + $container->set('my_amqp_sender', $sender); + + $locator = new SenderLocator($container, array( + DummyMessageInterface::class => array( + 'my_amqp_sender', + ), + )); + + $this->assertSame(array($sender), $locator->getSendersForMessage(new DummyMessage('Hello'))); + $this->assertSame(array(), $locator->getSendersForMessage(new SecondMessage())); } public function testItSupportsAWildcardInsteadOfTheMessageClass() @@ -55,7 +95,11 @@ class SenderLocatorTest extends TestCase ), )); - $this->assertEquals(array($sender), $locator->getSendersForMessage(new DummyMessage('Hello'))); - $this->assertEquals(array($apiSender), $locator->getSendersForMessage(new SecondMessage())); + $this->assertSame(array($sender), $locator->getSendersForMessage(new DummyMessage('Hello'))); + $this->assertSame(array($apiSender), $locator->getSendersForMessage(new SecondMessage())); } } + +class ChildDummyMessage extends DummyMessage +{ +} diff --git a/src/Symfony/Component/Messenger/Tests/Fixtures/DummyMessage.php b/src/Symfony/Component/Messenger/Tests/Fixtures/DummyMessage.php index fb02ca7b86..2a9c70b1c5 100644 --- a/src/Symfony/Component/Messenger/Tests/Fixtures/DummyMessage.php +++ b/src/Symfony/Component/Messenger/Tests/Fixtures/DummyMessage.php @@ -2,7 +2,7 @@ namespace Symfony\Component\Messenger\Tests\Fixtures; -class DummyMessage +class DummyMessage implements DummyMessageInterface { private $message; diff --git a/src/Symfony/Component/Messenger/Tests/Fixtures/DummyMessageInterface.php b/src/Symfony/Component/Messenger/Tests/Fixtures/DummyMessageInterface.php new file mode 100644 index 0000000000..557b958ddc --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Fixtures/DummyMessageInterface.php @@ -0,0 +1,7 @@ + Date: Thu, 10 May 2018 14:27:02 +0200 Subject: [PATCH 08/18] [Messenger] Fix new AMQP Transport test with Envelope --- .../Tests/Transport/AmqpExt/AmqpTransportTest.php | 7 ++++--- .../Messenger/Transport/AmqpExt/AmqpTransport.php | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportTest.php b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportTest.php index 26f3261577..3d003a270d 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/AmqpTransportTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\AmqpExt; use PHPUnit\Framework\TestCase; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransport; use Symfony\Component\Messenger\Transport\AmqpExt\Connection; @@ -45,11 +46,11 @@ class AmqpTransportTest extends TestCase $amqpEnvelope->method('getBody')->willReturn('body'); $amqpEnvelope->method('getHeaders')->willReturn(array('my' => 'header')); - $decoder->method('decode')->with(array('body' => 'body', 'headers' => array('my' => 'header')))->willReturn($decodedMessage); + $decoder->method('decode')->with(array('body' => 'body', 'headers' => array('my' => 'header')))->willReturn(Envelope::wrap($decodedMessage)); $connection->method('get')->willReturn($amqpEnvelope); - $transport->receive(function ($message) use ($transport, $decodedMessage) { - $this->assertSame($decodedMessage, $message); + $transport->receive(function (Envelope $envelope) use ($transport, $decodedMessage) { + $this->assertSame($decodedMessage, $envelope->getMessage()); $transport->stop(); }); diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php index 583ec03bae..3edefd0ab1 100644 --- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php +++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransport.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Messenger\Transport\AmqpExt; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Transport\Serialization\DecoderInterface; use Symfony\Component\Messenger\Transport\Serialization\EncoderInterface; use Symfony\Component\Messenger\Transport\TransportInterface; @@ -52,9 +53,9 @@ class AmqpTransport implements TransportInterface /** * {@inheritdoc} */ - public function send($message): void + public function send(Envelope $envelope): void { - ($this->sender ?? $this->getSender())->send($message); + ($this->sender ?? $this->getSender())->send($envelope); } private function getReceiver() From 6295879a3080482bf996beca9ea080a2e6c2274a Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Thu, 10 May 2018 13:03:14 -0400 Subject: [PATCH 09/18] Autoconfiguring TransportFactoryInterface classes --- .../FrameworkBundle/DependencyInjection/FrameworkExtension.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index c08ad7b390..9f2075057a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -65,6 +65,7 @@ use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Transport\ReceiverInterface; use Symfony\Component\Messenger\Transport\SenderInterface; +use Symfony\Component\Messenger\Transport\TransportFactoryInterface; use Symfony\Component\Messenger\Transport\TransportInterface; use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; @@ -349,6 +350,8 @@ class FrameworkExtension extends Extension ->addTag('messenger.sender'); $container->registerForAutoconfiguration(MessageHandlerInterface::class) ->addTag('messenger.message_handler'); + $container->registerForAutoconfiguration(TransportFactoryInterface::class) + ->addTag('messenger.transport_factory'); if (!$container->getParameter('kernel.debug')) { // remove tagged iterator argument for resource checkers From 1ef27a7e6a10a24a023a4f136d60d6ebda35e5eb Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Thu, 10 May 2018 17:13:07 -0400 Subject: [PATCH 10/18] Rename tag attribute "name" by "alias" --- .../DependencyInjection/FrameworkExtension.php | 4 ++-- .../DependencyInjection/FrameworkExtensionTest.php | 4 ++-- .../Messenger/DependencyInjection/MessengerPass.php | 12 ++++++------ .../Tests/DependencyInjection/MessengerPassTest.php | 10 +++++----- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index c08ad7b390..af449c1a54 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1511,8 +1511,8 @@ class FrameworkExtension extends Extension $transportDefinition = (new Definition(TransportInterface::class)) ->setFactory(array(new Reference('messenger.transport_factory'), 'createTransport')) ->setArguments(array($transport['dsn'], $transport['options'])) - ->addTag('messenger.receiver', array('name' => $name)) - ->addTag('messenger.sender', array('name' => $name)) + ->addTag('messenger.receiver', array('alias' => $name)) + ->addTag('messenger.sender', array('alias' => $name)) ; $container->setDefinition('messenger.transport.'.$name, $transportDefinition); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 8d94d32aa8..cbed3c89f8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -539,8 +539,8 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertTrue($container->hasDefinition('messenger.transport.default')); $this->assertTrue($container->getDefinition('messenger.transport.default')->hasTag('messenger.receiver')); $this->assertTrue($container->getDefinition('messenger.transport.default')->hasTag('messenger.sender')); - $this->assertEquals(array(array('name' => 'default')), $container->getDefinition('messenger.transport.default')->getTag('messenger.receiver')); - $this->assertEquals(array(array('name' => 'default')), $container->getDefinition('messenger.transport.default')->getTag('messenger.sender')); + $this->assertEquals(array(array('alias' => 'default')), $container->getDefinition('messenger.transport.default')->getTag('messenger.receiver')); + $this->assertEquals(array(array('alias' => 'default')), $container->getDefinition('messenger.transport.default')->getTag('messenger.sender')); $this->assertTrue($container->hasDefinition('messenger.transport.customised')); $transportFactory = $container->getDefinition('messenger.transport.customised')->getFactory(); diff --git a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php index 9789ec038c..81f8461706 100644 --- a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php +++ b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php @@ -63,7 +63,7 @@ class MessengerPass implements CompilerPassInterface } if ($container->hasDefinition('messenger.data_collector')) { - $this->registerBusToCollector($container, $busId, $tags[0]); + $this->registerBusToCollector($container, $busId); } } @@ -177,8 +177,8 @@ class MessengerPass implements CompilerPassInterface $receiverMapping[$id] = new Reference($id); foreach ($tags as $tag) { - if (isset($tag['name'])) { - $receiverMapping[$tag['name']] = $receiverMapping[$id]; + if (isset($tag['alias'])) { + $receiverMapping[$tag['alias']] = $receiverMapping[$id]; } } } @@ -202,8 +202,8 @@ class MessengerPass implements CompilerPassInterface $senderLocatorMapping[$id] = new Reference($id); foreach ($tags as $tag) { - if (isset($tag['name'])) { - $senderLocatorMapping[$tag['name']] = $senderLocatorMapping[$id]; + if (isset($tag['alias'])) { + $senderLocatorMapping[$tag['alias']] = $senderLocatorMapping[$id]; } } } @@ -211,7 +211,7 @@ class MessengerPass implements CompilerPassInterface $container->getDefinition('messenger.sender_locator')->replaceArgument(0, $senderLocatorMapping); } - private function registerBusToCollector(ContainerBuilder $container, string $busId, array $tag) + private function registerBusToCollector(ContainerBuilder $container, string $busId) { $container->setDefinition( $tracedBusId = 'debug.traced.'.$busId, diff --git a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php index eeb5d585f5..d49a38421b 100644 --- a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php +++ b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php @@ -101,7 +101,7 @@ class MessengerPassTest extends TestCase public function testItRegistersReceivers() { $container = $this->getContainerBuilder(); - $container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver', array('name' => 'amqp')); + $container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver', array('alias' => 'amqp')); (new MessengerPass())->process($container); @@ -128,7 +128,7 @@ class MessengerPassTest extends TestCase null, )); - $container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver', array('name' => 'amqp')); + $container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver', array('alias' => 'amqp')); (new MessengerPass())->process($container); @@ -145,8 +145,8 @@ class MessengerPassTest extends TestCase null, )); - $container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver', array('name' => 'amqp')); - $container->register(DummyReceiver::class, DummyReceiver::class)->addTag('messenger.receiver', array('name' => 'dummy')); + $container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver', array('alias' => 'amqp')); + $container->register(DummyReceiver::class, DummyReceiver::class)->addTag('messenger.receiver', array('alias' => 'dummy')); (new MessengerPass())->process($container); @@ -156,7 +156,7 @@ class MessengerPassTest extends TestCase public function testItRegistersSenders() { $container = $this->getContainerBuilder(); - $container->register(AmqpSender::class, AmqpSender::class)->addTag('messenger.sender', array('name' => 'amqp')); + $container->register(AmqpSender::class, AmqpSender::class)->addTag('messenger.sender', array('alias' => 'amqp')); (new MessengerPass())->process($container); From ee44903fd060fdf714d0d17144541a9d9bed115d Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Wed, 9 May 2018 21:15:27 +0200 Subject: [PATCH 11/18] [HttpKernel] Fix services are no longer injected into __invoke controllers method --- .../RegisterControllerArgumentLocatorsPass.php | 8 ++++++-- .../RemoveEmptyControllerArgumentLocatorsPass.php | 13 ++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php index cd76e56caa..490b595b94 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -33,11 +33,13 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface { private $resolverServiceId; private $controllerTag; + private $controllerLocator; - public function __construct(string $resolverServiceId = 'argument_resolver.service', string $controllerTag = 'controller.service_arguments') + public function __construct(string $resolverServiceId = 'argument_resolver.service', string $controllerTag = 'controller.service_arguments', string $controllerLocator = 'argument_resolver.controller_locator') { $this->resolverServiceId = $resolverServiceId; $this->controllerTag = $controllerTag; + $this->controllerLocator = $controllerLocator; } public function process(ContainerBuilder $container) @@ -179,6 +181,8 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface } $container->getDefinition($this->resolverServiceId) - ->replaceArgument(0, ServiceLocatorTagPass::register($container, $controllers)); + ->replaceArgument(0, $controllerLocatorRef = ServiceLocatorTagPass::register($container, $controllers)); + + $container->setAlias($this->controllerLocator, (string) $controllerLocatorRef); } } diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php index b7d64994ce..596b6188f6 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php @@ -21,21 +21,16 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; */ class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface { - private $resolverServiceId; + private $controllerLocator; - public function __construct(string $resolverServiceId = 'argument_resolver.service') + public function __construct(string $controllerLocator = 'argument_resolver.controller_locator') { - $this->resolverServiceId = $resolverServiceId; + $this->controllerLocator = $controllerLocator; } public function process(ContainerBuilder $container) { - if (false === $container->hasDefinition($this->resolverServiceId)) { - return; - } - - $serviceResolver = $container->getDefinition($this->resolverServiceId); - $controllerLocator = $container->getDefinition((string) $serviceResolver->getArgument(0)); + $controllerLocator = $container->findDefinition($this->controllerLocator); $controllers = $controllerLocator->getArgument(0); foreach ($controllers as $controller => $argumentRef) { From 63871c9ce49086e5c97075af957ccb63600f2809 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Thu, 10 May 2018 12:41:21 -0400 Subject: [PATCH 12/18] [Messenger] Make sure default receiver name is set before command configuration --- .../Command/ConsumeMessagesCommand.php | 4 +-- .../Command/ConsumeMessagesCommandTest.php | 36 +++++++++++++++++++ src/Symfony/Component/Messenger/composer.json | 1 + 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Messenger/Tests/Command/ConsumeMessagesCommandTest.php diff --git a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php index 62892c59f0..e64d10f195 100644 --- a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php +++ b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php @@ -41,12 +41,12 @@ class ConsumeMessagesCommand extends Command public function __construct(MessageBusInterface $bus, ContainerInterface $receiverLocator, LoggerInterface $logger = null, string $defaultReceiverName = null) { - parent::__construct(); - $this->bus = $bus; $this->receiverLocator = $receiverLocator; $this->logger = $logger; $this->defaultReceiverName = $defaultReceiverName; + + parent::__construct(); } /** diff --git a/src/Symfony/Component/Messenger/Tests/Command/ConsumeMessagesCommandTest.php b/src/Symfony/Component/Messenger/Tests/Command/ConsumeMessagesCommandTest.php new file mode 100644 index 0000000000..4eca2423aa --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Command/ConsumeMessagesCommandTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\Messenger\Command\ConsumeMessagesCommand; +use Symfony\Component\Messenger\MessageBus; + +class ConsumeMessagesCommandTest extends TestCase +{ + public function testConfigurationWithDefaultReceiver() + { + $command = new ConsumeMessagesCommand($this->createMock(MessageBus::class), $this->createMock(ServiceLocator::class), null, 'messenger.transport.amqp'); + $inputArgument = $command->getDefinition()->getArgument('receiver'); + $this->assertFalse($inputArgument->isRequired()); + $this->assertSame('messenger.transport.amqp', $inputArgument->getDefault()); + } + + public function testConfigurationWithoutDefaultReceiver() + { + $command = new ConsumeMessagesCommand($this->createMock(MessageBus::class), $this->createMock(ServiceLocator::class)); + $inputArgument = $command->getDefinition()->getArgument('receiver'); + $this->assertTrue($inputArgument->isRequired()); + $this->assertNull($inputArgument->getDefault()); + } +} diff --git a/src/Symfony/Component/Messenger/composer.json b/src/Symfony/Component/Messenger/composer.json index 1767f4dab9..adbd0e2a9f 100644 --- a/src/Symfony/Component/Messenger/composer.json +++ b/src/Symfony/Component/Messenger/composer.json @@ -20,6 +20,7 @@ }, "require-dev": { "psr/log": "~1.0", + "symfony/console": "~3.4|~4.0", "symfony/dependency-injection": "~3.4.6|~4.0", "symfony/http-kernel": "~3.4|~4.0", "symfony/process": "~3.4|~4.0", From fa4ce7bbc44fc5a16e9402d7e33845f0f13d963d Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Thu, 10 May 2018 15:56:57 -0400 Subject: [PATCH 13/18] [Messenger] remove autoconfiguration for Sender/ReceiverInterface --- .../DependencyInjection/FrameworkExtension.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index fa853e0940..bc41753a7b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -63,8 +63,6 @@ use Symfony\Component\Lock\StoreInterface; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\MessageBusInterface; -use Symfony\Component\Messenger\Transport\ReceiverInterface; -use Symfony\Component\Messenger\Transport\SenderInterface; use Symfony\Component\Messenger\Transport\TransportFactoryInterface; use Symfony\Component\Messenger\Transport\TransportInterface; use Symfony\Component\PropertyAccess\PropertyAccessor; @@ -344,10 +342,6 @@ class FrameworkExtension extends Extension ->addTag('validator.constraint_validator'); $container->registerForAutoconfiguration(ObjectInitializerInterface::class) ->addTag('validator.initializer'); - $container->registerForAutoconfiguration(ReceiverInterface::class) - ->addTag('messenger.receiver'); - $container->registerForAutoconfiguration(SenderInterface::class) - ->addTag('messenger.sender'); $container->registerForAutoconfiguration(MessageHandlerInterface::class) ->addTag('messenger.message_handler'); $container->registerForAutoconfiguration(TransportFactoryInterface::class) From 2461e5119ae8de182b88eb8cbb49fb4df0864b34 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Tue, 24 Apr 2018 17:35:44 +0100 Subject: [PATCH 14/18] [Messenger][DX] Uses custom method names for handlers --- .../DependencyInjection/MessengerPass.php | 37 ++++++- .../Handler/MessageSubscriberInterface.php | 11 ++- .../DependencyInjection/MessengerPassTest.php | 96 ++++++++++++++++++- 3 files changed, 132 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php index 9789ec038c..230676d8ee 100644 --- a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php +++ b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php @@ -74,14 +74,27 @@ class MessengerPass implements CompilerPassInterface private function registerHandlers(ContainerBuilder $container) { + $definitions = array(); $handlersByMessage = array(); foreach ($container->findTaggedServiceIds($this->handlerTag, true) as $serviceId => $tags) { foreach ($tags as $tag) { - $handles = isset($tag['handles']) ? array($tag['handles']) : $this->guessHandledClasses($r = $container->getReflectionClass($container->getDefinition($serviceId)->getClass()), $serviceId); + $r = $container->getReflectionClass($container->getDefinition($serviceId)->getClass()); + + if (isset($tag['handles'])) { + $handles = isset($tag['method']) ? array($tag['handles'] => $tag['method']) : array($tag['handles']); + } else { + $handles = $this->guessHandledClasses($r, $serviceId); + } + $priority = $tag['priority'] ?? 0; - foreach ($handles as $messageClass) { + foreach ($handles as $messageClass => $method) { + if (\is_int($messageClass)) { + $messageClass = $method; + $method = '__invoke'; + } + if (\is_array($messageClass)) { $messagePriority = $messageClass[1]; $messageClass = $messageClass[0]; @@ -89,12 +102,27 @@ class MessengerPass implements CompilerPassInterface $messagePriority = $priority; } - if (!class_exists($messageClass)) { - $messageClassLocation = isset($tag['handles']) ? 'declared in your tag attribute "handles"' : sprintf($r->implementsInterface(MessageHandlerInterface::class) ? 'returned by method "%s::getHandledMessages()"' : 'used as argument type in method "%s::__invoke()"', $r->getName()); + if (\is_array($method)) { + $messagePriority = $method[1]; + $method = $method[0]; + } + + if (!\class_exists($messageClass)) { + $messageClassLocation = isset($tag['handles']) ? 'declared in your tag attribute "handles"' : $r->implementsInterface(MessageHandlerInterface::class) ? sprintf('returned by method "%s::getHandledMessages()"', $r->getName()) : sprintf('used as argument type in method "%s::%s()"', $r->getName(), $method); throw new RuntimeException(sprintf('Invalid handler service "%s": message class "%s" %s does not exist.', $serviceId, $messageClass, $messageClassLocation)); } + if (!$r->hasMethod($method)) { + throw new RuntimeException(sprintf('Invalid handler service "%s": method "%s::%s()" does not exist.', $serviceId, $r->getName(), $method)); + } + + if ('__invoke' !== $method) { + $wrapperDefinition = (new Definition('callable'))->addArgument(array(new Reference($serviceId), $method))->setFactory('Closure::fromCallable'); + + $definitions[$serviceId = '.messenger.method_on_object_wrapper.'.ContainerBuilder::hash($messageClass.':'.$messagePriority.':'.$serviceId.':'.$method)] = $wrapperDefinition; + } + $handlersByMessage[$messageClass][$messagePriority][] = new Reference($serviceId); } } @@ -105,7 +133,6 @@ class MessengerPass implements CompilerPassInterface $handlersByMessage[$message] = array_merge(...$handlersByMessage[$message]); } - $definitions = array(); $handlersLocatorMapping = array(); foreach ($handlersByMessage as $message => $handlers) { if (1 === \count($handlers)) { diff --git a/src/Symfony/Component/Messenger/Handler/MessageSubscriberInterface.php b/src/Symfony/Component/Messenger/Handler/MessageSubscriberInterface.php index 6a751482bf..ba4ae3ee79 100644 --- a/src/Symfony/Component/Messenger/Handler/MessageSubscriberInterface.php +++ b/src/Symfony/Component/Messenger/Handler/MessageSubscriberInterface.php @@ -34,9 +34,14 @@ interface MessageSubscriberInterface extends MessageHandlerInterface * [SecondMessage::class, -10], * ]; * - * The `__invoke` method of the handler will be called as usual with the message to handle. + * It can also specify a method and/or a priority per message: * - * @return array + * return [ + * FirstMessage::class => 'firstMessageMethod', + * SecondMessage::class => ['secondMessageMethod', 20], + * ]; + * + * The `__invoke` method of the handler will be called as usual with the message to handle. */ - public static function getHandledMessages(): array; + public static function getHandledMessages(): iterable; } diff --git a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php index eeb5d585f5..ac32461da6 100644 --- a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php +++ b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php @@ -98,6 +98,53 @@ class MessengerPassTest extends TestCase $this->assertEquals(array(new Reference(PrioritizedHandler::class), new Reference(HandlerWithMultipleMessages::class)), $definition->getArgument(0)); } + public function testGetClassesAndMethodsAndPrioritiesFromTheSubscriber() + { + $container = $this->getContainerBuilder(); + $container + ->register(HandlerMappingMethods::class, HandlerMappingMethods::class) + ->addTag('messenger.message_handler') + ; + $container + ->register(PrioritizedHandler::class, PrioritizedHandler::class) + ->addTag('messenger.message_handler') + ; + + (new MessengerPass())->process($container); + + $handlerLocatorDefinition = $container->getDefinition($container->getDefinition('messenger.handler_resolver')->getArgument(0)); + $handlerMapping = $handlerLocatorDefinition->getArgument(0); + + $this->assertArrayHasKey('handler.'.DummyMessage::class, $handlerMapping); + $this->assertArrayHasKey('handler.'.SecondMessage::class, $handlerMapping); + + $dummyHandlerReference = (string) $handlerMapping['handler.'.DummyMessage::class]->getValues()[0]; + $dummyHandlerDefinition = $container->getDefinition($dummyHandlerReference); + $this->assertSame('callable', $dummyHandlerDefinition->getClass()); + $this->assertEquals(array(new Reference(HandlerMappingMethods::class), 'dummyMethod'), $dummyHandlerDefinition->getArgument(0)); + $this->assertSame(array('Closure', 'fromCallable'), $dummyHandlerDefinition->getFactory()); + + $secondHandlerReference = (string) $handlerMapping['handler.'.SecondMessage::class]->getValues()[0]; + $secondHandlerDefinition = $container->getDefinition($secondHandlerReference); + $this->assertSame(ChainHandler::class, $secondHandlerDefinition->getClass()); + $this->assertEquals(new Reference(PrioritizedHandler::class), $secondHandlerDefinition->getArgument(0)[1]); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage Invalid handler service "Symfony\Component\Messenger\Tests\DependencyInjection\HandlerMappingWithNonExistentMethod": method "Symfony\Component\Messenger\Tests\DependencyInjection\HandlerMappingWithNonExistentMethod::dummyMethod()" does not exist. + */ + public function testThrowsExceptionIfTheHandlerMethodDoesNotExist() + { + $container = $this->getContainerBuilder(); + $container + ->register(HandlerMappingWithNonExistentMethod::class, HandlerMappingWithNonExistentMethod::class) + ->addTag('messenger.message_handler') + ; + + (new MessengerPass())->process($container); + } + public function testItRegistersReceivers() { $container = $this->getContainerBuilder(); @@ -397,7 +444,7 @@ class UndefinedMessageHandler class UndefinedMessageHandlerViaInterface implements MessageSubscriberInterface { - public static function getHandledMessages(): array + public static function getHandledMessages(): iterable { return array(UndefinedMessage::class); } @@ -434,31 +481,72 @@ class BuiltinArgumentTypeHandler class HandlerWithMultipleMessages implements MessageSubscriberInterface { - public static function getHandledMessages(): array + public static function getHandledMessages(): iterable { return array( DummyMessage::class, SecondMessage::class, ); } + + public function __invoke() + { + } } class PrioritizedHandler implements MessageSubscriberInterface { - public static function getHandledMessages(): array + public static function getHandledMessages(): iterable { return array( array(SecondMessage::class, 10), ); } + + public function __invoke() + { + } +} + +class HandlerMappingMethods implements MessageSubscriberInterface +{ + public static function getHandledMessages(): iterable + { + return array( + DummyMessage::class => 'dummyMethod', + SecondMessage::class => array('secondMessage', 20), + ); + } + + public function dummyMethod() + { + } + + public function secondMessage() + { + } +} + +class HandlerMappingWithNonExistentMethod implements MessageSubscriberInterface +{ + public static function getHandledMessages(): iterable + { + return array( + DummyMessage::class => 'dummyMethod', + ); + } } class HandleNoMessageHandler implements MessageSubscriberInterface { - public static function getHandledMessages(): array + public static function getHandledMessages(): iterable { return array(); } + + public function __invoke() + { + } } class UselessMiddleware implements MiddlewareInterface From 2882f8d8c848ebbc919d247662c5cb4eb8301f78 Mon Sep 17 00:00:00 2001 From: Valentin Date: Mon, 7 May 2018 23:13:10 +0300 Subject: [PATCH 15/18] [Workflow] Added DefinitionBuilder::setMetadataStore(). --- src/Symfony/Component/Workflow/Definition.php | 1 - .../Component/Workflow/DefinitionBuilder.php | 16 +++++++++++++++- .../Workflow/Tests/DefinitionBuilderTest.php | 11 +++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Workflow/Definition.php b/src/Symfony/Component/Workflow/Definition.php index 9e9e1e796f..310cec8f15 100644 --- a/src/Symfony/Component/Workflow/Definition.php +++ b/src/Symfony/Component/Workflow/Definition.php @@ -30,7 +30,6 @@ final class Definition /** * @param string[] $places * @param Transition[] $transitions - * @param string|null $initialPlace */ public function __construct(array $places, array $transitions, string $initialPlace = null, MetadataStoreInterface $metadataStore = null) { diff --git a/src/Symfony/Component/Workflow/DefinitionBuilder.php b/src/Symfony/Component/Workflow/DefinitionBuilder.php index 94e1e2effe..308f950324 100644 --- a/src/Symfony/Component/Workflow/DefinitionBuilder.php +++ b/src/Symfony/Component/Workflow/DefinitionBuilder.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Workflow; +use Symfony\Component\Workflow\Metadata\MetadataStoreInterface; + /** * Builds a definition. * @@ -23,6 +25,7 @@ class DefinitionBuilder private $places = array(); private $transitions = array(); private $initialPlace; + private $metadataStore; /** * @param string[] $places @@ -39,7 +42,7 @@ class DefinitionBuilder */ public function build() { - return new Definition($this->places, $this->transitions, $this->initialPlace); + return new Definition($this->places, $this->transitions, $this->initialPlace, $this->metadataStore); } /** @@ -52,6 +55,7 @@ class DefinitionBuilder $this->places = array(); $this->transitions = array(); $this->initialPlace = null; + $this->metadataStore = null; return $this; } @@ -122,6 +126,16 @@ class DefinitionBuilder return $this; } + /** + * @return $this + */ + public function setMetadataStore(MetadataStoreInterface $metadataStore) + { + $this->metadataStore = $metadataStore; + + return $this; + } + /** * @deprecated since Symfony 4.1, use the clear() method instead. * diff --git a/src/Symfony/Component/Workflow/Tests/DefinitionBuilderTest.php b/src/Symfony/Component/Workflow/Tests/DefinitionBuilderTest.php index 1939fb5713..1af7a32693 100644 --- a/src/Symfony/Component/Workflow/Tests/DefinitionBuilderTest.php +++ b/src/Symfony/Component/Workflow/Tests/DefinitionBuilderTest.php @@ -4,6 +4,7 @@ namespace Symfony\Component\Workflow\Tests; use PHPUnit\Framework\TestCase; use Symfony\Component\Workflow\DefinitionBuilder; +use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore; use Symfony\Component\Workflow\Transition; class DefinitionBuilderTest extends TestCase @@ -44,4 +45,14 @@ class DefinitionBuilderTest extends TestCase $this->assertEquals('a', $definition->getPlaces()['a']); $this->assertEquals('b', $definition->getPlaces()['b']); } + + public function testSetMetadataStore() + { + $builder = new DefinitionBuilder(array('a')); + $metadataStore = new InMemoryMetadataStore(); + $builder->setMetadataStore($metadataStore); + $definition = $builder->build(); + + $this->assertSame($metadataStore, $definition->getMetadataStore()); + } } From 3d19578297e64497d3613f9dc2294d510885a466 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Tue, 8 May 2018 18:54:52 +0200 Subject: [PATCH 16/18] [Messenger] Improve the profiler panel --- .../views/Collector/messenger.html.twig | 171 ++++++++++++++---- .../views/Profiler/profiler.css.twig | 3 + .../DataCollector/MessengerDataCollector.php | 66 ++++--- .../MessengerDataCollectorTest.php | 88 +++++++-- .../Tests/Fixtures/AnEnvelopeItem.php | 33 ++++ .../Messenger/Tests/MessageBusTest.php | 21 +-- .../Tests/TraceableMessageBusTest.php | 24 ++- .../Messenger/TraceableMessageBus.php | 6 + 8 files changed, 310 insertions(+), 102 deletions(-) create mode 100644 src/Symfony/Component/Messenger/Tests/Fixtures/AnEnvelopeItem.php diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig index 908efe8771..d8befbbf8d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig @@ -4,17 +4,33 @@ {% block toolbar %} {% if collector.messages|length > 0 %} + {% set status_color = collector.exceptionsCount ? 'red' %} {% set icon %} {{ include('@WebProfiler/Icon/messenger.svg') }} {{ collector.messages|length }} {% endset %} - {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: 'messenger' }) }} + {% set text %} + {% for bus in collector.buses %} + {% set exceptionsCount = collector.exceptionsCount(bus) %} +
+ {{ bus }} + + {{ collector.messages(bus)|length }} + +
+ {% endfor %} + {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: 'messenger', status: status_color }) }} {% endif %} {% endblock %} {% block menu %} - + {{ include('@WebProfiler/Icon/messenger.svg') }} Messages @@ -24,7 +40,28 @@ {% endblock %} +{% block head %} + {{ parent() }} + +{% endblock %} + {% block panel %} + {% import _self as helper %} +

Messages

{% if collector.messages is empty %} @@ -32,41 +69,99 @@

No messages have been collected.

{% else %} - - - - - - - - - - {% for message in collector.messages %} - - - - - - {% endfor %} - -
BusMessageResult
{{ message.bus }} - {% if message.result.object is defined %} - {{ profiler_dump(message.message.object, maxDepth=2) }} - {% else %} - {{ message.message.type }} - {% endif %} - - {% if message.result.object is defined %} - {{ profiler_dump(message.result.object, maxDepth=2) }} - {% elseif message.result.type is defined %} - {{ message.result.type }} - {% if message.result.value is defined %} - {{ message.result.value }} - {% endif %} - {% endif %} - {% if message.exception.type is defined %} - {{ message.exception.type }} - {% endif %} -
+ +
+
+ {% set messages = collector.messages %} + {% set exceptionsCount = collector.exceptionsCount %} +

All{{ messages|length }}

+ +
+

Ordered list of dispatched messages across all your buses

+ {{ helper.render_bus_messages(messages, true) }} +
+
+ + {% for bus in collector.buses %} +
+ {% set messages = collector.messages(bus) %} + {% set exceptionsCount = collector.exceptionsCount(bus) %} +

{{ bus }}{{ messages|length }}

+ +
+

Ordered list of messages dispatched on the {{ bus }} bus

+ {{ helper.render_bus_messages(messages) }} +
+
+ {% endfor %} {% endif %} + {% endblock %} + +{% macro render_bus_messages(messages, showBus = false) %} + {% set discr = random() %} + {% for i, dispatchCall in messages %} + + + + + + + + {% if showBus %} + + + + + {% endif %} + + + + + + + + + + + + + {% if dispatchCall.exception is defined %} + + + + + {% endif %} + +
+ {{ profiler_dump(dispatchCall.message.type) }} + {% if showBus %} + {{ dispatchCall.bus }} + {% endif %} + {% if dispatchCall.exception is defined %} + exception + {% endif %} + + {{ include('@Twig/images/icon-minus-square.svg') }} + {{ include('@Twig/images/icon-plus-square.svg') }} + +
Bus{{ dispatchCall.bus }}
Message{{ profiler_dump(dispatchCall.message.value, maxDepth=2) }}
Envelope items + {% for item in dispatchCall.envelopeItems %} + {{ profiler_dump(item) }} + {% else %} + No items + {% endfor %} +
Result + {% if dispatchCall.result is defined %} + {{ profiler_dump(dispatchCall.result.seek('value'), maxDepth=2) }} + {% elseif dispatchCall.exception is defined %} + No result as an exception occurred + {% endif %} +
Exception + {{ profiler_dump(dispatchCall.exception.value, maxDepth=1) }} +
+ {% endfor %} +{% endmacro %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig index 96cd8878a8..f9bc41d6a1 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig @@ -215,6 +215,9 @@ table tbody ul { .text-muted { color: #999; } +.text-danger { + color: {{ colors.error|raw }}; +} .text-bold { font-weight: bold; } diff --git a/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php b/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php index 0fe44d62fe..ed98f4cfa4 100644 --- a/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php +++ b/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php @@ -16,6 +16,8 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; use Symfony\Component\Messenger\TraceableMessageBus; +use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Component\VarDumper\Cloner\Data; /** * @author Samuel Roze @@ -44,13 +46,25 @@ class MessengerDataCollector extends DataCollector implements LateDataCollectorI */ public function lateCollect() { - $this->data = array('messages' => array()); + $this->data = array('messages' => array(), 'buses' => array_keys($this->traceableBuses)); + $messages = array(); foreach ($this->traceableBuses as $busName => $bus) { foreach ($bus->getDispatchedMessages() as $message) { - $this->data['messages'][] = $this->collectMessage($busName, $message); + $debugRepresentation = $this->cloneVar($this->collectMessage($busName, $message)); + $messages[] = array($debugRepresentation, $message['callTime']); } } + + // Order by call time + usort($messages, function (array $a, array $b): int { + return $a[1] > $b[1] ? 1 : -1; + }); + + // Keep the messages clones only + $this->data['messages'] = array_map(function (array $item): Data { + return $item[0]; + }, $messages); } /** @@ -78,31 +92,19 @@ class MessengerDataCollector extends DataCollector implements LateDataCollectorI $debugRepresentation = array( 'bus' => $busName, + 'envelopeItems' => $tracedMessage['envelopeItems'] ?? null, 'message' => array( - 'type' => \get_class($message), - 'object' => $this->cloneVar($message), + 'type' => new ClassStub(\get_class($message)), + 'value' => $message, ), ); if (array_key_exists('result', $tracedMessage)) { $result = $tracedMessage['result']; - - if (\is_object($result)) { - $debugRepresentation['result'] = array( - 'type' => \get_class($result), - 'object' => $this->cloneVar($result), - ); - } elseif (\is_array($result)) { - $debugRepresentation['result'] = array( - 'type' => 'array', - 'object' => $this->cloneVar($result), - ); - } else { - $debugRepresentation['result'] = array( - 'type' => \gettype($result), - 'value' => $result, - ); - } + $debugRepresentation['result'] = array( + 'type' => \is_object($result) ? \get_class($result) : gettype($result), + 'value' => $result, + ); } if (isset($tracedMessage['exception'])) { @@ -110,15 +112,31 @@ class MessengerDataCollector extends DataCollector implements LateDataCollectorI $debugRepresentation['exception'] = array( 'type' => \get_class($exception), - 'message' => $exception->getMessage(), + 'value' => $exception, ); } return $debugRepresentation; } - public function getMessages(): array + public function getExceptionsCount(string $bus = null): int { - return $this->data['messages'] ?? array(); + return array_reduce($this->getMessages($bus), function (int $carry, Data $message) { + return $carry += isset($message['exception']) ? 1 : 0; + }, 0); + } + + public function getMessages(string $bus = null): array + { + $messages = $this->data['messages'] ?? array(); + + return $bus ? array_filter($messages, function (Data $message) use ($bus): bool { + return $bus === $message['bus']; + }) : $messages; + } + + public function getBuses(): array + { + return $this->data['buses']; } } diff --git a/src/Symfony/Component/Messenger/Tests/DataCollector/MessengerDataCollectorTest.php b/src/Symfony/Component/Messenger/Tests/DataCollector/MessengerDataCollectorTest.php index 91f54490ac..d88593e3e7 100644 --- a/src/Symfony/Component/Messenger/Tests/DataCollector/MessengerDataCollectorTest.php +++ b/src/Symfony/Component/Messenger/Tests/DataCollector/MessengerDataCollectorTest.php @@ -16,14 +16,22 @@ use Symfony\Component\Messenger\DataCollector\MessengerDataCollector; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\TraceableMessageBus; -use Symfony\Component\VarDumper\Test\VarDumperTestTrait; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\CliDumper; /** * @author Maxime Steinhausser */ class MessengerDataCollectorTest extends TestCase { - use VarDumperTestTrait; + /** @var CliDumper */ + private $dumper; + + protected function setUp() + { + $this->dumper = new CliDumper(); + $this->dumper->setColors(false); + } /** * @dataProvider getHandleTestData @@ -46,17 +54,18 @@ class MessengerDataCollectorTest extends TestCase $messages = $collector->getMessages(); $this->assertCount(1, $messages); - $this->assertDumpMatchesFormat($expected, $messages[0]); + $this->assertStringMatchesFormat($expected, $this->getDataAsString($messages[0])); } public function getHandleTestData() { $messageDump = << "default" + "envelopeItems" => null "message" => array:2 [ "type" => "Symfony\Component\Messenger\Tests\Fixtures\DummyMessage" - "object" => Symfony\Component\VarDumper\Cloner\Data {%A - %A+class: "Symfony\Component\Messenger\Tests\Fixtures\DummyMessage"%A + "value" => Symfony\Component\Messenger\Tests\Fixtures\DummyMessage %A + -message: "dummy message" } ] DUMP; @@ -64,7 +73,7 @@ DUMP; yield 'no returned value' => array( null, << array:2 [ "type" => "NULL" @@ -77,7 +86,7 @@ DUMP yield 'scalar returned value' => array( 'returned value', << array:2 [ "type" => "string" @@ -90,11 +99,13 @@ DUMP yield 'array returned value' => array( array('returned value'), << array:2 [ "type" => "array" - "object" => Symfony\Component\VarDumper\Cloner\Data {%A + "value" => array:1 [ + 0 => "returned value" + ] ] ] DUMP @@ -123,21 +134,66 @@ DUMP $messages = $collector->getMessages(); $this->assertCount(1, $messages); - $this->assertDumpMatchesFormat(<<assertStringMatchesFormat(<< "default" + "envelopeItems" => null "message" => array:2 [ "type" => "Symfony\Component\Messenger\Tests\Fixtures\DummyMessage" - "object" => Symfony\Component\VarDumper\Cloner\Data {%A - %A+class: "Symfony\Component\Messenger\Tests\Fixtures\DummyMessage"%A + "value" => Symfony\Component\Messenger\Tests\Fixtures\DummyMessage %A + -message: "dummy message" } ] "exception" => array:2 [ "type" => "RuntimeException" - "message" => "foo" + "value" => RuntimeException %A ] -] +] DUMP - , $messages[0]); + , $this->getDataAsString($messages[0])); + } + + public function testKeepsOrderedDispatchCalls() + { + $firstBus = $this->getMockBuilder(MessageBusInterface::class)->getMock(); + $firstBus = new TraceableMessageBus($firstBus); + + $secondBus = $this->getMockBuilder(MessageBusInterface::class)->getMock(); + $secondBus = new TraceableMessageBus($secondBus); + + $collector = new MessengerDataCollector(); + $collector->registerBus('first bus', $firstBus); + $collector->registerBus('second bus', $secondBus); + + $firstBus->dispatch(new DummyMessage('#1')); + $secondBus->dispatch(new DummyMessage('#2')); + $secondBus->dispatch(new DummyMessage('#3')); + $firstBus->dispatch(new DummyMessage('#4')); + $secondBus->dispatch(new DummyMessage('#5')); + + $collector->lateCollect(); + + $messages = $collector->getMessages(); + $this->assertCount(5, $messages); + + $this->assertSame('#1', $messages[0]['message']['value']['message']); + $this->assertSame('first bus', $messages[0]['bus']); + + $this->assertSame('#2', $messages[1]['message']['value']['message']); + $this->assertSame('second bus', $messages[1]['bus']); + + $this->assertSame('#3', $messages[2]['message']['value']['message']); + $this->assertSame('second bus', $messages[2]['bus']); + + $this->assertSame('#4', $messages[3]['message']['value']['message']); + $this->assertSame('first bus', $messages[3]['bus']); + + $this->assertSame('#5', $messages[4]['message']['value']['message']); + $this->assertSame('second bus', $messages[4]['bus']); + } + + private function getDataAsString(Data $data): string + { + return rtrim($this->dumper->dump($data, true)); } } diff --git a/src/Symfony/Component/Messenger/Tests/Fixtures/AnEnvelopeItem.php b/src/Symfony/Component/Messenger/Tests/Fixtures/AnEnvelopeItem.php new file mode 100644 index 0000000000..9e5bb0c92b --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Fixtures/AnEnvelopeItem.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\Messenger\Tests\Fixtures; + +use Symfony\Component\Messenger\EnvelopeItemInterface; + +class AnEnvelopeItem implements EnvelopeItemInterface +{ + /** + * {@inheritdoc} + */ + public function serialize() + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + // noop + } +} diff --git a/src/Symfony/Component/Messenger/Tests/MessageBusTest.php b/src/Symfony/Component/Messenger/Tests/MessageBusTest.php index ba40eb3f9a..9dc93a9d15 100644 --- a/src/Symfony/Component/Messenger/Tests/MessageBusTest.php +++ b/src/Symfony/Component/Messenger/Tests/MessageBusTest.php @@ -15,10 +15,10 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\EnvelopeAwareInterface; -use Symfony\Component\Messenger\EnvelopeItemInterface; use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; +use Symfony\Component\Messenger\Tests\Fixtures\AnEnvelopeItem; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; class MessageBusTest extends TestCase @@ -160,22 +160,3 @@ class MessageBusTest extends TestCase $bus->dispatch($envelope); } } - -class AnEnvelopeItem implements EnvelopeItemInterface -{ - /** - * {@inheritdoc} - */ - public function serialize() - { - return ''; - } - - /** - * {@inheritdoc} - */ - public function unserialize($serialized) - { - return new self(); - } -} diff --git a/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php b/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php index 4da1e5a630..8a2946ee42 100644 --- a/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php +++ b/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Tests; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Messenger\Tests\Fixtures\AnEnvelopeItem; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\TraceableMessageBus; @@ -28,19 +29,29 @@ class TraceableMessageBusTest extends TestCase $traceableBus = new TraceableMessageBus($bus); $this->assertSame($result, $traceableBus->dispatch($message)); - $this->assertSame(array(array('message' => $message, 'result' => $result)), $traceableBus->getDispatchedMessages()); + $this->assertCount(1, $tracedMessages = $traceableBus->getDispatchedMessages()); + $this->assertArraySubset(array( + 'message' => $message, + 'result' => $result, + 'envelopeItems' => null, + ), $tracedMessages[0], true); } public function testItTracesResultWithEnvelope() { - $envelope = Envelope::wrap($message = new DummyMessage('Hello')); + $envelope = Envelope::wrap($message = new DummyMessage('Hello'))->with($envelopeItem = new AnEnvelopeItem()); $bus = $this->getMockBuilder(MessageBusInterface::class)->getMock(); $bus->expects($this->once())->method('dispatch')->with($envelope)->willReturn($result = array('foo' => 'bar')); $traceableBus = new TraceableMessageBus($bus); $this->assertSame($result, $traceableBus->dispatch($envelope)); - $this->assertSame(array(array('message' => $message, 'result' => $result)), $traceableBus->getDispatchedMessages()); + $this->assertCount(1, $tracedMessages = $traceableBus->getDispatchedMessages()); + $this->assertArraySubset(array( + 'message' => $message, + 'result' => $result, + 'envelopeItems' => array($envelopeItem), + ), $tracedMessages[0], true); } public function testItTracesExceptions() @@ -58,6 +69,11 @@ class TraceableMessageBusTest extends TestCase $this->assertSame($exception, $e); } - $this->assertSame(array(array('message' => $message, 'exception' => $exception)), $traceableBus->getDispatchedMessages()); + $this->assertCount(1, $tracedMessages = $traceableBus->getDispatchedMessages()); + $this->assertArraySubset(array( + 'message' => $message, + 'exception' => $exception, + 'envelopeItems' => null, + ), $tracedMessages[0], true); } } diff --git a/src/Symfony/Component/Messenger/TraceableMessageBus.php b/src/Symfony/Component/Messenger/TraceableMessageBus.php index 9620e0189d..b60d220b15 100644 --- a/src/Symfony/Component/Messenger/TraceableMessageBus.php +++ b/src/Symfony/Component/Messenger/TraceableMessageBus.php @@ -29,21 +29,27 @@ class TraceableMessageBus implements MessageBusInterface */ public function dispatch($message) { + $callTime = microtime(true); $messageToTrace = $message instanceof Envelope ? $message->getMessage() : $message; + $envelopeItems = $message instanceof Envelope ? array_values($message->all()) : null; try { $result = $this->decoratedBus->dispatch($message); $this->dispatchedMessages[] = array( + 'envelopeItems' => $envelopeItems, 'message' => $messageToTrace, 'result' => $result, + 'callTime' => $callTime, ); return $result; } catch (\Throwable $e) { $this->dispatchedMessages[] = array( + 'envelopeItems' => $envelopeItems, 'message' => $messageToTrace, 'exception' => $e, + 'callTime' => $callTime, ); throw $e; From 585ae7c6466eedf27c6e889e58996a35823eedfe Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Sun, 13 May 2018 19:11:39 +0200 Subject: [PATCH 17/18] [HttpKernel] Make TraceableValueResolver $stopwatch mandatory --- .../TraceableValueResolver.php | 4 ++-- .../ControllerArgumentValueResolverPass.php | 9 ++++---- ...ontrollerArgumentValueResolverPassTest.php | 22 ++++++++++++++++++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php index 9837a057a6..4d60aa15f7 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php @@ -26,10 +26,10 @@ final class TraceableValueResolver implements ArgumentValueResolverInterface private $inner; private $stopwatch; - public function __construct(ArgumentValueResolverInterface $inner, ?Stopwatch $stopwatch = null) + public function __construct(ArgumentValueResolverInterface $inner, Stopwatch $stopwatch) { $this->inner = $inner; - $this->stopwatch = $stopwatch ?? new Stopwatch(); + $this->stopwatch = $stopwatch; } /** diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php index 1b12a581f3..77c0e479ae 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php @@ -15,7 +15,6 @@ use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver; use Symfony\Component\Stopwatch\Stopwatch; @@ -31,11 +30,13 @@ class ControllerArgumentValueResolverPass implements CompilerPassInterface private $argumentResolverService; private $argumentValueResolverTag; + private $traceableResolverStopwatch; - public function __construct(string $argumentResolverService = 'argument_resolver', string $argumentValueResolverTag = 'controller.argument_value_resolver') + public function __construct(string $argumentResolverService = 'argument_resolver', string $argumentValueResolverTag = 'controller.argument_value_resolver', string $traceableResolverStopwatch = 'debug.stopwatch') { $this->argumentResolverService = $argumentResolverService; $this->argumentValueResolverTag = $argumentValueResolverTag; + $this->traceableResolverStopwatch = $traceableResolverStopwatch; } public function process(ContainerBuilder $container) @@ -46,12 +47,12 @@ class ControllerArgumentValueResolverPass implements CompilerPassInterface $resolvers = $this->findAndSortTaggedServices($this->argumentValueResolverTag, $container); - if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class)) { + if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class) && $container->has($this->traceableResolverStopwatch)) { foreach ($resolvers as $resolverReference) { $id = (string) $resolverReference; $container->register("debug.$id", TraceableValueResolver::class) ->setDecoratedService($id) - ->setArguments(array(new Reference("debug.$id.inner"), new Reference('debug.stopwatch', ContainerInterface::NULL_ON_INVALID_REFERENCE))); + ->setArguments(array(new Reference("debug.$id.inner"), new Reference($this->traceableResolverStopwatch))); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php index 3cbc62131f..49bbd0f9c1 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass; +use Symfony\Component\Stopwatch\Stopwatch; class ControllerArgumentValueResolverPassTest extends TestCase { @@ -52,7 +53,7 @@ class ControllerArgumentValueResolverPassTest extends TestCase $this->assertFalse($container->hasDefinition('n3.traceable')); } - public function testInDebug() + public function testInDebugWithStopWatchDefinition() { $services = array( 'n3' => array(array()), @@ -68,6 +69,7 @@ class ControllerArgumentValueResolverPassTest extends TestCase $definition = new Definition(ArgumentResolver::class, array(null, array())); $container = new ContainerBuilder(); + $container->register('debug.stopwatch', Stopwatch::class); $container->setDefinition('argument_resolver', $definition); foreach ($services as $id => list($tag)) { @@ -88,6 +90,24 @@ class ControllerArgumentValueResolverPassTest extends TestCase $this->assertTrue($container->hasDefinition('n3')); } + public function testInDebugWithouStopWatchDefinition() + { + $expected = array(new Reference('n1')); + + $definition = new Definition(ArgumentResolver::class, array(null, array())); + $container = new ContainerBuilder(); + $container->register('n1')->addTag('controller.argument_value_resolver'); + $container->setDefinition('argument_resolver', $definition); + + $container->setParameter('kernel.debug', true); + + (new ControllerArgumentValueResolverPass())->process($container); + $this->assertEquals($expected, $definition->getArgument(1)->getValues()); + + $this->assertFalse($container->hasDefinition('debug.n1')); + $this->assertTrue($container->hasDefinition('n1')); + } + public function testReturningEmptyArrayWhenNoService() { $definition = new Definition(ArgumentResolver::class, array(null, array())); From f5ef421474aa291411e08e57b2d955e730580c5c Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Wed, 2 May 2018 20:53:36 +0200 Subject: [PATCH 18/18] [Messenger] Middleware factories support in config --- .../DoctrineTransactionMiddlewareFactory.php | 37 +++++++++++++ .../DependencyInjection/Configuration.php | 31 ++++++++++- .../FrameworkExtension.php | 11 ++-- .../Resources/config/schema/symfony-1.0.xsd | 9 +++- ...er_middleware_factory_erroneous_format.php | 16 ++++++ .../Fixtures/php/messenger_multiple_buses.php | 1 + .../Fixtures/xml/messenger_multiple_buses.xml | 15 ++++-- ...er_middleware_factory_erroneous_format.yml | 7 +++ .../Fixtures/yml/messenger_multiple_buses.yml | 1 + .../FrameworkExtensionTest.php | 29 ++++++++-- .../XmlFrameworkExtensionTest.php | 5 ++ .../DependencyInjection/MessengerPass.php | 31 +++++++---- .../DependencyInjection/MessengerPassTest.php | 53 +++++++++++++++++-- 13 files changed, 222 insertions(+), 24 deletions(-) create mode 100644 src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddlewareFactory.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_middleware_factory_erroneous_format.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_middleware_factory_erroneous_format.yml diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddlewareFactory.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddlewareFactory.php new file mode 100644 index 0000000000..0db646fdc8 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddlewareFactory.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\Bridge\Doctrine\Messenger; + +use Symfony\Bridge\Doctrine\ManagerRegistry; + +/** + * Create a Doctrine ORM transaction middleware to be used in a message bus from an entity manager name. + * + * @author Maxime Steinhausser + * + * @experimental in 4.1 + * @final + */ +class DoctrineTransactionMiddlewareFactory +{ + private $managerRegistry; + + public function __construct(ManagerRegistry $managerRegistry) + { + $this->managerRegistry = $managerRegistry; + } + + public function createMiddleware(string $managerName): DoctrineTransactionMiddleware + { + return new DoctrineTransactionMiddleware($this->managerRegistry, $managerName); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index a20fde0313..ce65f52bef 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1061,7 +1061,36 @@ class Configuration implements ConfigurationInterface }) ->end() ->defaultValue(array()) - ->prototype('scalar')->end() + ->prototype('array') + ->beforeNormalization() + ->always() + ->then(function ($middleware): array { + if (!\is_array($middleware)) { + return array('id' => $middleware); + } + if (isset($middleware['id'])) { + return $middleware; + } + if (\count($middleware) > 1) { + throw new \InvalidArgumentException(sprintf('There is an error at path "framework.messenger" in one of the buses middleware definitions: expected a single entry for a middleware item config, with factory id as key and arguments as value. Got "%s".', json_encode($middleware))); + } + + return array( + 'id' => key($middleware), + 'arguments' => current($middleware), + ); + }) + ->end() + ->fixXmlConfig('argument') + ->children() + ->scalarNode('id')->isRequired()->cannotBeEmpty()->end() + ->arrayNode('arguments') + ->normalizeKeys(false) + ->defaultValue(array()) + ->prototype('variable') + ->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 c08ad7b390..6b6d266036 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1471,12 +1471,17 @@ class FrameworkExtension extends Extension $config['default_bus'] = key($config['buses']); } - $defaultMiddleware = array('before' => array('logging'), 'after' => array('route_messages', 'call_message_handler')); + $defaultMiddleware = array( + 'before' => array(array('id' => 'logging')), + 'after' => array(array('id' => 'route_messages'), array('id' => 'call_message_handler')), + ); foreach ($config['buses'] as $busId => $bus) { $middleware = $bus['default_middleware'] ? array_merge($defaultMiddleware['before'], $bus['middleware'], $defaultMiddleware['after']) : $bus['middleware']; - if (!$validationConfig['enabled'] && \in_array('messenger.middleware.validation', $middleware, true)) { - throw new LogicException('The Validation middleware is only available when the Validator component is installed and enabled. Try running "composer require symfony/validator".'); + foreach ($middleware as $middlewareItem) { + if (!$validationConfig['enabled'] && 'messenger.middleware.validation' === $middlewareItem['id']) { + throw new LogicException('The Validation middleware is only available when the Validator component is installed and enabled. Try running "composer require symfony/validator".'); + } } $container->setParameter($busId.'.middleware', $middleware); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 96a13c8cb8..c5e05b11a7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -391,9 +391,16 @@ - + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_middleware_factory_erroneous_format.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_middleware_factory_erroneous_format.php new file mode 100644 index 0000000000..d6cc86bd37 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_middleware_factory_erroneous_format.php @@ -0,0 +1,16 @@ +loadFromExtension('framework', array( + 'messenger' => array( + 'buses' => array( + 'command_bus' => array( + 'middleware' => array( + array( + 'foo' => array('qux'), + 'bar' => array('baz'), + ), + ), + ), + ), + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_multiple_buses.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_multiple_buses.php index 51080f5036..5f3b2b2302 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_multiple_buses.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_multiple_buses.php @@ -7,6 +7,7 @@ $container->loadFromExtension('framework', array( 'messenger.bus.commands' => null, 'messenger.bus.events' => array( 'middleware' => array( + array('with_factory' => array('foo', true, array('bar' => 'baz'))), 'allow_no_handler', ), ), diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_multiple_buses.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_multiple_buses.xml index 88b4358536..f63df5bbbe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_multiple_buses.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_multiple_buses.xml @@ -9,12 +9,19 @@ - allow_no_handler + + foo + true + + baz + + + - route_messages - allow_no_handler - call_message_handler + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_middleware_factory_erroneous_format.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_middleware_factory_erroneous_format.yml new file mode 100644 index 0000000000..74431414ba --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_middleware_factory_erroneous_format.yml @@ -0,0 +1,7 @@ +framework: + messenger: + buses: + command_bus: + middleware: + - foo: ['qux'] + bar: ['baz'] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_multiple_buses.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_multiple_buses.yml index 83c6213348..e279ef3bba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_multiple_buses.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_multiple_buses.yml @@ -5,6 +5,7 @@ framework: messenger.bus.commands: ~ messenger.bus.events: middleware: + - with_factory: [foo, true, { bar: baz }] - "allow_no_handler" messenger.bus.queries: default_middleware: false diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 8d94d32aa8..3461e8defe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -604,18 +604,41 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertTrue($container->has('messenger.bus.commands')); $this->assertSame(array(), $container->getDefinition('messenger.bus.commands')->getArgument(0)); - $this->assertEquals(array('logging', 'route_messages', 'call_message_handler'), $container->getParameter('messenger.bus.commands.middleware')); + $this->assertEquals(array( + array('id' => 'logging'), + array('id' => 'route_messages'), + array('id' => 'call_message_handler'), + ), $container->getParameter('messenger.bus.commands.middleware')); $this->assertTrue($container->has('messenger.bus.events')); $this->assertSame(array(), $container->getDefinition('messenger.bus.events')->getArgument(0)); - $this->assertEquals(array('logging', 'allow_no_handler', 'route_messages', 'call_message_handler'), $container->getParameter('messenger.bus.events.middleware')); + $this->assertEquals(array( + array('id' => 'logging'), + array('id' => 'with_factory', 'arguments' => array('foo', true, array('bar' => 'baz'))), + array('id' => 'allow_no_handler', 'arguments' => array()), + array('id' => 'route_messages'), + array('id' => 'call_message_handler'), + ), $container->getParameter('messenger.bus.events.middleware')); $this->assertTrue($container->has('messenger.bus.queries')); $this->assertSame(array(), $container->getDefinition('messenger.bus.queries')->getArgument(0)); - $this->assertEquals(array('route_messages', 'allow_no_handler', 'call_message_handler'), $container->getParameter('messenger.bus.queries.middleware')); + $this->assertEquals(array( + array('id' => 'route_messages', 'arguments' => array()), + array('id' => 'allow_no_handler', 'arguments' => array()), + array('id' => 'call_message_handler', 'arguments' => array()), + ), $container->getParameter('messenger.bus.queries.middleware')); $this->assertTrue($container->hasAlias('message_bus')); $this->assertSame('messenger.bus.commands', (string) $container->getAlias('message_bus')); } + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage There is an error at path "framework.messenger" in one of the buses middleware definitions: expected a single entry for a middleware item config, with factory id as key and arguments as value. Got "{"foo":["qux"],"bar":["baz"]}" + */ + public function testMessengerMiddlewareFactoryErroneousFormat() + { + $this->createContainerFromFile('messenger_middleware_factory_erroneous_format'); + } + public function testTranslator() { $container = $this->createContainerFromFile('full'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php index 03401e2482..a272ab3327 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php @@ -27,4 +27,9 @@ class XmlFrameworkExtensionTest extends FrameworkExtensionTest { $this->markTestSkipped('The assets key cannot be set to false using the XML configuration format.'); } + + public function testMessengerMiddlewareFactoryErroneousFormat() + { + $this->markTestSkipped('XML configuration will not allow eeroneous format.'); + } } diff --git a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php index 9789ec038c..1089027c28 100644 --- a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php +++ b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php @@ -221,24 +221,37 @@ class MessengerPass implements CompilerPassInterface $container->getDefinition('messenger.data_collector')->addMethodCall('registerBus', array($busId, new Reference($tracedBusId))); } - private function registerBusMiddleware(ContainerBuilder $container, string $busId, array $middleware) + private function registerBusMiddleware(ContainerBuilder $container, string $busId, array $middlewareCollection) { - $container->getDefinition($busId)->replaceArgument(0, array_map(function (string $name) use ($container, $busId) { - if (!$container->has($messengerMiddlewareId = 'messenger.middleware.'.$name)) { - $messengerMiddlewareId = $name; + $middlewareReferences = array(); + foreach ($middlewareCollection as $middlewareItem) { + $id = $middlewareItem['id']; + $arguments = $middlewareItem['arguments'] ?? array(); + if (!$container->has($messengerMiddlewareId = 'messenger.middleware.'.$id)) { + $messengerMiddlewareId = $id; } if (!$container->has($messengerMiddlewareId)) { - throw new RuntimeException(sprintf('Invalid middleware "%s": define such service to be able to use it.', $name)); + throw new RuntimeException(sprintf('Invalid middleware "%s": define such service to be able to use it.', $id)); } - if ($container->getDefinition($messengerMiddlewareId)->isAbstract()) { + if (($definition = $container->findDefinition($messengerMiddlewareId))->isAbstract()) { $childDefinition = new ChildDefinition($messengerMiddlewareId); + $count = \count($definition->getArguments()); + foreach (array_values($arguments ?? array()) as $key => $argument) { + // Parent definition can provide default arguments. + // Replace each explicitly or add if not set: + $key < $count ? $childDefinition->replaceArgument($key, $argument) : $childDefinition->addArgument($argument); + } - $container->setDefinition($messengerMiddlewareId = $busId.'.middleware.'.$name, $childDefinition); + $container->setDefinition($messengerMiddlewareId = $busId.'.middleware.'.$id, $childDefinition); + } elseif ($arguments) { + throw new RuntimeException(sprintf('Invalid middleware factory "%s": a middleware factory must be an abstract definition.', $id)); } - return new Reference($messengerMiddlewareId); - }, $middleware)); + $middlewareReferences[] = new Reference($messengerMiddlewareId); + } + + $container->getDefinition($busId)->replaceArgument(0, $middlewareReferences); } } diff --git a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php index eeb5d585f5..0bcec20fcb 100644 --- a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php +++ b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Messenger\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; @@ -312,14 +313,42 @@ class MessengerPassTest extends TestCase $container = $this->getContainerBuilder(); $container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus'); $container->register('messenger.middleware.allow_no_handler', AllowNoHandlerMiddleware::class)->setAbstract(true); + $container->register('middleware_with_factory', UselessMiddleware::class)->addArgument('some_default')->setAbstract(true); + $container->register('middleware_with_factory_using_default', UselessMiddleware::class)->addArgument('some_default')->setAbstract(true); $container->register(UselessMiddleware::class, UselessMiddleware::class); - $container->setParameter($middlewareParameter = $fooBusId.'.middleware', array(UselessMiddleware::class, 'allow_no_handler')); + $container->setParameter($middlewareParameter = $fooBusId.'.middleware', array( + array('id' => UselessMiddleware::class), + array('id' => 'middleware_with_factory', 'arguments' => array('foo', 'bar')), + array('id' => 'middleware_with_factory_using_default'), + array('id' => 'allow_no_handler'), + )); (new MessengerPass())->process($container); + (new ResolveChildDefinitionsPass())->process($container); $this->assertTrue($container->hasDefinition($childMiddlewareId = $fooBusId.'.middleware.allow_no_handler')); - $this->assertEquals(array(new Reference(UselessMiddleware::class), new Reference($childMiddlewareId)), $container->getDefinition($fooBusId)->getArgument(0)); + + $this->assertTrue($container->hasDefinition($factoryChildMiddlewareId = $fooBusId.'.middleware.middleware_with_factory')); + $this->assertEquals( + array('foo', 'bar'), + $container->getDefinition($factoryChildMiddlewareId)->getArguments(), + 'parent default argument is overridden, and next ones appended' + ); + + $this->assertTrue($container->hasDefinition($factoryWithDefaultChildMiddlewareId = $fooBusId.'.middleware.middleware_with_factory_using_default')); + $this->assertEquals( + array('some_default'), + $container->getDefinition($factoryWithDefaultChildMiddlewareId)->getArguments(), + 'parent default argument is used' + ); + + $this->assertEquals(array( + new Reference(UselessMiddleware::class), + new Reference($factoryChildMiddlewareId), + new Reference($factoryWithDefaultChildMiddlewareId), + new Reference($childMiddlewareId), + ), $container->getDefinition($fooBusId)->getArgument(0)); $this->assertFalse($container->hasParameter($middlewareParameter)); } @@ -331,7 +360,25 @@ class MessengerPassTest extends TestCase { $container = $this->getContainerBuilder(); $container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus'); - $container->setParameter($middlewareParameter = $fooBusId.'.middleware', array('not_defined_middleware')); + $container->setParameter($middlewareParameter = $fooBusId.'.middleware', array( + array('id' => 'not_defined_middleware', 'arguments' => array()), + )); + + (new MessengerPass())->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage Invalid middleware factory "not_an_abstract_definition": a middleware factory must be an abstract definition. + */ + public function testMiddlewareFactoryDefinitionMustBeAbstract() + { + $container = $this->getContainerBuilder(); + $container->register('not_an_abstract_definition', UselessMiddleware::class); + $container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus', array('name' => 'foo')); + $container->setParameter($middlewareParameter = $fooBusId.'.middleware', array( + array('id' => 'not_an_abstract_definition', 'arguments' => array('foo')), + )); (new MessengerPass())->process($container); }