8000 feature #27128 [Messenger] Middleware factories support in config (og… · symfony/symfony@f59ce97 · GitHub
[go: up one dir, main page]

Skip to content

Commit f59ce97

Browse files
committed
feature #27128 [Messenger] Middleware factories support in config (ogizanagi)
This PR was squashed before being merged into the 4.1 branch (closes #27128). Discussion ---------- [Messenger] Middleware factories support in config | Q | A | ------------- | --- | Branch? | master <!-- see below --> | Bug fix? | no | New feature? | yes <!-- don't forget to update src/**/CHANGELOG.md files --> | BC breaks? | no <!-- see https://symfony.com/bc --> | Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tests pass? | yes <!-- please add some, will be required by reviewers --> | Fixed tickets | N/A <!-- #-prefixed issue number(s), if any --> | License | MIT | Doc PR | todo Following #26864, this would allow to configure easily the middlewares by using an abstract factory definition to which are provided simple arguments (just scalars, no services references). For instance, here is how the DoctrineBundle would benefit from such a feature (also solving the wiring of the `DoctrineTransactionMiddleware` reverted in #26684): ```yaml framework: messenger: buses: default: middleware: - logger - doctrine_transaction_middleware: ['entity_manager_name'] ``` where `doctrine_transaction_middleware` would be an abstract factory definition provided by the doctrine bundle: ```yml services: doctrine.orm.messenger.middleware_factory.transaction: class: Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddlewareFactory arguments: ['@doctrine'] doctrine_transaction_middleware: class: Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware factory: ['@doctrine.orm.messenger.middleware_factory.transaction', 'createMiddleware'] abstract: true # the default arguments to use when none provided from config. # i.e: # middlewares: # - doctrine_transaction_middleware: ~ arguments: ['default'] ``` and is interpreted as: ```yml buses: default: middleware: - id: logger arguments: { } - id: doctrine_transaction_middleware arguments: - entity_manager_name default_middleware: true ``` --- <details> <summary>Here is the whole config reference with these changes: </summary> ```yaml # Messenger configuration messenger: enabled: true routing: # Prototype message_class: senders: [] serializer: enabled: true format: json context: # Prototype name: ~ encoder: messenger.transport.serializer decoder: messenger.transport.serializer adapters: # Prototype name: dsn: ~ options: [] default_bus: null buses: # Prototype name: default_middleware: true middleware: # Prototype - id: ~ # Required arguments: [] ``` </details> Commits ------- f5ef421 [Messenger] Middleware factories support in config
2 parents 5f0e2d6 + f5ef421 commit f59ce97

File tree

13 files changed

+222
-24
lines changed

13 files changed

+222
-24
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Messenger;
13+
14+
use Symfony\Bridge\Doctrine\ManagerRegistry;
15+
16+
/**
17+
* Create a Doctrine ORM transaction middleware to be used in a message bus from an entity manager name.
18+
*
19+
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
20+
*
21+
* @experimental in 4.1
22+
* @final
23+
*/
24+
class DoctrineTransactionMiddlewareFactory
25+
{
26+
private $managerRegistry;
27+
28+
public function __construct(ManagerRegistry $managerRegistry)
29+
{
30+
$this->managerRegistry = $managerRegistry;
31+
}
32+
33+
public function createMiddleware(string $managerName): DoctrineTransactionMiddleware
34+
{
35+
return new DoctrineTransactionMiddleware($this->managerRegistry, $managerName);
36+
}
37+
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1061,7 +1061,36 @@ function ($a) {
10611061
})
10621062
->end()
10631063
->defaultValue(array())
1064-
->prototype('scalar')->end()
1064+
->prototype('array')
1065+
->beforeNormalization()
1066+
->always()
1067+
->then(function ($middleware): array {
1068+
if (!\is_array($middleware)) {
1069+
return array('id' => $middleware);
1070+
}
1071+
if (isset($middleware['id'])) {
1072+
return $middleware;
1073+
}
1074+
if (\count($middleware) > 1) {
1075+
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)));
1076+
}
1077+
1078+
return array(
1079+
'id' => key($middleware),
1080+
'arguments' => current($middleware),
1081+
);
1082+
})
1083+
->end()
1084+
->fixXmlConfig('argument')
1085+
->children()
1086+
->scalarNode('id')->isRequired()->cannotBeEmpty()->end()
1087+
->arrayNode('arguments')
1088+
->normalizeKeys(false)
1089+
->defaultValue(array())
1090+
->prototype('variable')
1091+
->end()
1092+
->end()
1093+
->end()
10651094
->end()
10661095
->end()
10671096
->end()

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,12 +1468,17 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
14681468
$config['default_bus'] = key($config['buses']);
14691469
}
14701470

