Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ea6d861

Browse files
committed
feature #26647 [Messenger] Add a middleware that wraps all handlers in one Doctrine transaction. (Nyholm)
This PR was squashed before being merged into the 4.1-dev branch (closes #26647). Discussion ---------- [Messenger] Add a middleware that wraps all handlers in one Doctrine transaction. | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | na | License | MIT | Doc PR | coming up This is greatly inspired by SimpleBus. This middleware will rollback a transaction if the message handler throws an exception. Commits ------- 623dc5f [Messenger] Add a middleware that wraps all handlers in one Doctrine transaction.
2 parents 5b6ff93 + 623dc5f commit ea6d861

File tree

13 files changed

+222
-6
lines changed

13 files changed

+222
-6
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 Doctrine\Common\Persistence\ManagerRegistry;
15+
use Doctrine\ORM\EntityManagerInterface;
16+
use Symfony\Component\Messenger\MiddlewareInterface;
17+
18+
/**
19+
* Wraps all handlers in a single doctrine transaction.
20+
*
21+
* @author Tobias Nyholm <[email protected]>
22+
*/
23+
class DoctrineTransactionMiddleware implements MiddlewareInterface
24+
{
25+
private $managerRegistry;
26+
private $entityManagerName;
27+
28+
public function __construct(ManagerRegistry $managerRegistry, ?string $entityManagerName)
29+
{
30+
$this->managerRegistry = $managerRegistry;
31+
$this->entityManagerName = $entityManagerName;
32+
}
33+
34+
public function handle($message, callable $next)
35+
{
36+
$entityManager = $this->managerRegistry->getManager($this->entityManagerName);
37+
38+
if (!$entityManager instanceof EntityManagerInterface) {
39+
throw new \InvalidArgumentException(sprintf('The ObjectManager with name "%s" must be an instance of EntityManagerInterface', $this->entityManagerName));
40+
}
41+
42+
$entityManager->getConnection()->beginTransaction();
43+
try {
44+
$result = $next($message);
45+
$entityManager->flush();
46+
$entityManager->getConnection()->commit();
47+
} catch (\Throwable $exception) {
48+
$entityManager->getConnection()->rollBack();
49+
50+
throw $exception;
51+
}
52+
53+
return $result;
54+
}
55+
}

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

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -969,13 +969,27 @@ private function addMessengerSection(ArrayNodeDefinition $rootNode)
969969
->children()
970970
->arrayNode('routing')
971971
->useAttributeAsKey('message_class')
972+
->beforeNormalization()
973+
->always()
974+
->then(function ($config) {
975+
$newConfig = array();
976+
foreach ($config as $k => $v) {
977+
if (!is_int($k)) {
978+
$newConfig[$k] = array('senders' => is_array($v) ? array_values($v) : array($v));
979+
} else {
980+
$newConfig[$v['message-class']]['senders'] = array_map(
981+
function ($a) {
982+
return is_string($a) ? $a : $a['service'];
983+
},
984+
array_values($v['sender'])
985+
);
986+
}
987+
}
988+
989+
return $newConfig;
990+
})
991+
->end()
972992
->prototype('array')
973-
->beforeNormalization()
974-
->ifString()
975-
->then(function ($v) {
976-
return array('senders' => array($v));
977-
})
978-
->end()
979993
->children()
980994
->arrayNode('senders')
981995
->requiresAtLeastOneElement()
@@ -984,6 +998,16 @@ private function addMessengerSection(ArrayNodeDefinition $rootNode)
984998
->end()
985999
->end()
9861000
->end()
1001+
->arrayNode('middlewares')
1002+
->addDefaultsIfNotSet()
1003+
->children()
1004+
->arrayNode('doctrine_transaction')
1005+
->canBeEnabled()
1006+
->children()
1007+
->scalarNode('entity_manager_name')->info('The name of the entity manager to use')->defaultNull()->end()
1008+
->end()
1009+
->end()
1010+
->end()
9871011
->end()
9881012
->end()
9891013
->end()

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

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

1414
use Doctrine\Common\Annotations\Reader;
1515
use Doctrine\Common\Annotations\AnnotationRegistry;
16+
use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware;
1617
use Symfony\Bridge\Monolog\Processor\DebugProcessor;
1718
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1819
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
@@ -1445,6 +1446,15 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
14451446

14461447
$container->getDefinition('messenger.sender_locator')->replaceArgument(0, $senderLocatorMapping);
14471448
$container->getDefinition('messenger.asynchronous.routing.sender_locator')->replaceArgument(1, $messageToSenderIdsMapping);
1449+
1450+
if ($config['middlewares']['doctrine_transaction']['enabled']) {
1451+
if (!class_exists(DoctrineTransactionMiddleware::class)) {
1452+
throw new LogicException('The Doctrine transaction middleware is only available when the doctrine bridge is installed. Try running "composer require symfony/doctrine-bridge".');
1453+
}
1454+
$container->getDefinition('messenger.middleware.doctrine_transaction')->replaceArgument(1, $config['middlewares']['doctrine_transaction']['entity_manager_name']);
1455+
} else {
1456+
$container->removeDefinition('messenger.middleware.doctrine_transaction');
1457+
}
14481458
}
14491459

