[Messenger] Middleware factories support in config

This commit is contained in:
Maxime Steinhausser 2018-05-02 20:53:36 +02:00 committed by Samuel ROZE
parent 3cc4a701e6
commit f5ef421474
13 changed files with 222 additions and 24 deletions

View File

@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <maxime.steinhausser@gmail.com>
*
* @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);
}
}

View File

@ -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()

View File

@ -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);

View File

@ -391,9 +391,16 @@
<xsd:complexType name="messenger_bus">
<xsd:sequence>
<xsd:element name="middleware" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="middleware" type="messenger_middleware" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="default-middleware" type="xsd:boolean"/>
</xsd:complexType>
<xsd:complexType name="messenger_middleware">
<xsd:sequence>
<xsd:element name="argument" type="xsd:anyType" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:schema>

View File

@ -0,0 +1,16 @@
<?php
$container->loadFromExtension('framework', array(
'messenger' => array(
'buses' => array(
'command_bus' => array(
'middleware' => array(
array(
'foo' => array('qux'),
'bar' => array('baz'),
),
),
),
),
),
));

View File

@ -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',
),
),

View File

@ -9,12 +9,19 @@
<framework:messenger default-bus="messenger.bus.commands">
<framework:bus name="messenger.bus.commands" />
<framework:bus name="messenger.bus.events">
<framework:middleware>allow_no_handler</framework:middleware>
<framework:middleware id="with_factory">
<framework:argument>foo</framework:argument>
<framework:argument>true</framework:argument>
<framework:argument>
<framework:bar>baz</framework:bar>
</framework:argument>
</framework:middleware>
<framework:middleware id="allow_no_handler" />
</framework:bus>
<framework:bus name="messenger.bus.queries" default-middleware="false">
<framework:middleware>route_messages</framework:middleware>
<framework:middleware>allow_no_handler</framework:middleware>
<framework:middleware>call_message_handler</framework:middleware>
<framework:middleware id="route_messages" />
<framework:middleware id="allow_no_handler" />
<framework:middleware id="call_message_handler" />
</framework:bus>
</framework:messenger>
</framework:config>

View File

@ -0,0 +1,7 @@
framework:
messenger:
buses:
command_bus:
middleware:
- foo: ['qux']
bar: ['baz']

View File

@ -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

View File

@ -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');

View File

@ -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.');
}
}

View File

@ -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);
}
}

View File

@ -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);
}