Skip to content

Commit

Permalink
Only dispatch lifecycle events once per commit operation
Browse files Browse the repository at this point in the history
  • Loading branch information
alcaeus committed Nov 29, 2023
1 parent ac6c23e commit b926d0b
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 0 deletions.
3 changes: 3 additions & 0 deletions lib/Doctrine/ODM/MongoDB/UnitOfWork.php
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,8 @@ public function commit(array $options = []): void
$this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->dm));

if ($this->useTransaction($options)) {
$this->lifecycleEventManager->enableTransactionalMode();

with_transaction(
$this->dm->getClient()->startSession(),
function (Session $session) use ($options): void {
Expand Down Expand Up @@ -484,6 +486,7 @@ function (Session $session) use ($options): void {
$this->hasScheduledCollections = [];
} finally {
$this->commitsInProgress--;
$this->lifecycleEventManager->clearTransactionalState();
}
}

Expand Down
66 changes: 66 additions & 0 deletions lib/Doctrine/ODM/MongoDB/Utility/LifecycleEventManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,31 @@
use Doctrine\ODM\MongoDB\UnitOfWork;
use MongoDB\Driver\Session;

use function spl_object_hash;

/** @internal */
final class LifecycleEventManager
{
private bool $transactionalModeEnabled = false;

/** @var array<string, array<string, true>> */
private array $transactionalEvents = [];

public function __construct(private DocumentManager $dm, private UnitOfWork $uow, private EventManager $evm)
{
}

public function clearTransactionalState(): void
{
$this->transactionalModeEnabled = false;
$this->transactionalEvents = [];
}

public function enableTransactionalMode(): void
{
$this->transactionalModeEnabled = true;
}

/**
* @param mixed $id
*
Expand Down Expand Up @@ -58,6 +76,10 @@ public function postCollectionLoad(PersistentCollectionInterface $coll): void
*/
public function postPersist(ClassMetadata $class, object $document, ?Session $session = null): void
{
if (! $this->shouldDispatchEvent($document, Events::postPersist)) {
return;
}

$isInTransaction = $session ? $session->isInTransaction() : false;
$eventArgs = new LifecycleEventArgs($document, $this->dm, $isInTransaction, $session);

Expand All @@ -76,6 +98,10 @@ public function postPersist(ClassMetadata $class, object $document, ?Session $se
*/
public function postRemove(ClassMetadata $class, object $document, ?Session $session = null): void
{
if (! $this->shouldDispatchEvent($document, Events::postRemove)) {
return;
}

$isInTransaction = $session ? $session->isInTransaction() : false;
$eventArgs = new LifecycleEventArgs($document, $this->dm, $isInTransaction, $session);

Expand All @@ -94,6 +120,10 @@ public function postRemove(ClassMetadata $class, object $document, ?Session $ses
*/
public function postUpdate(ClassMetadata $class, object $document, ?Session $session = null): void
{
if (! $this->shouldDispatchEvent($document, Events::postUpdate)) {
return;
}

$isInTransaction = $session ? $session->isInTransaction() : false;
$eventArgs = new LifecycleEventArgs($document, $this->dm, $isInTransaction, $session);

Expand All @@ -112,6 +142,10 @@ public function postUpdate(ClassMetadata $class, object $document, ?Session $ses
*/
public function prePersist(ClassMetadata $class, object $document): void
{
if (! $this->shouldDispatchEvent($document, Events::prePersist)) {
return;
}

$class->invokeLifecycleCallbacks(Events::prePersist, $document, [new LifecycleEventArgs($document, $this->dm)]);
$this->dispatchEvent($class, Events::prePersist, new LifecycleEventArgs($document, $this->dm));
}
Expand All @@ -126,6 +160,10 @@ public function prePersist(ClassMetadata $class, object $document): void
*/
public function preRemove(ClassMetadata $class, object $document): void
{
if (! $this->shouldDispatchEvent($document, Events::preRemove)) {
return;
}

$class->invokeLifecycleCallbacks(Events::preRemove, $document, [new LifecycleEventArgs($document, $this->dm)]);
$this->dispatchEvent($class, Events::preRemove, new LifecycleEventArgs($document, $this->dm));
}
Expand All @@ -140,6 +178,10 @@ public function preRemove(ClassMetadata $class, object $document): void
*/
public function preUpdate(ClassMetadata $class, object $document, ?Session $session = null): void
{
if (! $this->shouldDispatchEvent($document, Events::preUpdate)) {
return;
}

$isInTransaction = $session ? $session->isInTransaction() : false;

if (! empty($class->lifecycleCallbacks[Events::preUpdate])) {
Expand Down Expand Up @@ -207,6 +249,10 @@ private function cascadePostUpdate(ClassMetadata $class, object $document, ?Sess
$entryClass = $this->dm->getClassMetadata($entry::class);
$event = $this->uow->isScheduledForInsert($entry) ? Events::postPersist : Events::postUpdate;

if (! $this->shouldDispatchEvent($entry, $event)) {
continue;
}

$eventArgs = new LifecycleEventArgs($entry, $this->dm, $isInTransaction, $session);

$entryClass->invokeLifecycleCallbacks($event, $entry, [$eventArgs]);
Expand Down Expand Up @@ -249,4 +295,24 @@ private function dispatchEvent(ClassMetadata $class, string $eventName, ?EventAr

$this->evm->dispatchEvent($eventName, $eventArgs);
}

private function shouldDispatchEvent(object $document, string $eventName): bool
{
if (! $this->transactionalModeEnabled) {
return true;
}

// Check whether the event has already been dispatched.
$hasDispatched = isset($this->transactionalEvents[$this->getObjectId($document)][$eventName]);

// Mark the event as dispatched - no problem doing this if it already was dispatched
$this->transactionalEvents[$this->getObjectId($document)][$eventName] = true;

return ! $hasDispatched;
}

private function getObjectId(object $document): string
{
return spl_object_hash($document);
}
}
Loading

0 comments on commit b926d0b

Please sign in to comment.