1471-
$defaultMiddleware = array('before' => array('logging'), 'after' => array('route_messages', 'call_message_handler'));
1471+
$defaultMiddleware = array(
1472+
'before' => array(array('id' => 'logging')),
1473+
'after' => array(array('id' => 'route_messages'), array('id' => 'call_message_handler')),
1474+
);
14721475
foreach ($config['buses'] as $busId => $bus) {
14731476
$middleware = $bus['default_middleware'] ? array_merge($defaultMiddleware['before'], $bus['middleware'], $defaultMiddleware['after']) : $bus['middleware'];
14741477

1475-
if (!$validationConfig['enabled'] && \in_array('messenger.middleware.validation', $middleware, true)) {
1476-
throw new LogicException('The Validation middleware is only available when the Validator component is installed and enabled. Try running "composer require symfony/validator".');
1478+
foreach ($middleware as $middlewareItem) {
1479+
if (!$validationConfig['enabled'] && 'messenger.middleware.validation' === $middlewareItem['id']) {
1480+
throw new LogicException('The Validation middleware is only available when the Validator component is installed and enabled. Try running "composer require symfony/validator".');
1481+
}
14771482
}
14781483

14791484
$container->setParameter($busId.'.middleware', $middleware);

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,9 +391,16 @@
391391

392392
<xsd:complexType name="messenger_bus">
393393
<xsd:sequence>
394-
<xsd:element name="middleware" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
394+
<xsd:element name="middleware" type="messenger_middleware" minOccurs="0" maxOccurs="unbounded" />
395395
</xsd:sequence>
396396
<xsd:attribute name="name" type="xsd:string" use="required"/>
397397
<xsd:attribute name="default-middleware" type="xsd:boolean"/>
398398
</xsd:complexType>
399+
400+
<xsd:complexType name="messenger_middleware">
401+
<xsd:sequence>
402+
<xsd:element name="argument" type="xsd:anyType" minOccurs="0" maxOccurs="unbounded" />
403+
</xsd:sequence>
404+
<xsd:attribute name="id" type="xsd:string" use="required"/>
405+
</xsd:complexType>
399406
</xsd:schema>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
$container->loadFromExtension('framework', array(
4+
'messenger' => array(
5+
'buses' => array(
6+
'command_bus' => array(
7+
'middleware' => array(
8+
array(
9+
'foo' => array('qux'),
10+
'bar' => array('baz'),
11+
),
12+
),
13+
),
14+
),
15+
),
16+
));

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_multiple_buses.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
'messenger.bus.commands' => null,
88
'messenger.bus.events' => array(
99
'middleware' => array(
10+
array('with_factory' => array('foo', true, array('bar' => 'baz'))),
1011
'allow_no_handler',
1112
),
1213
),

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_multiple_buses.xml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,19 @@
99
<framework:messenger default-bus="messenger.bus.commands">
1010
<framework:bus name="messenger.bus.commands" />
1111
<framework:bus name="messenger.bus.events">
12-
<framework:middleware>allow_no_handler</framework:middleware>
12+
<framework:middleware id="with_factory">
13+
<framework:argument>foo</framework:argument>
14+
<framework:argument>true</framework:argument>
15+
<framework:argument>
16+
<framework:bar>baz</framework:bar>
17+
</framework:argument>
18+
</framework:middleware>
19+
<framework:middleware id="allow_no_handler" />
1320
</framework:bus>
1421
<framework:bus name="messenger.bus.queries" default-middleware="false">
15-
<framework:middleware>route_messages</framework:middleware>
16-
<framework:middleware>allow_no_handler</framework:middleware>
17-
<framework:middleware>call_message_handler</framework:middleware>
22+
<framework:middleware id="route_messages" />
23+
<framework:middleware id="allow_no_handler" />
24+
<framework:middleware id="call_message_handler" />
1825
</framework:bus>
1926
</framework:messenger>
2027
</framework:config>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
framework:
2+
messenger:
3+
buses:
4+
command_bus:
5+
middleware:
6+
- foo: ['qux']
7+
bar: ['baz']

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_multiple_buses.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ framework:
55
messenger.bus.commands: ~
66
messenger.bus.events:
77
middleware:
8+
- with_factory: [foo, true, { bar: baz }]
89
- "allow_no_handler"
910
messenger.bus.queries:
1011
default_middleware: false

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -604,18 +604,41 @@ public function testMessengerWithMultipleBuses()
604604

605605
$this->assertTrue($container->has('messenger.bus.commands'));
606606
$this->assertSame(array(), $container->getDefinition('messenger.bus.commands')->getArgument(0));
607-
$this->assertEquals(array('logging', 'route_messages', 'call_message_handler'), $container->getParameter('messenger.bus.commands.middleware'));
607+
$this->assertEquals(array(
608+
array('id' => 'logging'),
609+
array('id' => 'route_messages'),
610+
array('id' => 'call_message_handler'),
611+
), $container->getParameter('messenger.bus.commands.middleware'));
608612
$this->assertTrue($container->has('messenger.bus.events'));
609613
$this->assertSame(array(), $container->getDefinition('messenger.bus.events')->getArgument(0));
610-
$this->assertEquals(array('logging', 'allow_no_handler', 'route_messages', 'call_message_handler'), $container->getParameter('messenger.bus.events.middleware'));
614+
$this->assertEquals(array(
615+
array('id' => 'logging'),
616+
array('id' => 'with_factory', 'arguments' => array('foo', true, array('bar' => 'baz'))),
617+
array('id' => 'allow_no_handler', 'arguments' => array()),
618+
array('id' => 'route_messages'),
619+
array('id' => 'call_message_handler'),
620+
), $container->getParameter('messenger.bus.events.middleware'));
611621
$this->assertTrue($container->has('messenger.bus.queries'));
612622
$this->assertSame(array(), $container->getDefinition('messenger.bus.queries')->getArgument(0));
613-
$this->assertEquals(array('route_messages', 'allow_no_handler', 'call_message_handler'), $container->getParameter('messenger.bus.queries.middleware'));
623+
$this->assertEquals(array(
624+
array('id' => 'route_messages', 'arguments' => array()),
625+
array('id' => 'allow_no_handler', 'arguments' => array()),
626+
array('id' => 'call_message_handler', 'arguments' => array()),
627+
), $container->getParameter('messenger.bus.queries.middleware'));
614628

615629
$this->assertTrue($container->hasAlias('message_bus'));
616630
$this->assertSame('messenger.bus.commands', (string) $container->getAlias('message_bus'));
617631
}
618632

633+
/**
634+
* @expectedException \InvalidArgumentException
635+
* @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"]}"
636+
*/
637+
public function testMessengerMiddlewareFactoryErroneousFormat()
638+
{
639+
$this->createContainerFromFile('messenger_middleware_factory_erroneous_format');
640+
}
641+
619642
public function testTranslator()
620643
{
621644
$container = $this->createContainerFromFile('full');

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,9 @@ public function testAssetsHelperIsRemovedWhenPhpTemplatingEngineIsEnabledAndAsse
2727
{
2828
$this->markTestSkipped('The assets key cannot be set to false using the XML configuration format.');
2929
}
30+
31+
public function testMessengerMiddlewareFactoryErroneousFormat()
32+
{
33+
$this->markTestSkipped('XML configuration will not allow eeroneous format.');
34+
}
3035
}

src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -248,24 +248,37 @@ private function registerBusToCollector(ContainerBuilder $container, string $bus
248248
$container->getDefinition('messenger.data_collector')->addMethodCall('registerBus', array($busId, new Reference($tracedBusId)));
249249
}
250250

251-
private function registerBusMiddleware(ContainerBuilder $container, string $busId, array $middleware)
251+
private function registerBusMiddleware(ContainerBuilder $container, string $busId, array $middlewareCollection)
252252
{
253-
$container->getDefinition($busId)->replaceArgument(0, array_map(function (string $name) use ($container, $busId) {
254-
if (!$container->has($messengerMiddlewareId = 'messenger.middleware.'.$name)) {
255-
$messengerMiddlewareId = $name;
253+
$middlewareReferences = array();
254+
foreach ($middlewareCollection as $middlewareItem) {
255+
$id = $middlewareItem['id'];
256+
$arguments = $middlewareItem['arguments'] ?? array();
257+
if (!$container->has($messengerMiddlewareId = 'messenger.middleware.'.$id)) {
258+
$messengerMiddlewareId = $id;
256259
}
257260

258261
if (!$container->has($messengerMiddlewareId)) {
259-
throw new RuntimeException(sprintf('Invalid middleware "%s": define such service to be able to use it.', $name));
262+
throw new RuntimeException(sprintf('Invalid middleware "%s": define such service to be able to use it.', $id));
260263
}
261264

262-
if ($container->getDefinition($messengerMiddlewareId)->isAbstract()) {
265+
if (($definition = $container->findDefinition($messengerMiddlewareId))->isAbstract()) {
263266
$childDefinition = new ChildDefinition($messengerMiddlewareId);
267+
$count = \count($definition->getArguments());
268+
foreach (array_values($arguments ?? array()) as $key => $argument) {
269+
// Parent definition can provide default arguments.
270+
// Replace each explicitly or add if not set:
271+
$key < $count ? $childDefinition->replaceArgument($key, $argument) : $childDefinition->addArgument($argument);
272+
}
264273

265-
$container->setDefinition($messengerMiddlewareId = $busId.'.middleware.'.$name, $childDefinition);
274+
$container->setDefinition($messengerMiddlewareId = $busId.'.middleware.'.$id, $childDefinition);
275+
} elseif ($arguments) {
276+
throw new RuntimeException(sprintf('Invalid middleware factory "%s": a middleware factory must be an abstract definition.', $id));
266277
}
267278

268-
return new Reference($messengerMiddlewareId);
269-
}, $middleware));
279+
$middlewareReferences[] = new Reference($messengerMiddlewareId);
280+
}
281+
282+
$container->getDefinition($busId)->replaceArgument(0, $middlewareReferences);
270283
}
271284
}

src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
16+
use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
1718
use Symfony\Component\DependencyInjection\Reference;
1819
use Symfony\Component\DependencyInjection\ServiceLocator;
@@ -359,14 +360,42 @@ public function testRegistersMiddlewareFromServices()
359360
$container = $this->getContainerBuilder();
360361
$container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
361362
$container->register('messenger.middleware.allow_no_handler', AllowNoHandlerMiddleware::class)->setAbstract(true);
363+
$container->register('middleware_with_factory', UselessMiddleware::class)->addArgument('some_default')->setAbstract(true);
364+
$container->register('middleware_with_factory_using_default', UselessMiddleware::class)->addArgument('some_default')->setAbstract(true);
362365
$container->register(UselessMiddleware::class, UselessMiddleware::class);
363366

364-
$container->setParameter($middlewareParameter = $fooBusId.'.middleware', array(UselessMiddleware::class, 'allow_no_handler'));
367+
$container->setParameter($middlewareParameter = $fooBusId.'.middleware', array(
368+
array('id' => UselessMiddleware::class),
369+
array('id' => 'middleware_with_factory', 'arguments' => array('foo', 'bar')),
370+
array('id' => 'middleware_with_factory_using_default'),
371+
array('id' => 'allow_no_handler'),
372+
));
365373

366374
(new MessengerPass())->process($container);
375+
(new ResolveChildDefinitionsPass())->process($container);
367376

368377
$this->assertTrue($container->hasDefinition($childMiddlewareId = $fooBusId.'.middleware.allow_no_handler'));
369-
$this->assertEquals(array(new Reference(UselessMiddleware::class), new Reference($childMiddlewareId)), $container->getDefinition($fooBusId)->getArgument(0));
378+
379+
$this->assertTrue($container->hasDefinition($factoryChildMiddlewareId = $fooBusId.'.middleware.middleware_with_factory'));
380+
$this->assertEquals(
381+
array('foo', 'bar'),
382+
$container->getDefinition($factoryChildMiddlewareId)->getArguments(),
383+
'parent default argument is overridden, and next ones appended'
384+
);
385+
386+
$this->assertTrue($container->hasDefinition($factoryWithDefaultChildMiddlewareId = $fooBusId.'.middleware.middleware_with_factory_using_default'));
387+
$this->assertEquals(
388+
array('some_default'),
389+
$container->getDefinition($factoryWithDefaultChildMiddlewareId)->getArguments(),
390+
'parent default argument is used'
391+
);
392+
393+
$this->assertEquals(array(
394+
new Reference(UselessMiddleware::class),
395+
new Reference($factoryChildMiddlewareId),
396+
new Reference($factoryWithDefaultChildMiddlewareId),
397+
new Reference($childMiddlewareId),
398+
), $container->getDefinition($fooBusId)->getArgument(0));
370399
$this->assertFalse($container->hasParameter($middlewareParameter));
371400
}
372401

@@ -378,7 +407,25 @@ public function testCannotRegistersAnUndefinedMiddleware()
378407
{
379408
$container = $this->getContainerBuilder();
380409
$container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
381-
$container->setParameter($middlewareParameter = $fooBusId.'.middleware', array('not_defined_middleware'));
410+
$container->setParameter($middlewareParameter = $fooBusId.'.middleware', array(
411+
array('id' => 'not_defined_middleware', 'arguments' => array()),
412+
));
413+
414+
(new MessengerPass())->process($container);
415+
}
416+
417+
/**
418+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
419+
* @expectedExceptionMessage Invalid middleware factory "not_an_abstract_definition": a middleware factory must be an abstract definition.
420+
*/
421+
public function testMiddlewareFactoryDefinitionMustBeAbstract()
422+
{
423+
$container = $this->getContainerBuilder();
424+
$container->register('not_an_abstract_definition', UselessMiddleware::class);
425+
$container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus', array('name' => 'foo'));
426+
$container->setParameter($middlewareParameter = $fooBusId.'.middleware', array(
427+
array('id' => 'not_an_abstract_definition', 'arguments' => array('foo')),
428+
));
382429

383430
(new MessengerPass())->process($container);
384431
}

0 commit comments

Comments
 (0)
0