Emit postFlush event after transaction commit. #5906

Open
opened 2026-01-22 15:21:39 +01:00 by admin · 19 comments
Owner

Originally created by @d-ph on GitHub (Mar 1, 2018).

Hello,

Would it be possible to emit postFlush event (or just call EntityManager::flush(), which would do it by itself) just after db transaction commit in EntityManager::transactional(), please?

In other words, something like the following:

   /**
    * {@inheritDoc}
    */
   public function transactional(callable $func)
   {
       $this->conn->beginTransaction();
       try {
           $return = $func($this);
           $this->flush(); // first flush
           $this->conn->commit();

           // call a noop flush again to trigger the onFlush and postFlush events
           $this->flush(); // second flush; this line is new
           
           return $return;
       } catch (\Throwable $e) {
           $this->close();
           $this->conn->rollBack();
           throw $e;
       }
   }

The use case is this: I have a postFlush listener that:

  1. Makes sure the flush didn't happen in a db transaction by checking Connection::isTransactionActive(). During the first flush in transactional(), my listener wouldn't do its job because the flush happened in a db transaction.
  2. Once sure that the flush didn't happen in a db transaction, dispatch a queue task to the queue server, which requires all in-memory state to be completely flushed to the database, so that it's accessible from another OS process or php script.

I currently work-around the current lack of the second flush by manually flushing after transactional(). I don't, however, like how I (the end user) should be concerned with this vendor implementation detail. I'd much more appreciate this detail stay in the vendor code (i.e. Doctrine's)

Thank you,
@d-ph

Originally created by @d-ph on GitHub (Mar 1, 2018). Hello, Would it be possible to emit postFlush event (or just call `EntityManager::flush()`, which would do it by itself) just after db transaction commit in `EntityManager::transactional()`, please? In other words, something like the [following](https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/EntityManager.php#L223): ```php /** * {@inheritDoc} */ public function transactional(callable $func) { $this->conn->beginTransaction(); try { $return = $func($this); $this->flush(); // first flush $this->conn->commit(); // call a noop flush again to trigger the onFlush and postFlush events $this->flush(); // second flush; this line is new return $return; } catch (\Throwable $e) { $this->close(); $this->conn->rollBack(); throw $e; } } ``` The use case is this: I have a postFlush listener that: 1. Makes sure the flush didn't happen in a db transaction by checking `Connection::isTransactionActive()`. During the first flush in `transactional()`, my listener wouldn't do its job because the flush happened in a db transaction. 2. Once sure that the flush didn't happen in a db transaction, dispatch a queue task to the queue server, which requires all in-memory state to be completely flushed to the database, so that it's accessible from another OS process or php script. I currently work-around the current lack of the second flush by manually flushing after `transactional()`. I don't, however, like how I (the end user) should be concerned with this vendor implementation detail. I'd much more appreciate this detail stay in the vendor code (i.e. Doctrine's) Thank you, @d-ph
Author
Owner

@Ocramius commented on GitHub (Mar 1, 2018):

my listener wouldn't do its job because the flush happened in a db transaction.

Something has gone horribly wrong: executing a flush() in a transaction or outside of it shouldn't make any difference.

@Ocramius commented on GitHub (Mar 1, 2018): > my listener wouldn't do its job because the flush happened in a db transaction. Something has gone horribly wrong: executing a `flush()` in a transaction or outside of it shouldn't make any difference.
Author
Owner

@d-ph commented on GitHub (Mar 1, 2018):

Hi,

Thanks for a light-speed response.

Are you sure it shouldn't make any difference? The result of the flush before the commit is visible only to the current db transaction / current pdo connection. It's not visible to another OS process (which has its own db connection open). Only after the Connection::commit(), everything that happened in the transaction becomes visible to the "outside world" (assuming the commit succeeds, which is another topic, but irrelevant to this discussion)

@d-ph commented on GitHub (Mar 1, 2018): Hi, Thanks for a light-speed response. Are you sure it shouldn't make any difference? The result of the flush before the [commit](https://github.com/doctrine/dbal/blob/0e99c343ef364ee0ab2f0d5aceb18b8768045037/lib/Doctrine/DBAL/Connection.php#L1289) is visible only to the current db transaction / current pdo connection. It's not visible to another OS process (which has its own db connection open). Only after the `Connection::commit()`, everything that happened in the transaction becomes visible to the "outside world" (assuming the commit succeeds, which is another topic, but irrelevant to this discussion)
Author
Owner

@Ocramius commented on GitHub (Mar 1, 2018):

It's not visible to another OS process (which has its own db connection open).

Depends on the transaction isolation level that you are running. SERIALIZED transactions aren't visible to other transactions (by design), so if you do want to read that data (at the risk of having ghost reads), you may configure your transaction isolation level differently.

@Ocramius commented on GitHub (Mar 1, 2018): > It's not visible to another OS process (which has its own db connection open). Depends on the transaction isolation level that you are running. `SERIALIZED` transactions aren't visible to other transactions (by design), so if you do want to read that data (at the risk of having ghost reads), you may configure your transaction isolation level differently.
Author
Owner

@d-ph commented on GitHub (Mar 1, 2018):

Ok, let me think about it and test different serialisation levels. Maybe I'm missing something here. I got to get going now, so I'll respond in 24h.

@d-ph commented on GitHub (Mar 1, 2018): Ok, let me think about it and test different serialisation levels. Maybe I'm missing something here. I got to get going now, so I'll respond in 24h.
Author
Owner

@d-ph commented on GitHub (Mar 1, 2018):

Hi again,

So I refreshed my knowledge about db transaction isolation levels and also ran some tests on my own to see when new records are actually visible in the database, and came to conclusion that everything I've said so far is correct.

Transaction isolation levels are for what data the queries inside the transaction see, not the other way around. I.e. it doesn't matter what the transaction isolation level is -- what happens in a transaction, stays in the transaction until it's committed. And by the way, I was testing with the default, least restrictive transaction isolation level in postgres: "read committed". The bottom line here is: other db connections from other OS processes won't see the data my script is putting in the db until my script commits the transaction. Which again, happens after the first flush, as opposed to: during the first flush, which makes my postFlush listener ignore it and wait further for "the final flush".

You said that flush()ing inside or outside of db transaction shouldn't make any difference. It does. Here's a quick rundown of what happens, when EntityManager::transactional() is used:

  1. $this->conn->beginTransaction();. At this point private $_transactionNestingLevel is 1.
  2. The first $this->flush();.
    2.1. $conn->beginTransaction();. At this point private $_transactionNestingLevel is 2.
    2.2. $conn->commit();. At this point private $_transactionNestingLevel is set back to 1. Nothing is visible to the outside world yet.
  3. $this->conn->commit();. At this point private $_transactionNestingLevel is set back to 0 and a SQL COMMIT; is run, which makes everything that happened in the transaction visible to the outside world.
  4. ... due to the missing second flush(), postFlush listeners that do stuff on "the final flush" won't have the opportunity to do their job.

Bear in mind that all I care about is the postFlush event, not the second flush per se. But since running a second (noop) flush triggers the postFlush event, I'm ok with that solution.

@d-ph commented on GitHub (Mar 1, 2018): Hi again, So I refreshed my knowledge about [db transaction isolation levels](https://www.postgresql.org/docs/9.6/static/transaction-iso.html) and also ran some tests on my own to see when new records are actually visible in the database, and came to conclusion that everything I've said so far is correct. Transaction isolation levels are for what data the queries inside the transaction see, not the other way around. I.e. it doesn't matter what the transaction isolation level is -- what happens in a transaction, stays in the transaction until it's committed. And by the way, I was testing with the default, least restrictive transaction isolation level in postgres: "read committed". The bottom line here is: other db connections from other OS processes won't see the data my script is putting in the db until my script commits the transaction. Which again, happens after the first flush, as opposed to: during the first flush, which makes my postFlush listener ignore it and wait further for "the final flush". You said that `flush()`ing inside or outside of db transaction shouldn't make any difference. It does. Here's a quick rundown of what happens, when `EntityManager::transactional()` is used: 1. [$this->conn->beginTransaction();](https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/EntityManager.php#L225). At this point [private $_transactionNestingLevel](https://github.com/doctrine/dbal/blob/0e99c343ef364ee0ab2f0d5aceb18b8768045037/lib/Doctrine/DBAL/Connection.php#L142) is `1`. 2. The first [$this->flush();](https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/EntityManager.php#L225). 2.1. [$conn->beginTransaction();](https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/UnitOfWork.php#L370). At this point `private $_transactionNestingLevel` is 2. 2.2. [$conn->commit();](https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/UnitOfWork.php#L411). At this point `private $_transactionNestingLevel` is set back to 1. Nothing is visible to the outside world yet. 3. [$this->conn->commit();](https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/EntityManager.php#L231). At this point `private $_transactionNestingLevel` is set back to 0 and a SQL `COMMIT;` is run, which makes everything that happened in the transaction visible to the outside world. 4. ... due to the missing second `flush()`, postFlush listeners that do stuff on "the final flush" won't have the opportunity to do their job. Bear in mind that all I care about is the postFlush event, not the second flush per se. But since running a second (noop) flush triggers the postFlush event, I'm ok with that solution.
Author
Owner

@Ocramius commented on GitHub (Mar 2, 2018):

I don't really understand what you are trying to achieve here: all data
within the current and previous nested flush-related transaction is
readable after or at the end of a flush operation, so unless your listeners
are running on a separate process, you shouldn't need any tweaking at all.

On 1 Mar 2018 22:43, "d-ph" notifications@github.com wrote:

Hi again,

So I refreshed my knowledge about db transaction isolation levels
https://www.postgresql.org/docs/9.6/static/transaction-iso.html and
also ran some tests on my own to see when new records are actually visible
in the database, and came to conclusion that everything I've said so far is
correct.

Transaction isolation levels are for what data the queries inside the
transaction see, not the other way around. I.e. it doesn't matter what the
transaction isolation level is -- what happens in a transaction, stays in
the transaction until it's committed. And by the way, I was testing with
the default, least restrictive transaction isolation level in postgres:
"read committed". The bottom line here is: other db connections from other
OS processes won't see the data my script is putting in the db until my
script commits the transaction. Which again, happens after the first flush,
as opposed to: during the first flush, which makes my postFlush listener
ignore it and wait further for "the final flush".

You said that flush()ing inside or outside of db transaction shouldn't
make any difference. It does. Here's a quick rundown of what happens, when
EntityManager::transactional() is used:

  1. $this->conn->beginTransaction();
    https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/EntityManager.php#L225.
    At this point private $_transactionNestingLevel
    https://github.com/doctrine/dbal/blob/0e99c343ef364ee0ab2f0d5aceb18b8768045037/lib/Doctrine/DBAL/Connection.php#L142
    is 1.
  2. The first $this->flush();
    https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/EntityManager.php#L225
    .
    2.1. $conn->beginTransaction();
    https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/UnitOfWork.php#L370.
    At this point private $_transactionNestingLevel is 2.
    2.2. $conn->commit();
    https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/UnitOfWork.php#L411.
    At this point private $_transactionNestingLevel is set back to 1.
    Nothing is visible to the outside world yet.
  3. $this->conn->commit();
    https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/EntityManager.php#L231.
    At this point private $_transactionNestingLevel is set back to 0 and a
    SQL COMMIT; is run, which makes everything that happened in the
    transaction visible to the outside world.
  4. ... due to the missing second flush(), postFlush listeners that do
    stuff on "the final flush" won't have the opportunity to do their job.

Bear in mind that all I care about is the postFlush event, not the second
flush per se. But since running a second (noop) flush triggers the
postFlush event, I'm ok with that solution.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/doctrine/doctrine2/issues/7103#issuecomment-369740845,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAJakFEjioWm6zYTGH5H90okuY6Oe8sGks5taGtlgaJpZM4SX4oC
.

@Ocramius commented on GitHub (Mar 2, 2018): I don't really understand what you are trying to achieve here: all data within the current and previous nested flush-related transaction is readable after or at the end of a flush operation, so unless your listeners are running on a separate process, you shouldn't need any tweaking at all. On 1 Mar 2018 22:43, "d-ph" <notifications@github.com> wrote: > Hi again, > > So I refreshed my knowledge about db transaction isolation levels > <https://www.postgresql.org/docs/9.6/static/transaction-iso.html> and > also ran some tests on my own to see when new records are actually visible > in the database, and came to conclusion that everything I've said so far is > correct. > > Transaction isolation levels are for what data the queries inside the > transaction see, not the other way around. I.e. it doesn't matter what the > transaction isolation level is -- what happens in a transaction, stays in > the transaction until it's committed. And by the way, I was testing with > the default, least restrictive transaction isolation level in postgres: > "read committed". The bottom line here is: other db connections from other > OS processes won't see the data my script is putting in the db until my > script commits the transaction. Which again, happens after the first flush, > as opposed to: during the first flush, which makes my postFlush listener > ignore it and wait further for "the final flush". > > You said that flush()ing inside or outside of db transaction shouldn't > make any difference. It does. Here's a quick rundown of what happens, when > EntityManager::transactional() is used: > > 1. $this->conn->beginTransaction(); > <https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/EntityManager.php#L225>. > At this point private $_transactionNestingLevel > <https://github.com/doctrine/dbal/blob/0e99c343ef364ee0ab2f0d5aceb18b8768045037/lib/Doctrine/DBAL/Connection.php#L142> > is 1. > 2. The first $this->flush(); > <https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/EntityManager.php#L225> > . > 2.1. $conn->beginTransaction(); > <https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/UnitOfWork.php#L370>. > At this point private $_transactionNestingLevel is 2. > 2.2. $conn->commit(); > <https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/UnitOfWork.php#L411>. > At this point private $_transactionNestingLevel is set back to 1. > Nothing is visible to the outside world yet. > 3. $this->conn->commit(); > <https://github.com/doctrine/doctrine2/blob/07a5861503443367e373f33a957c3852fb4daf7c/lib/Doctrine/ORM/EntityManager.php#L231>. > At this point private $_transactionNestingLevel is set back to 0 and a > SQL COMMIT; is run, which makes everything that happened in the > transaction visible to the outside world. > 4. ... due to the missing second flush(), postFlush listeners that do > stuff on "the final flush" won't have the opportunity to do their job. > > Bear in mind that all I care about is the postFlush event, not the second > flush per se. But since running a second (noop) flush triggers the > postFlush event, I'm ok with that solution. > > — > You are receiving this because you commented. > Reply to this email directly, view it on GitHub > <https://github.com/doctrine/doctrine2/issues/7103#issuecomment-369740845>, > or mute the thread > <https://github.com/notifications/unsubscribe-auth/AAJakFEjioWm6zYTGH5H90okuY6Oe8sGks5taGtlgaJpZM4SX4oC> > . >
Author
Owner

@d-ph commented on GitHub (Mar 2, 2018):

My postFlush listener kicks off another OS process via a queue solution (rabbitmq, but that doesn't matter). That another OS process absolutely must be able to read what my php script (with the postFlush listener) is flushing to the database. This is not the case after the first flush() because that flush() happens before a transaction commit and therefore the data is not yet visible to the outside world.

I understand that my postFlush listener can read the data after the first or the second or even before any of the flushes. But that doesn't matter because my postFlush listener doesn't read any data -- it just kicks off another OS process (probably on a different machine as well, but that's irrelevant).

One could argue: what's the problem? The commit happens immediately after the first flush, so that should be fast enough. It turns out it's not. During those couple of milliseconds between the first flush and the db commit, the other OS process manages to start and fail a couple of times due to unreachable db data.

That's why the other OS process must start only after the transaction is committed. And the other OS process is "started" by my postFlush listener, so my postFlush listener should run after the transaction is committed. And that's what's not happening right now.

If running the second flush or if explicitly triggering the postFlush event is not desirable after the transaction commit, then maybe a new event should be introduced. E.g. postCommit. I'd personally rather avoid adding new events in this case, since at least to me triggering the postFlush again after the db commit makes perfect sense.

All boils down to the fact that I need a way to know when all in-memory content is flushed to the database, so other OS processes can access it. So far I've been using the postFlush event for it. But this falls short in the case of EntityManager::transactional(), which doesn't communicate when the in-memory content is completely flushed to the database.

@d-ph commented on GitHub (Mar 2, 2018): My postFlush listener kicks off another OS process via a queue solution (rabbitmq, but that doesn't matter). That another OS process absolutely must be able to read what my php script (with the postFlush listener) is flushing to the database. This is not the case after the first `flush()` because that `flush()` happens before a transaction commit and therefore the data is not yet visible to the outside world. I understand that my postFlush listener can read the data after the first or the second or even before any of the flushes. But that doesn't matter because my postFlush listener doesn't read any data -- it just kicks off another OS process (probably on a different machine as well, but that's irrelevant). One could argue: what's the problem? The commit happens immediately after the first flush, so that should be fast enough. It turns out it's not. During those couple of milliseconds between the first flush and the db commit, the other OS process manages to start and fail a couple of times due to unreachable db data. That's why the other OS process must start only after the transaction is committed. And the other OS process is "started" by my postFlush listener, so my postFlush listener should run after the transaction is committed. And that's what's not happening right now. If running the second flush or if explicitly triggering the postFlush event is not desirable after the transaction commit, then maybe a new event should be introduced. E.g. postCommit. I'd personally rather avoid adding new events in this case, since at least to me triggering the postFlush again after the db commit makes perfect sense. All boils down to the fact that I need a way to know when all in-memory content is flushed to the database, so other OS processes can access it. So far I've been using the postFlush event for it. But this falls short in the case of `EntityManager::transactional()`, which doesn't communicate when the in-memory content is completely flushed to the database.
Author
Owner

@Isinlor commented on GitHub (Apr 6, 2018):

I think you could try to collect the relevant events and trigger actual dispatching after your application has finished all processing and you are sure that all transactions are closed. In Symfony it would be HttpKernel::TERMINATE event.

@Isinlor commented on GitHub (Apr 6, 2018): I think you could try to collect the relevant events and trigger actual dispatching after your application has finished all processing and you are sure that all transactions are closed. [In Symfony it would be HttpKernel::TERMINATE event.](https://symfony.com/doc/current/components/http_kernel.html#component-http-kernel-event-table)
Author
Owner

@d-ph commented on GitHub (Apr 7, 2018):

Thanks for your reply.

I already have a makeshift solution to my problem (calling the EntityManager::flush() again after the transactional() is finished), which I'm happy to continue using for time being. I also have bad experience with doing things on kernel terminate event (essentially, error handling is not ideal according to my taste).

This ticket is getting old. If after all my explanation there's still no intention to implement what I asked for, then I'm happy to close this ticket. I do still, however, believe that my feature request is legitimate.

@d-ph commented on GitHub (Apr 7, 2018): Thanks for your reply. I already have a makeshift solution to my problem (calling the `EntityManager::flush()` again after the `transactional()` is finished), which I'm happy to continue using for time being. I also have bad experience with doing things on kernel terminate event (essentially, error handling is not ideal according to my taste). This ticket is getting old. If after all my explanation there's still no intention to implement what I asked for, then I'm happy to close this ticket. I do still, however, believe that my feature request is legitimate.
Author
Owner

@errogaht commented on GitHub (Nov 1, 2018):

@d-ph your feature request is the same what I need too. Exactly the same problem with message bus and not closed transaction. I also vote for afterCommit event

@errogaht commented on GitHub (Nov 1, 2018): @d-ph your feature request is the same what I need too. Exactly the same problem with message bus and not closed transaction. I also vote for afterCommit event
Author
Owner

@Isinlor commented on GitHub (Nov 1, 2018):

@errogaht Doctrine is not the right level of abstraction to dispatch afterCommit event. Doctrine does not actually know whether transaction was committed, since this is handled by DBAL and DBAL allows nesting transactions outside Doctrine.

If you really, really want to hook into it then this line $logger->stopQuery(); gives you that opportunity.

You can combine it with PDO::inTransaction, to be 100% sure.
Actually, just overwriting PDO::commit and pushing that into DBAL should work too.

@Isinlor commented on GitHub (Nov 1, 2018): @errogaht Doctrine is not the right level of abstraction to dispatch `afterCommit` event. Doctrine does not actually know whether transaction was committed, since this is handled by DBAL and DBAL allows nesting transactions outside Doctrine. If you really, really want to hook into it then this line [`$logger->stopQuery();`](https://github.com/doctrine/dbal/blob/bef35e9024cca9e4e758b4fed274e8a03dfa8a94/lib/Doctrine/DBAL/Connection.php#L1288) gives you that opportunity. You can combine it with [`PDO::inTransaction`]( http://php.net/manual/en/pdo.intransaction.php), to be 100% sure. Actually, just overwriting `PDO::commit` and pushing that into DBAL should work too.
Author
Owner

@kgrieco commented on GitHub (Nov 6, 2018):

I'm also experiencing exactly the same problem :(

@kgrieco commented on GitHub (Nov 6, 2018): I'm also experiencing exactly the same problem :(
Author
Owner

@jelen07 commented on GitHub (Jan 29, 2019):

Any progress here?

@jelen07 commented on GitHub (Jan 29, 2019): Any progress here?
Author
Owner

@Isinlor commented on GitHub (Jan 29, 2019):

@jelen07 Have you read my comment above? Have you tried it?

@Isinlor commented on GitHub (Jan 29, 2019): @jelen07 Have you read my comment above? Have you tried it?
Author
Owner

@ismail1432 commented on GitHub (Apr 29, 2019):

I have the same issue 👍 The afterCommit event seems a very good feature

@ismail1432 commented on GitHub (Apr 29, 2019): I have the same issue :+1: The `afterCommit` event seems a very good feature
Author
Owner

@gquemener commented on GitHub (Apr 15, 2020):

Allow me to share my personal experience while trying to perform the same kind of "after transaction commit" operations. I feel that it could help other people facing the same issue.

I think you could try to collect the relevant events and trigger actual dispatching after your application has finished all processing and you are sure that all transactions are closed. In Symfony it would be HttpKernel::TERMINATE event.

Don't forget the ConsoleEvents::TERMINATE event as well, if your process is running in CLI.

I'm having a similar issue, and thus would like to point out my use case.

I'm using the postFlush event to dispatch domain events which are recorded within my entity when some mutations are performed.
The requirement being to ensure that the domain events are dispatched if and only if the entity has been persisted (i.e. db row has been updated and this data is accessible by others).

My understanding of the documentation was that the postFlush event would occur once the transaction is commited (if the flush() is called after a transaction has begun).

It seems that this is not the case and flush will dispatch postFlush events even if a "master" transaction is still opened. Thus, this doctrine event doesn't meet the requirement I've previously stated, and it's not meant to do "after transaction commit" operations.

Another way to do it is to delegate the domain events dispatching to another event occuring after the transaction is commited, like the HttpKernel::TERMINATE and Console::TERMINATE events from Symfony. I don't see any drawback, yet, except that it would tightly couples your application with a framework (but this is also the case with a doctrine listener) and the error handling issue that @d-ph mentionned.

Finally, another solution I find more elegant would be to have a Command and an Event bus. You wrap all command handling within a doctrine transaction and dispatch the domain events (in the event bus) once the command has been handled (and after the transaction is commited).

Like this you have total control about when and where "post commit" operations are performed and you don't rely on any library events.

FYI, Symfony Messenger component already provides a DoctrineTransactionMiddleware that does exactly that (wrapping message handling within a Doctrine transaction). When using Symfony Messenger, "performing extra stuff (or in this example, dispatching domain events) after transaction commit" would probably be implemented through another middleware configured on the command bus (decorating the doctrine middleware).

@gquemener commented on GitHub (Apr 15, 2020): Allow me to share my personal experience while trying to perform the same kind of "after transaction commit" operations. I feel that it could help other people facing the same issue. > I think you could try to collect the relevant events and trigger actual dispatching after your application has finished all processing and you are sure that all transactions are closed. [In Symfony it would be HttpKernel::TERMINATE event.](https://symfony.com/doc/current/components/http_kernel.html#component-http-kernel-event-table) Don't forget the ConsoleEvents::TERMINATE event as well, if your process is running in CLI. I'm having a similar issue, and thus would like to point out my use case. I'm using the `postFlush` event to dispatch domain events which are recorded within my entity when some mutations are performed. The requirement being to ensure that the domain events are dispatched if and only if the entity has been persisted (i.e. db row has been updated and this data is accessible by others). My understanding [of the documentation](https://github.com/doctrine/orm/blob/2.7/lib/Doctrine/ORM/Events.php#L150-L154) was that the `postFlush` event would occur once the transaction is commited (if the `flush()` is called after a transaction has begun). It seems that this is not the case and `flush` will dispatch `postFlush` events even if a "master" transaction is still opened. Thus, this doctrine event doesn't meet the requirement I've previously stated, and it's not meant to do "after transaction commit" operations. Another way to do it is to delegate the domain events dispatching to another event occuring after the transaction is commited, like the `HttpKernel::TERMINATE` and `Console::TERMINATE` events from Symfony. I don't see any drawback, yet, except that it would tightly couples your application with a framework (but this is also the case with a doctrine listener) and the error handling issue that @d-ph mentionned. Finally, another solution I find more elegant would be to have a Command and an Event bus. You wrap all command handling within a doctrine transaction and dispatch the domain events (in the event bus) once the command has been handled (and after the transaction is commited). Like this you have total control about when and where "post commit" operations are performed and you don't rely on any library events. FYI, Symfony Messenger component already provides a DoctrineTransactionMiddleware that does exactly that (wrapping message handling within a Doctrine transaction). When using Symfony Messenger, "performing extra stuff (or in this example, dispatching domain events) after transaction commit" would probably be implemented through another middleware configured on the command bus (decorating the doctrine middleware).
Author
Owner

@mtanasiewicz commented on GitHub (Jul 29, 2020):

Hello. According to @gquemener solution, from what I've observed recently, closing transaction with messenger event bus, won't help in all cases. Most of lifecycle events are fired on every transaction layer that was added before. If we dispatch some domain events/commands when subscribing to lifecycle, it can end up dispatched multiple times, on each transaction layer.

There is one quite simple solution, I came up with after couple hours of battling. You can call $entityManager->->getConnection()->getTransactionNestingLevel() to check if there are any remaining transactions to close. Returned value greater than 1 is most likely result with firing same lifecycle event again by Doctrine.

@mtanasiewicz commented on GitHub (Jul 29, 2020): Hello. According to @gquemener solution, from what I've observed recently, closing transaction with messenger event bus, won't help in all cases. Most of lifecycle events are fired on every transaction layer that was added before. If we dispatch some domain events/commands when subscribing to lifecycle, it can end up dispatched multiple times, on each transaction layer. There is one quite simple solution, I came up with after couple hours of battling. You can call `$entityManager->->getConnection()->getTransactionNestingLevel()` to check if there are any remaining transactions to close. Returned value greater than `1` is most likely result with firing same lifecycle event again by Doctrine.
Author
Owner

@pcha commented on GitHub (Nov 6, 2020):

Hello. @mtanasiewicz where call you $entityManager->->getConnection()->getTransactionNestingLevel() to know if the commit was executed?

@pcha commented on GitHub (Nov 6, 2020): Hello. @mtanasiewicz where call you $entityManager->->getConnection()->getTransactionNestingLevel() to know if the commit was executed?
Author
Owner

@xammmue commented on GitHub (Jul 5, 2022):

We encountered the same problem recently and were able to solve it using the DispatchAfterCurrentBusStamp / DispatchAfterCurrentBusMiddleware
https://symfony.com/doc/5.4/messenger/dispatch_after_current_bus.html

This won't help if your process is started by an HTTP request or command execution, but can solve the issue when handling messages and sending out other messages from within the handler

@xammmue commented on GitHub (Jul 5, 2022): We encountered the same problem recently and were able to solve it using the DispatchAfterCurrentBusStamp / DispatchAfterCurrentBusMiddleware https://symfony.com/doc/5.4/messenger/dispatch_after_current_bus.html This won't help if your process is started by an HTTP request or command execution, but can solve the issue when handling messages and sending out other messages from within the handler
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5906