Events ====== Doctrine 2 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. .. code-block:: php addEventListener(array(self::preFoo, self::postFoo), $this); } public function preFoo(EventArgs $e) { $this->preFooInvoked = true; } public function postFoo(EventArgs $e) { $this->postFooInvoked = true; } } // Create a new instance $test = new EventTest($evm); Events can be dispatched by using the ``dispatchEvent()`` method. .. code-block:: php dispatchEvent(EventTest::preFoo); $evm->dispatchEvent(EventTest::postFoo); You can easily remove a listener with the ``removeEventListener()`` method. .. code-block:: php removeEventListener(array(self::preFoo, self::postFoo), $this); The Doctrine 2 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. .. code-block:: php preFooInvoked = true; } public function getSubscribedEvents() { return array(TestEvent::preFoo); } } $eventSubscriber = new TestEventSubscriber(); $evm->addEventSubscriber($eventSubscriber); Now when you dispatch an event any event subscribers will be notified for that event. .. code-block:: php dispatchEvent(TestEvent::preFoo); Now you can test the ``$eventSubscriber`` instance to see if the ``preFoo()`` method was invoked. .. code-block:: php preFooInvoked) { echo 'pre foo invoked!'; } Naming convention ~~~~~~~~~~~~~~~~~ Events being used with the Doctrine 2 EventManager are best named with camelcase and the value of the corresponding constant should be the name of the constant itself, even with spelling. This has several reasons: - It is easy to read. - Simplicity. - Each method within an EventSubscriber is named after the corresponding constant. If constant name and constant value differ, you MUST use the new value and thus, your code might be subject to codechanges when the value changes. This contradicts the intention of a constant. An example for a correct notation can be found in the example ``EventTest`` above. Lifecycle Events ---------------- The EntityManager and UnitOfWork trigger a bunch of events during the life-time of their registered entities. - preRemove - The preRemove event occurs for a given entity before the respective EntityManager remove operation for that entity is executed. It is not called for a DQL DELETE statement. - postRemove - The postRemove event occurs for an entity after the entity has been deleted. It will be invoked after the database delete operations. It is not called for a DQL DELETE statement. - prePersist - The prePersist event occurs for a given entity before the respective EntityManager persist operation for that entity is executed. It should be noted that this event is only triggered on *initial* persist of an entity - postPersist - The postPersist event occurs for an entity after the entity 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 entity data. It is not called for a DQL UPDATE statement. - postUpdate - The postUpdate event occurs after the database update operations to entity data. It is not called for a DQL UPDATE statement. - postLoad - The postLoad event occurs for an entity after the entity has been loaded into the current EntityManager 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 (annotations/xml/yaml). - preFlush - The preFlush event occurs at the very beginning of a flush operation. This event is not a lifecycle callback. - onFlush - The onFlush event occurs after the change-sets of all managed entities are computed. This event is not a lifecycle callback. - postFlush - The postFlush event occurs at the end of a flush operation. This event is not a lifecycle callback. - onClear - The onClear event occurs when the EntityManager#clear() operation is invoked, after all references to entities have been removed from the unit of work. .. warning:: Note that the postLoad event occurs for an entity before any associations have been initialized. Therefore it is not safe to access associations in a postLoad callback or event handler. You can access the Event constants from the ``Events`` class in the ORM package. .. code-block:: php createdAt = date('Y-m-d H:i:s'); } /** @PrePersist */ public function doOtherStuffOnPrePersist() { $this->value = 'changed from prePersist callback!'; } /** @PostPersist */ public function doStuffOnPostPersist() { $this->value = 'changed from postPersist callback!'; } /** @PostLoad */ public function doStuffOnPostLoad() { $this->value = 'changed from postLoad callback!'; } /** @PreUpdate */ public function doStuffOnPreUpdate() { $this->value = 'changed from preUpdate callback!'; } } Note that when using annotations you have to apply the @HasLifecycleCallbacks marker annotation on the entity class. If you want to register lifecycle callbacks from YAML or XML you can do it with the following. .. code-block:: yaml User: type: entity fields: # ... name: type: string(50) lifecycleCallbacks: prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] postPersist: [ doStuffOnPostPersist ] XML would look something like this: .. code-block:: xml You just need to make sure a public ``doStuffOnPrePersist()`` and ``doStuffOnPostPersist()`` method is defined on your ``User`` model. .. code-block:: php hasChangedField('username')) { // Do something when the username is changed. } } } Listening to Lifecycle Events ----------------------------- Lifecycle event listeners are much more powerful than the simple lifecycle callbacks that are defined on the entity classes. They allow to implement re-usable behaviors between different entity classes, yet require much more detailed knowledge about the inner workings of the EntityManager 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 EntityManager factory: .. code-block:: php addEventListener(array(Events::preUpdate), new MyEventListener()); $eventManager->addEventSubscriber(new MyEventSubscriber()); $entityManager = EntityManager::create($dbOpts, $config, $eventManager); You can also retrieve the event manager instance after the EntityManager was created: .. code-block:: php getEventManager()->addEventListener(array(Events::preUpdate), new MyEventListener()); $entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber()); .. _reference-events-implementing-listeners: 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 EntityManager in all of these events, you have to follow these restrictions very carefully since operations in the wrong event may produce lots of different errors, such as inconsistent data and lost updates/persists/removes. For the described events that are also lifecycle callback events the restrictions apply as well, with the additional restriction that you do not have access to the EntityManager or UnitOfWork APIs inside these events. 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 "reachability" 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 ~~~~~~~~~ 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. preFlush ~~~~~~~~ ``preFlush`` is called at ``EntityManager#flush()`` before anything else. ``EntityManager#flush()`` can be called safely inside its listeners. .. code-block:: php 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: - If you create and persist a new entity in "onFlush", then calling ``EntityManager#persist()`` is not enough. 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->recomputeSingleEntityChangeSet($classMetadata, $entity)``. postFlush ~~~~~~~~~ ``postFlush`` is called at the end of ``EntityManager#flush()``. ``EntityManager#flush()`` can be called safely inside its listeners. .. code-block:: php getEntity() instanceof User) { if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == '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: .. code-block:: php getEntity() instanceof Account) { if ($eventArgs->hasChangedField('creditCard')) { $this->validateCreditCard($eventArgs->getNewValue('creditCard')); } } } private function validateCreditCard($no) { // throw an exception to interrupt 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 these events to alter non-persistable items, like non-mapped fields, logging or even associated classes that are directly mapped by Doctrine. postLoad ~~~~~~~~ This event is called after an entity is constructed by the EntityManager. Entity listeners ---------------- An entity listeners is a lifecycle listener classes used for an entity. - The entity listeners mapping may be applied to an entity class or mapped superclass. - An entity listener is defined by mapping the entity class with the corresponding mapping. .. configuration-block:: .. code-block:: php .. code-block:: yaml MyProject\Entity\User: type: entity entityListeners: UserListener: # .... .. _reference-entity-listeners: Entity listeners class ~~~~~~~~~~~~~~~~~~~~~~ An ``Entity Listener`` could be any class, by default it should be a class with a no-arg constructor. - Different from :ref:`reference-events-implementing-listeners` an ``Entity Listener`` is invoked just to the specified entity - An entity listener method receives two arguments, the entity instance and the lifecycle event. - A callback method could be defined by naming convention or specifying a method mapping. - When the listener mapping is not given the parser will lookup for methods that match with the naming convention. - When the listener mapping is given the parser won't lookup for any naming convention. .. code-block:: php .. code-block:: yaml MyProject\Entity\User: type: entity entityListeners: UserListener: preFlush: [preFlushHandler] postLoad: [postLoadHandler] postPersist: [postPersistHandler] prePersist: [prePersistHandler] postUpdate: [postUpdateHandler] preUpdate: [preUpdateHandler] postRemove: [postRemoveHandler] preRemove: [preRemoveHandler] # .... Entity listeners resolver ~~~~~~~~~~~~~~~~~~~~~~~~~~ Doctrine invoke the listener resolver to get the listener instance. - An resolver allows you register a specific ``Entity Listener`` instance. - You can also implement your own resolver by extending ``Doctrine\ORM\Mapping\DefaultEntityListenerResolver`` or implementing ``Doctrine\ORM\Mapping\EntityListenerResolver`` Specifying an entity listener instance : .. code-block:: php service = $service; } public function preUpdate(User $user, PreUpdateEventArgs $event) { $this->service->doSomething($user); } } // register a entity listener. $listener = $container->get('user_listener'); $em->getConfiguration()->getEntityListenerResolver()->register($listener); Implementing your own resolver : .. code-block:: php container = $container; } public function resolve($className) { // resolve the service id by the given class name; $id = 'user_listener'; return $this->container->get($id); } } // configure the listener resolver. $em->getConfiguration()->setEntityListenerResolver($container->get('my_resolver')); Load ClassMetadata Event ------------------------ When the mapping information for an entity is read, it is populated in to a ``ClassMetadataInfo`` instance. You can hook in to this process and manipulate the instance. .. code-block:: php getMetadataFactory(); $evm = $em->getEventManager(); $evm->addEventListener(Events::loadClassMetadata, $test); class EventTest { public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs) { $classMetadata = $eventArgs->getClassMetadata(); $fieldMapping = array( 'fieldName' => 'about', 'type' => 'string', 'length' => 255 ); $classMetadata->mapField($fieldMapping); } }