14501460
private function registerCacheConfiguration(array $config, ContainerBuilder $container)

src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@
2525
<tag name="message_bus_middleware" priority="-10" />
2626
</service>
2727

28+
<service id="messenger.middleware.doctrine_transaction" class="Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware">
29+
<argument type="service" id="doctrine" />
30+
<argument /> <!-- Entity manager name -->
31+
32+
<tag name="message_bus_middleware" priority="9" />
33+
</service>
34+
2835
<!-- Asynchronous -->
2936
<service id="messenger.asynchronous.routing.sender_locator" class="Symfony\Component\Messenger\Asynchronous\Routing\SenderLocator">
3037
<argument type="service" id="messenger.sender_locator" />

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<xsd:element name="cache" type="cache" minOccurs="0" maxOccurs="1" />
3131
<xsd:element name="workflow" type="workflow" minOccurs="0" maxOccurs="unbounded" />
3232
<xsd:element name="lock" type="lock" minOccurs="0" maxOccurs="1" />
33+
<xsd:element name="messenger" type="messenger" minOccurs="0" maxOccurs="1" />
3334
</xsd:choice>
3435

3536
<xsd:attribute name="http-method-override" type="xsd:boolean" />
@@ -342,4 +343,33 @@
342343
</xsd:extension>
343344
</xsd:simpleContent>
344345
</xsd:complexType>
346+
347+
<xsd:complexType name="messenger">
348+
<xsd:sequence>
349+
<xsd:element name="routing" type="messenger_routing" minOccurs="0" maxOccurs="unbounded" />
350+
<xsd:element name="middlewares" type="messenger_middleware" minOccurs="0" maxOccurs="unbounded" />
351+
</xsd:sequence>
352+
</xsd:complexType>
353+
354+
<xsd:complexType name="messenger_routing">
355+
<xsd:choice minOccurs="0" maxOccurs="unbounded">
356+
<xsd:element name="sender" type="messenger_routing_sender" />
357+
</xsd:choice>
358+
<xsd:attribute name="message-class" type="xsd:string" use="required"/>
359+
</xsd:complexType>
360+
361+
<xsd:complexType name="messenger_routing_sender">
362+
<xsd:attribute name="service" type="xsd:string" use="required"/>
363+
</xsd:complexType>
364+
365+
<xsd:complexType name="messenger_middleware">
366+
<xsd:sequence>
367+
<xsd:element name="doctrine-transaction" type="messenger_doctrine_transaction" minOccurs="0" maxOccurs="1" />
368+
</xsd:sequence>
369+
</xsd:complexType>
370+
371+
<xsd:complexType name="messenger_doctrine_transaction">
372+
<xsd:attribute name="entity-manager-name" type="xsd:string" />
373+
<xsd:attribute name="enabled" type="xsd:boolean" />
374+
</xsd:complexType>
345375
</xsd:schema>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
253253
'messenger' => array(
254254
'enabled' => !class_exists(FullStack::class) && class_exists(MessageBusInterface::class),
255255
'routing' => array(),
256+
'middlewares' => array(
257+
'doctrine_transaction' => array(
258+
'enabled' => false,
259+
'entity_manager_name' => null,
260+
),
261+
),
256262
),
257263
);
258264
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
$container->loadFromExtension('framework', array(
4+
'messenger' => array(
5+
'routing' => array(
6+
'App\Bar' => array('sender.bar', 'sender.biz'),
7+
'App\Foo' => 'sender.foo',
8+
),
9+
),
10+
));
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
$container->loadFromExtension('framework', array(
4+
'messenger' => array(
5+
'middlewares' => array(
6+
'doctrine_transaction' => array(
7+
'entity_manager_name' => 'foobar',
8+
),
9+
),
10+
),
11+
));
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<container xmlns="http://symfony.com/schema/dic/services"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:framework="http://symfony.com/schema/dic/symfony"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
6+
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
7+
8+
<framework:config>
9+
<framework:messenger>
10+
<framework:routing message-class="App\Bar">
11+
<framework:sender service="sender.bar" />
12+
<framework:sender service="sender.biz" />
13+
</framework:routing>
14+
<framework:routing message-class="App\Foo">
15+
<framework:sender service="sender.foo" />
16+
</framework:routing>
17+
</framework:messenger>
18+
</framework:config>
19+
</container>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<container xmlns="http://symfony.com/schema/dic/services"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:framework="http://symfony.com/schema/dic/symfony"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
6+
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
7+
8+
<framework:config>
9+
<framework:messenger>
10+
<framework:middlewares>
11+
<framework:doctrine-transaction entity-manager-name="foobar" />
12+
</framework:middlewares>
13+
</framework:messenger>
14+
</framework:config>
15+
</container>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
framework:
2+
messenger:
3+
routing:
4+
'App\Bar': ['sender.bar', 'sender.biz']
5+
'App\Foo': 'sender.foo'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
framework:
2+
messenger:
3+
middlewares:
4+
doctrine_transaction:
5+
entity_manager_name: 'foobar'

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection;
1313

