Doctrine's postPersist, postUpdate and postRemove events are fired when the corresponding SQL queries (INSERT / UPDATE / DELETE) have been performed against the database server.
What happens under the hood is that Doctrine creates a wrapping transaction, runs SQL queries, then commits the transaction.
However, these events are fired immediately, e.g. not once the transaction is complete, which means:
- If the wrapping transaction fails, events have already been fired anyway (meaning you trusted generated primary key values, although they're going to be rolled back)
- If the wrapping transaction takes some time (typically during row locks), you get the inserted / updated / deleted information before it's actually done (meaning if you run some async process once those events are triggered, you end up in processing not data which is not up-to-date)
The idea of this repository indeed came up with the following issue:
- An entity is persisted, then
$em->flush()is called - A
postPersistevent listener gets the entity's id, then asks a worker to do some async processing through Symfony Messenger - The worker queries database against the entity's id, and gets an
EntityNotFoundexception (theCOMMITdid not happen yet) - The flush operation on the main thread completes, and the
postFlushevent is fired (but it does not contain the inserted / updated / deleted entities)
If you run into the same kind of issues, you can replace your listeners' listened events in favor of:
Bentools\DoctrineSafeEvents\SafeEvents::POST_PERSIST(and implementsafePostPersistas a replacement ofpostPersist)Bentools\DoctrineSafeEvents\SafeEvents::POST_UPDATE(and implementsafePostUpdateas a replacement ofpostUpdate)Bentools\DoctrineSafeEvents\SafeEvents::POST_REMOVE(and implementsafePostRemoveas a replacement ofpostRemove)
Basically, this library will collect entities which are scheduled for insertion / update / deletion, except it will delay event firing until the postFlush occurs.
composer require bentools/doctrine-safe-eventsAlthough this library has no dependency on Symfony, you can easily use it in your Symfony project:
Bentools\DoctrineSafeEvents\SafeEventsDispatcher:
tags:
- { name: 'doctrine.event_subscriber' }
- { name: 'kernel.reset', method: 'reset' }declare(strict_types=1);
namespace App;
use Bentools\DoctrineSafeEvents\SafeEvents;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\Persistence\Event\LifecycleEventArgs;
#[AsDoctrineListener(SafeEvents::POST_PERSIST)]
final class SomeListener
{
public function safePostPersist(LifecycleEventArgs $eventArgs): void
{
// ...
}
}composer testMIT.