-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Messenger] Add a middleware that wraps all handlers in one Doctrine transaction. #26647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@@ -984,6 +984,12 @@ private function addMessengerSection(ArrayNodeDefinition $rootNode) | |||
->end() | |||
->end() | |||
->end() | |||
->arrayNode('doctrine') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest making it more explicit: doctrine_transaction
.
->arrayNode('doctrine') | ||
->canBeEnabled() | ||
->children() | ||
->scalarNode('entity_manager')->info('The name of the entity manager to use')->defaultValue('default')->end() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
entity_manager
=> entity_manager_name
to clarify is not a service name?
class WrapsMessageHandlingInTransaction implements MiddlewareInterface | ||
{ | ||
/** | ||
* @var ManagerRegistry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be removed.
private $managerRegistry; | ||
|
||
/** | ||
* @var string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be removed as well.
private $entityManagerName; | ||
|
||
/** | ||
* @param ManagerRegistry $managerRegistry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these as well :)
|
||
public function handle($message, callable $next) | ||
{ | ||
/** @var $entityManager EntityManagerInterface */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest to check with instanceof
the type, rather than assuming it is always going to be EntityManagerInterface
. If not, we can simply throw an InvalidArgumentException
.
|
||
$result = null; | ||
try { | ||
$entityManager->transactional( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like to me that using the beginTransaction
, commit
and rollback
methods would be much more explicit that this callback-based transaction that forces you to do this weird resetManager
later :)
Thank you for the review |
@@ -1445,6 +1445,12 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder | |||
|
|||
$container->getDefinition('messenger.sender_locator')->replaceArgument(0, $senderLocatorMapping); | |||
$container->getDefinition('messenger.asynchronous.routing.sender_locator')->replaceArgument(1, $messageToSenderIdsMapping); | |||
|
|||
if ($config['doctrine']['enabled']) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Array keys mismatch after recent rename.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
@@ -1445,6 +1445,12 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder | |||
|
|||
$container->getDefinition('messenger.sender_locator')->replaceArgument(0, $senderLocatorMapping); | |||
$container->getDefinition('messenger.asynchronous.routing.sender_locator')->replaceArgument(1, $messageToSenderIdsMapping); | |||
|
|||
if ($config['doctrine']['enabled']) { | |||
$container->getDefinition('messenger.middleware.doctrine_transaction')->replaceArgument(1, $config['doctrine']['entity_manager']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$config['doctrine']
should be $config['doctrine_transaction']
and ['entity_manager']
should be ['entity_manager']['name']
, we will need tests for this if it's not already covered
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you
@chalasr The tests currently fails due invalid xml config. Do I need to modify the |
@Nyholm yes |
I've got an error I cannot figure out:
Any help would be appreciated. |
$entityManager = $this->managerRegistry->getManager($this->entityManagerName); | ||
|
||
if (!$entityManager instanceof EntityManagerInterface) { | ||
throw new \InvalidArgumentException(sprintf('The ObjectManager with name "%s" must be an instance of EntityManagerInterface', $this->entityManagerName)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm.. But the doc block says ObjectManager..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! I see, NVM, sorry for the noise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not be sorry. Thank you for challenging this =)
->arrayNode('doctrine_transaction') | ||
->canBeEnabled() | ||
->children() | ||
->scalarNode('entity_manager_name')->info('The name of the entity manager to use')->defaultValue('default')->end() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we make it null
by default and let Doctrine determine the default name? (see)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent idea. Thanks
* | ||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> | ||
*/ | ||
class WrapsMessageHandlingInTransaction implements MiddlewareInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldnt this be moved tot the doctrine bridge instead? Otherwise i expect naming like DoctrineMessageWrappingMiddleware
or so :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about DoctrineTransactionMiddleware
?
@Nyholm could you move the middleware to the DoctrineBridge and let Doctrine decide the default manager name? |
Done |
|
||
<xsd:complexType name="messenger_doctrine_transaction"> | ||
<xsd:sequence> | ||
<xsd:element name="entity-manager-name" type="xsd:string" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this one could be an attribute rather than a child element IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please assist me here? I've had a hard time making this work. That is why the tests are failing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last thing I guess: can you move this to be an attribute? It would mean this XML would look like that instead:
<framework:config>
<framework:messenger>
<framework:doctrine-transaction entity-manager-name="foobar" />
</framework:messenger>
</framework:config>
Finally done with the config. It took me way more time than I want to admit. Anyhow. I learnt something =) Example XML: <framework:config>
<framework:messenger>
<framework:routing message-class="App\Bar">
<framework:sender service="sender.bar"></framework:sender>
<framework:sender service="sender.biz"></framework:sender>
</framework:routing>
<framework:routing message-class="App\Foo">
<framework:sender service="sender.foo"></framework:sender>
</framework:routing>
</framework:messenger>
</framework:config> Example Yaml: framework:
messenger:
routing:
'App\Bar': ['sender.bar', 'sender.biz']
'App\Foo': 'sender.foo' |
|
||
if ($config['doctrine_transaction']['enabled']) { | ||
if (!class_exists(DoctrineTransactionMiddleware::class)) { | ||
throw new LogicException('You must install symfony/doctrine-bridge to use the "DoctrineTransactionMiddleware"'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest the following:
The Doctrine transaction middleware is only available when the doctrine bridge is installed. Try running "composer require symfony/doctrine-bridge".
|
||
<xsd:complexType name="messenger_doctrine_transaction"> | ||
<xsd:sequence> | ||
<xsd:element name="entity-manager-name" type="xsd:string" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last thing I guess: can you move this to be an attribute? It would mean this XML would look like that instead:
<framework:config>
<framework:messenger>
<framework:doctrine-transaction entity-manager-name="foobar" />
</framework:messenger>
</framework:config>
<framework:config> | ||
<framework:messenger> | ||
<framework:routing message-class="App\Bar"> | ||
<framework:sender service="sender.bar"></framework:sender> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you can end them with />
instead of the full XML closing tag.
d653fb6
to
dcb07ca
Compare
Thank you for the reviews. I've updated accordingly and rebased the PR on master. |
framework: | ||
messenger: | ||
doctrine_transaction: | ||
entity_manager_name: 'foobar' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing eol :}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you
Just wondering, should this new config be wrapped in a |
Good idea. I've updated the PR |
$config['middlewares'] = array(); | ||
return $config; | ||
}) | ||
->end() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the proper way of making sure that there always is a config key named "middlewares"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it is not. We should of course use ->addDefaultsIfNotSet()
Example Yaml: framework:
messenger:
routing:
'App\Bar': ['sender.bar', 'sender.biz']
'App\Foo': 'sender.foo'
middlewares:
doctrine_transaction: ~ |
@chalasr I think we might want to do this in the future. I guess it really depends on the "demand" as it isn't that hard to register your own bus services yourself either. |
Thank you @Nyholm. |
Thank you for merging |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one has been merged too fast. I don't want to have Doctrine specific code in FrameworkBundle. It should be moved to DoctrineBundle.
$entityManager = $this->managerRegistry->getManager($this->entityManagerName); | ||
|
||
if (!$entityManager instanceof EntityManagerInterface) { | ||
throw new \InvalidArgumentException(sprintf('The ObjectManager with name "%s" must be an instance of EntityManagerInterface', $this->entityManagerName)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing dot at the end of the exception message.
->arrayNode('middlewares') | ||
->addDefaultsIfNotSet() | ||
->children() | ||
->arrayNode('doctrine_transaction') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be configured via Framework, but via DoctrineBundle. We don't have any other Doctrine config here AFAIR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fabpot are you happy about the middleware being in the bridge, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#26684 is in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think this makes sense as is. Moving it to doctrine bundle would mean configuring messenger middlewares in two different places, I can't think of a good final result.
We enable logging features for some components via framework config in the same way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @chalasr , it's only a feature flag. It does no harm having this in the fwb whereas having to configure middlewares in 2 different config keys is really bad, DX wise. WDYT?
/cc @sroze |
@fabpot OK, PR on the way. |
…on from the FrameworkBundle (sroze) This PR was merged into the 4.1-dev branch. Discussion ---------- [Messenger] Remove the Doctrine middleware configuration from the FrameworkBundle | Q | A | ------------- | --- | Branch? | master | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #26647 | License | MIT | Doc PR | ø As per @fabpot's request, remove the enabling feature of the DoctrineMiddleware from the FramworkBundle. /cc @Nyholm Commits ------- 27a8b1d Remove the Doctrine middleware configuration from the FrameworkBundle
This is greatly inspired by SimpleBus. This middleware will rollback a transaction if the message handler throws an exception.