1414
use Doctrine\Common\Annotations\Annotation;
15+
use Symfony\Bridge\Doctrine\ContainerAwareEventManager;
1516
use Symfony\Bundle\FullStack;
1617
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
1718
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddAnnotationsCachedReaderPass;
@@ -489,6 +490,24 @@ public function testWebLink()
489490
$this->assertTrue($container->hasDefinition('web_link.add_link_header_listener'));
490491
}
491492

493+
public function testMessenger()
494+
{
495+
$container = $this->createContainerFromFile('messenger');
496+
$this->assertFalse($container->hasDefinition('messenger.middleware.doctrine_transaction'));
497+
}
498+
499+
public function testMessengerDoctrine()
500+
{
501+
if (!class_exists(ContainerAwareEventManager::class)) {
502+
self::markTestSkipped('Skipping tests since Doctrine bridge is not installed');
503+
}
504+
505+
$container = $this->createContainerFromFile('messenger_doctrine');
506+
$this->assertTrue($container->hasDefinition('messenger.middleware.doctrine_transaction'));
507+
$def = $container->getDefinition('messenger.middleware.doctrine_transaction');
508+
$this->assertEquals('foobar', $def->getArgument(1));
509+
}
510+
492511
public function testTranslator()
493512
{
494513
$container = $this->createContainerFromFile('full');

0 commit comments

Comments
 (0)