Enhanced Description of how the different events work and what restrictions apply to them.
This commit is contained in:
parent
30b9cfce3d
commit
51729cbaa7
@ -267,16 +267,167 @@ Although you get passed the EntityManager in all of these events, you have to fo
|
|||||||
carefully since operations in the wrong event may produce lots of different errors, such as inconsistent data and
|
carefully since operations in the wrong event may produce lots of different errors, such as inconsistent data and
|
||||||
lost updates/persists/removes.
|
lost updates/persists/removes.
|
||||||
|
|
||||||
|
For the described events that are also lifecycle callback events the restrictions
|
||||||
|
apply aswell, with the additional restriction that you do not have access to the EntityManager
|
||||||
|
or UnitOfWork APIs inside these events.
|
||||||
|
|
||||||
+++ prePersist
|
+++ prePersist
|
||||||
|
|
||||||
|
There are two ways for the `prePersist` event to be triggered. One is obviously
|
||||||
|
when you call `EntityManager#persist()`. The event is also called for all
|
||||||
|
cascaded associations.
|
||||||
|
|
||||||
|
There is another way for `prePersist` to be called, inside the `flush()`
|
||||||
|
method when changes to associations are computed and this association
|
||||||
|
is marked as cascade persist. Any new entity found during this operation
|
||||||
|
is also persisted and `prePersist` called on it. This is called "persistence by reachability".
|
||||||
|
|
||||||
|
In both cases you get passed a `LifecycleEventArgs`
|
||||||
|
instance which has access to the entity and the entity manager.
|
||||||
|
|
||||||
|
The following restrictions apply to `prePersist`:
|
||||||
|
|
||||||
|
* If you are using a PrePersist Identity Generator such as sequences the ID value
|
||||||
|
will *NOT* be available within any PrePersist events.
|
||||||
|
* Doctrine will not recognize changes made to relations in a pre persist event
|
||||||
|
called by "reachibility" through a cascade persist unless you use the internal
|
||||||
|
`UnitOfWork` API. We do not recommend such operations in the persistence by
|
||||||
|
reachability context, so do this at your own risk and possibly supported by unit-tests.
|
||||||
|
|
||||||
+++ preRemove
|
+++ preRemove
|
||||||
|
|
||||||
|
The `preRemove` event is called on every entity when its passed to
|
||||||
|
the `EntityManager#remove()` method. It is cascaded for all
|
||||||
|
associations that are marked as cascade delete.
|
||||||
|
|
||||||
|
There are no restrictions to what methods can be called inside
|
||||||
|
the `preRemove` event, except when the remove method itself was
|
||||||
|
called during a flush operation.
|
||||||
|
|
||||||
+++ onFlush
|
+++ onFlush
|
||||||
|
|
||||||
|
OnFlush is a very powerful event. It is called inside `EntityManager#flush()`
|
||||||
|
after the changes to all the managed entities and their associations have
|
||||||
|
been computed. This means, the `onFlush` event has access to the sets of:
|
||||||
|
|
||||||
|
* Entities scheduled for insert
|
||||||
|
* Entities scheduled for update
|
||||||
|
* Entities scheduled for removal
|
||||||
|
* Collections scheduled for update
|
||||||
|
* Collections scheduled for removal
|
||||||
|
|
||||||
|
To make use of the onFlush event you have to be familiar with interal UnitOfWork API,
|
||||||
|
which grants you access to the previously mentioned sets. See this example:
|
||||||
|
|
||||||
|
[php]
|
||||||
|
class FlushExampleListener
|
||||||
|
{
|
||||||
|
public function onFlush(OnFlushEventArgs $eventArgs)
|
||||||
|
{
|
||||||
|
$em = $eventArgs->getEntityManager();
|
||||||
|
$uow = $em->getUnitOfWork();
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityInsertions() AS $entity) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityUpdates() AS $entity) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityDeletions() AS $entity) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledCollectionDeletions() AS $col) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledCollectionUpdates() AS $col) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The following restrictions apply to the onFlush event:
|
||||||
|
|
||||||
|
* Calling `EntityManager#persist()` does not suffice to trigger a persist on an entity.
|
||||||
|
You have to execute an additional call to `$unitOfWork->computeChangeSet($classMetadata, $entity)`.
|
||||||
|
* Changing primitive fields or associations requires you to explicitly trigger
|
||||||
|
a re-computation of the changeset of the affected entity. This can be done
|
||||||
|
by either calling `$unitOfWork->computeChangeSet($classMetadata, $entity)`
|
||||||
|
or `$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)`. The second
|
||||||
|
method has lower overhead, but only re-computes primitive fields, never associations.
|
||||||
|
|
||||||
+++ preUpdate
|
+++ preUpdate
|
||||||
|
|
||||||
|
PreUpdate is the most restrictive to use event, since it is called right before
|
||||||
|
an update statement is called for an entity inside the `EntityManager#flush()`
|
||||||
|
method.
|
||||||
|
|
||||||
|
Changes to associations of the updated entity are never allowed in this event, since Doctrine cannot guarantee to
|
||||||
|
correctly handle referential integrity at this point of the flush operation. This
|
||||||
|
event has a powerful feature however, it is executed with a `PreUpdateEventArgs`
|
||||||
|
instance, which contains a reference to the computed change-set of this entity.
|
||||||
|
|
||||||
|
This means you have access to all the fields that have changed for this entity
|
||||||
|
with their old and new value. The following methods are available on the `PreUpdateEventArgs`:
|
||||||
|
|
||||||
|
* `getEntity()` to get access to the actual entity.
|
||||||
|
* `getEntityChangeSet()` to get a copy of the changeset array. Changes to this returned array do not affect updating.
|
||||||
|
* `hasChangedField($fieldName)` to check if the given field name of the current entity changed.
|
||||||
|
* `getOldValue($fieldName)` and `getNewValue($fieldName)` to access the values of a field.
|
||||||
|
* `setNewValue($fieldName, $value)` to change the value of a field to be updated.
|
||||||
|
|
||||||
|
A simple example for this event looks like:
|
||||||
|
|
||||||
|
[php]
|
||||||
|
class NeverAliceOnlyBobListener
|
||||||
|
{
|
||||||
|
public function preUpdate(PreUpdateEventArgs $eventArgs)
|
||||||
|
{
|
||||||
|
if ($eventArgs->getEntity() instanceof User) {
|
||||||
|
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue() == 'Alice') {
|
||||||
|
$eventArgs->setNewValue('name', 'Bob');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
You could also use this listener to implement validation of all the fields that have changed.
|
||||||
|
This is more efficient than using a lifecycle callback when there are expensive validations
|
||||||
|
to call:
|
||||||
|
|
||||||
|
[php]
|
||||||
|
class ValidCreditCardListener
|
||||||
|
{
|
||||||
|
public function preUpdate(PreUpdateEventArgs $eventArgs)
|
||||||
|
{
|
||||||
|
if ($eventArgs->getEntity() instanceof Account) {
|
||||||
|
if ($eventArgs->hasChangedField('creditCard')) {
|
||||||
|
$this->validateCreditCard($eventArgs->getNewValue('creditCard'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateCreditCard($no)
|
||||||
|
{
|
||||||
|
// throw an exception to interupt flush event. Transaction will be rolled back.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Restrictions for this event:
|
||||||
|
|
||||||
|
* Changes to associations of the passed entities are not recognized by the flush operation anymore.
|
||||||
|
* Changes to fields of the passed entities are not recognized by the flush operation anymore, use the computed change-set passed to the event to modify primitive field values.
|
||||||
|
* Any calls to `EntityManager#persist()` or `EntityManager#remove()`, even in combination with the UnitOfWork API are strongly discouraged and don't work as expected outside the flush operation.
|
||||||
|
|
||||||
+++ postUpdate, postRemove, postPersist
|
+++ postUpdate, postRemove, postPersist
|
||||||
|
|
||||||
|
The three post events are called inside `EntityManager#flush()`. Changes in here
|
||||||
|
are not relevant to the persistence in the database, but you can use this events
|
||||||
|
to
|
||||||
|
|
||||||
++ Load ClassMetadata Event
|
++ Load ClassMetadata Event
|
||||||
|
|
||||||
When the mapping information for an entity is read, it is populated in to a
|
When the mapping information for an entity is read, it is populated in to a
|
||||||
|
Loading…
Reference in New Issue
Block a user