Events
Doctrine features a lightweight event system that is part of the Common package.
The Event System
The event system is controlled by the EventManager
. It is the
central point of Doctrine's event listener system. Listeners are
registered on the manager and events are dispatched through the
manager.
Now we can add some event listeners to the $evm
. Let's create a
EventTest
class to play around with.
1 <?php
use Doctrine\Common\EventManager;
class EventTest
{
public bool $preFooInvoked = false;
public bool $postFooInvoked = false;
public function __construct(EventManager $evm)
{
$evm->addEventListener(['preFoo', 'postFoo'], $this);
}
public function preFoo(EventArgs $e): void
{
$this->preFooInvoked = true;
}
public function postFoo(EventArgs $e): void
{
$this->postFooInvoked = true;
}
}
// Create a new instance
$test = new EventTest($evm);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Events can be dispatched by using the dispatchEvent()
method.
You can easily remove a listener with the removeEventListener()
method.
The Doctrine event system also has a simple concept of event
subscribers. We can define a simple TestEventSubscriber
class
which implements the \Doctrine\Common\EventSubscriber
interface
and implements a getSubscribedEvents()
method which returns an
array of events it should be subscribed to.
1 <?php
class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
{
const preFoo = 'preFoo';
public bool $preFooInvoked = false;
public function preFoo(): void
{
$this->preFooInvoked = true;
}
public function getSubscribedEvents(): array
{
return [self::preFoo];
}
}
$eventSubscriber = new TestEventSubscriber();
$evm->addEventSubscriber($eventSubscriber);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Now when you dispatch an event any event subscribers will be notified for that event.
Now test the $eventSubscriber
instance to see if the
preFoo()
method was invoked.
Lifecycle Events
The DocumentManager and UnitOfWork trigger several events during the life-time of their registered documents.
- preRemove - The preRemove event occurs for a given document before the respective DocumentManager remove operation for that document is executed.
- postRemove - The postRemove event occurs for a document after the document has been removed. It will be invoked after the database delete operations.
- prePersist - The prePersist event occurs for a given document before the respective DocumentManager persist operation for that document is executed.
- postPersist - The postPersist event occurs for a document after the document has been made persistent. It will be invoked after the database insert operations. Generated primary key values are available in the postPersist event.
- preUpdate - The preUpdate event occurs before the database update operations to document data.
- postUpdate - The postUpdate event occurs after the database update operations to document data.
- preLoad - The preLoad event occurs for a document before the document has been loaded into the current DocumentManager from the database or after the refresh operation has been applied to it.
- postLoad - The postLoad event occurs for a document after the document has been loaded into the current DocumentManager from the database or after the refresh operation has been applied to it.
- loadClassMetadata - The loadClassMetadata event occurs after the mapping metadata for a class has been loaded from a mapping source (attributes/xml).
- onClassMetadataNotFound - Loading class metadata for a particular requested class name failed. Manipulating the given event args instance allows providing fallback metadata even when no actual metadata exists or could be found. This event is not a lifecycle callback. Support for this event was added in MongoDB ODM 1.3.
- preFlush - The preFlush event occurs before the change-sets of all managed documents are computed. This both a lifecycle call back and and listener.
- postFlush - The postFlush event occurs after the change-sets of all managed documents are computed.
- onFlush - The onFlush event occurs after the change-sets of all managed documents are computed. This event is not a lifecycle callback.
- onClear - The onClear event occurs after the UnitOfWork has had its state cleared.
- documentNotFound - The documentNotFound event occurs when a proxy object could not be initialized. This event is not a lifecycle callback.
- postCollectionLoad - The postCollectionLoad event occurs just after collection has been initialized (loaded) and before new elements are re-added to it.
You can access the Event constants from the Events
class in the
ODM package.
These can be hooked into by two different types of event listeners:
- Lifecycle Callbacks are methods on the document classes that are
called when the event is triggered. They receive instances
of
Doctrine\ODM\MongoDB\Event\LifecycleEventArgs
(see relevant examples below) as arguments and are specifically designed to allow changes inside the document classes state. - Lifecycle Event Listeners are classes with specific callback
methods that receives some kind of
EventArgs
instance which give access to the document, DocumentManager or other relevant data.
All Lifecycle events that happen during the |
Lifecycle Callbacks
A lifecycle event is a regular event with the additional feature of providing a mechanism to register direct callbacks inside the corresponding document classes that are executed when the lifecycle event occurs.
1 <?php
#[Document]
#[HasLifecycleCallbacks]
class User
{
// ...
#[Field]
public string $value;
#[Field]
private \DateTimeInterface $createdAt;
#[PrePersist]
public function doStuffOnPrePersist(\Doctrine\ODM\MongoDB\Event\LifecycleEventArgs $eventArgs): void
{
$this->createdAt = new DateTimeImmutable();
}
#[PrePersist]
public function doOtherStuffOnPrePersist(\Doctrine\ODM\MongoDB\Event\LifecycleEventArgs $eventArgs): void
{
$this->value = 'changed from prePersist callback!';
}
#[PostPersist]
public function doStuffOnPostPersist(\Doctrine\ODM\MongoDB\Event\LifecycleEventArgs $eventArgs): void
{
$this->value = 'changed from postPersist callback!';
}
#[PreLoad]
public function doStuffOnPreLoad(\Doctrine\ODM\MongoDB\Event\PreLoadEventArgs $eventArgs): void
{
$data =& $eventArgs->getData();
$data['value'] = 'changed from preLoad callback';
}
#[PostLoad]
public function doStuffOnPostLoad(\Doctrine\ODM\MongoDB\Event\LifecycleEventArgs $eventArgs): void
{
$this->value = 'changed from postLoad callback!';
}
#[PreUpdate]
public function doStuffOnPreUpdate(\Doctrine\ODM\MongoDB\Event\PreUpdateEventArgs $eventArgs): void
{
$this->value = 'changed from preUpdate callback!';
}
#[PreFlush]
public function preFlush(\Doctrine\ODM\MongoDB\Event\PreFlushEventArgs $eventArgs): void
{
$this->value = 'changed from preFlush callback!';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
Note that when using attributes you have to apply the
#[HasLifecycleCallbacks]
marker attribute on the document class.
Listening to Lifecycle Events
Lifecycle event listeners are much more powerful than the simple lifecycle callbacks that are defined on the document classes. They allow to implement re-usable behaviours between different document classes, yet require much more detailed knowledge about the inner workings of the DocumentManager and UnitOfWork. Please read the Implementing Event Listeners section carefully if you are trying to write your own listener.
To register an event listener you have to hook it into the EventManager that is passed to the DocumentManager factory:
You can also retrieve the event manager instance after the DocumentManager was created:
Implementing Event Listeners
This section explains what is and what is not allowed during specific lifecycle events of the UnitOfWork. Although you get passed the DocumentManager in all of these events, you have to follow this restrictions very carefully since operations in the wrong event may produce lots of different errors, such as inconsistent data and lost updates/persists/removes.
Handling Transactional Flushes
When a flush operation is executed in a transaction, all queries inside a lifecycle event listener also have to make use
of the session used during the flush operation. This session object is exposed through the LifecycleEventArgs
parameter passed to the listener. Passing the session to queries ensures that the query will become part of the
transaction and will see data that has not been committed yet.
1 <?php
class EventTest
{
public function someEventListener(\Doctrine\ODM\MongoDB\Event\LifecycleEventArgs $eventArgs): void
{
// To check if a transaction is active:
if ($eventArgs->isInTransaction()) {
// Do something
}
// Pass the session to any query you execute
$eventArgs->getDocumentManager()->createQueryBuilder(User::class)
// Query logic
->getQuery(['session' => $eventArgs->session])
->execute();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Event listeners are only called during the first transaction attempt. If the transaction is retried, event listeners will not be invoked again. Make sure to run any persistence logic through the UnitOfWork instead of modifying data directly through queries run in an event listener. |
preUpdate
Define the EventTest
class with a preUpdate()
method:
1 <?php
class EventTest
{
public function preUpdate(\Doctrine\ODM\MongoDB\Event\LifecycleEventArgs $eventArgs): void
{
$document = $eventArgs->getDocument();
$document->setSomething();
$dm = $eventArgs->getDocumentManager();
$class = $dm->getClassMetadata(get_class($document));
$dm->getUnitOfWork()->recomputeSingleDocumentChangeSet($class, $document);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
If you modify a document in the preUpdate event you must call |
onClear
Define the EventTest
class with a onClear()
method:
1 <?php
class EventTest
{
public function onClear(\Doctrine\ODM\MongoDB\Event\OnClearEventArgs $eventArgs): void
{
$class = $eventArgs->getDocumentClass();
$dm = $eventArgs->getDocumentManager();
$uow = $dm->getUnitOfWork();
// Check if event clears all documents.
if ($eventArgs->clearsAllDocuments()) {
// do something
}
// do something
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
documentNotFound
Define the EventTest
class with a documentNotFound()
method:
1 <?php
class EventTest
{
public function documentNotFound(\Doctrine\ODM\MongoDB\Event\DocumentNotFoundEventArgs $eventArgs): void
{
$proxy = $eventArgs->getObject();
$identifier = $eventArgs->getIdentifier();
// do something
// To prevent the documentNotFound exception from being thrown, call the disableException() method:
$eventArgs->disableException();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
postUpdate, postRemove, postPersist
Define the EventTest
class with a postUpdate()
, postRemove()
and postPersist()
method:
1 <?php
class EventTest
{
public function postUpdate(\Doctrine\ODM\MongoDB\Event\LifecycleEventArgs $eventArgs): void
{
}
public function postRemove(\Doctrine\ODM\MongoDB\Event\LifecycleEventArgs $eventArgs): void
{
}
public function postPersist(\Doctrine\ODM\MongoDB\Event\LifecycleEventArgs $eventArgs): void
{
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
postCollectionLoad
This event was introduced in version 1.1 |
Define the EventTest
class with a postCollectionLoad()
method:
1 <?php
class EventTest
{
public function postCollectionLoad(\Doctrine\ODM\MongoDB\Event\PostCollectionLoadEventArgs $eventArgs): void
{
$collection = $eventArgs->getCollection();
if ($collection instanceof \Malarzm\Collections\DiffableCollection) {
$collection->snapshot();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
loadClassMetadata
When the mapping information for a document is read, it is
populated in to a ClassMetadata
instance. You can hook in to
this process and manipulate the instance with the loadClassMetadata
event:
1 <?php
$test = new EventTest();
$metadataFactory = $dm->getMetadataFactory();
$evm = $dm->getEventManager();
$evm->addEventListener(Events::loadClassMetadata, $test);
class EventTest
{
public function loadClassMetadata(\Doctrine\ODM\MongoDB\Event\LoadClassMetadataEventArgs $eventArgs): void
{
$classMetadata = $eventArgs->getClassMetadata();
$fieldMapping = [
'fieldName' => 'about',
'type' => 'string'
];
$classMetadata->mapField($fieldMapping);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19