Doctrine, the set of PHP libraries used by Symfony to work with databases,
provides a lightweight event system to update entities during the application
execution. These events, called lifecycle events, allow to perform tasks such
as "update the createdAt property automatically just before persisting entities
of this type".

Doctrine triggers events before/after performing the most common entity
operations (e.g. prePersist/postPersist, preUpdate/postUpdate) and also
on other common tasks (e.g. loadClassMetadata, onClear).

There are different ways to listen to these Doctrine events:

Lifecycle callbacks, they are defined as methods on the entity classes and
they are called when the events are triggered;

Lifecycle listeners and subscribers, they are classes with callback
methods for one or more events and they are called for all entities;

Entity listeners, they are similar to lifecycle listeners, but they are
called only for the entities of a certain class.

These are the drawbacks and advantages of each one:

Callbacks have better performance because they only apply to a single entity
class, but you can't reuse the logic for different entities and they don't
have access to Symfony services;

Lifecycle listeners and subscribers can reuse logic among different entities
and can access Symfony services but their performance is worse because they
are called for all entities;

Entity listeners have the same advantages of lifecycle listeners and they have
better performance because they only apply to a single entity class.

This article only explains the basics about Doctrine events when using them
inside a Symfony application. Read the official docs about Doctrine events
to learn everything about them.

Lifecycle callbacks are defined as methods inside the entity you want to modify.
For example, suppose you want to set a createdAt date column to the current
date, but only when the entity is first persisted (i.e. inserted). To do so,
define a callback for the prePersist Doctrine event:

Annotations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// src/Entity/Product.phpuseDoctrine\ORM\MappingasORM;// When using annotations, don't forget to add @ORM\HasLifecycleCallbacks()// to the class of the entity where you define the callback/** * @ORM\Entity() * @ORM\HasLifecycleCallbacks() */classProduct{// .../** * @ORM\PrePersist */publicfunctionsetCreatedAtValue(){$this->createdAt=new\DateTime();}}

Lifecycle listeners are defined as PHP classes that listen to a single Doctrine
event on all the application entities. For example, suppose that you want to
update some search index whenever a new entity is persisted in the database. To
do so, define a listener for the postPersist Doctrine event:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

// src/EventListener/SearchIndexer.phpnamespaceApp\EventListener;useApp\Entity\Product;useDoctrine\Persistence\Event\LifecycleEventArgs;classSearchIndexer{// the listener methods receive an argument which gives you access to// both the entity object of the event and the entity manager itselfpublicfunctionpostPersist(LifecycleEventArgs$args){$entity=$args->getObject();// if this listener only applies to certain entity types,// add some code to check the entity type as early as possibleif(!$entityinstanceofProduct){return;}$entityManager=$args->getObjectManager();// ... do something with the Product entity}}

The next step is to enable the Doctrine listener in the Symfony application by
creating a new service for it and tagging it
with the doctrine.event_listener tag:

YAML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# config/services.yamlservices:# ...App\EventListener\SearchIndexer:tags:-name:'doctrine.event_listener'# this is the only required option for the lifecycle listener tagevent:'postPersist'# listeners can define their priority in case multiple listeners are associated# to the same event (default priority = 0; higher numbers = listener is run earlier)priority:500# you can also restrict listeners to a specific Doctrine connectionconnection:'default'

// config/services.phpuseApp\EventListener\SearchIndexer;// listeners are applied by default to all Doctrine connections$container->autowire(SearchIndexer::class)->addTag('doctrine.event_listener',[// this is the only required option for the lifecycle listener tag'event'=>'postPersist',// listeners can define their priority in case multiple listeners are associated// to the same event (default priority = 0; higher numbers = listener is run earlier)'priority'=>500,# you can also restrict listeners to a specific Doctrine connection'connection'=>'default',]);

Tip

Symfony loads (and instantiates) Doctrine listeners only when the related
Doctrine event is actually fired; whereas Doctrine subscribers are always
loaded (and instantiated) by Symfony, making them less performant.

Entity listeners are defined as PHP classes that listen to a single Doctrine
event on a single entity class. For example, suppose that you want to send some
notifications whenever a User entity is modified in the database. To do so,
define a listener for the postUpdate Doctrine event:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// src/EventListener/UserChangedNotifier.phpnamespaceApp\EventListener;useApp\Entity\User;useDoctrine\Persistence\Event\LifecycleEventArgs;classUserChangedNotifier{// the entity listener methods receive two arguments:// the entity instance and the lifecycle eventpublicfunctionpostUpdate(User$user,LifecycleEventArgs$event){// ... do something to notify the changes}}

The next step is to enable the Doctrine listener in the Symfony application by
creating a new service for it and tagging it
with the doctrine.orm.entity_listener tag:

YAML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# config/services.yamlservices:# ...App\EventListener\UserChangedNotifier:tags:-# these are the basic options that define the entity listenername:'doctrine.orm.entity_listener'event:'postUpdate'entity:'App\Entity\User'# set the 'lazy' option to TRUE to only instantiate listeners when they are usedlazy:true# you can also associate an entity listener to a specific entity managerentity_manager:'custom'# by default, Symfony looks for a method called after the event (e.g. postUpdate())# if it doesn't exist, it tries to execute the '__invoke()' method, but you can# configure a custom method name with the 'method' optionmethod:'checkUserChanges'

XML

1
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

<!-- config/services.xml --><?xml version="1.0" ?><containerxmlns="http://symfony.com/schema/dic/services"xmlns:doctrine="http://symfony.com/schema/dic/doctrine"><services><!-- ... --><serviceid="App\EventListener\UserChangedNotifier"><!-- * 'event' and 'entity' are the basic options that define the entity listener * 'lazy': if TRUE, listeners are only instantiated when they are used * 'entity_manager': define it if the listener is not associated to the * default entity manager * 'method': by default, Symfony looks for a method called after the event (e.g. postUpdate()) * if it doesn't exist, it tries to execute the '__invoke()' method, but * you can configure a custom method name with the 'method' option --><tagname="doctrine.orm.entity_listener"event="postUpdate"entity="App\Entity\User"lazy="true"entity_manager="custom"method="checkUserChanges"/></service></services></container>

PHP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// config/services.phpuseApp\Entity\User;useApp\EventListener\UserChangedNotifier;$container->autowire(UserChangedNotifier::class)->addTag('doctrine.orm.entity_listener',[// these are the basic options that define the entity listener'event'=>'postUpdate','entity'=>User::class,// set the 'lazy' option to TRUE to only instantiate listeners when they are used'lazy'=>true,// you can also associate an entity listener to a specific entity manager'entity_manager'=>'custom',// by default, Symfony looks for a method called after the event (e.g. postUpdate())// if it doesn't exist, it tries to execute the '__invoke()' method, but you can// configure a custom method name with the 'method' option'method'=>'checkUserChanges',]);

New in version 4.4: Support for invokable listeners (using the __invoke() method) was introduced in Symfony 4.4.

Lifecycle subscribers are defined as PHP classes that implement the
Doctrine\Common\EventSubscriber interface and which listen to one or more
Doctrine events on all the application entities. For example, suppose that you
want to log all the database activity. To do so, define a subscriber for the
postPersist, postRemove and postUpdate Doctrine events:

// src/EventListener/DatabaseActivitySubscriber.phpnamespaceApp\EventListener;useApp\Entity\Product;useDoctrine\Common\EventSubscriber;useDoctrine\ORM\Events;useDoctrine\Persistence\Event\LifecycleEventArgs;classDatabaseActivitySubscriberimplementsEventSubscriber{// this method can only return the event names; you cannot define a// custom method name to execute when each event triggerspublicfunctiongetSubscribedEvents(){return[Events::postPersist,Events::postRemove,Events::postUpdate,];}// callback methods must be called exactly like the events they listen to;// they receive an argument of type LifecycleEventArgs, which gives you access// to both the entity object of the event and the entity manager itselfpublicfunctionpostPersist(LifecycleEventArgs$args){$this->logActivity('persist',$args);}publicfunctionpostRemove(LifecycleEventArgs$args){$this->logActivity('remove',$args);}publicfunctionpostUpdate(LifecycleEventArgs$args){$this->logActivity('update',$args);}privatefunctionlogActivity(string$action,LifecycleEventArgs$args){$entity=$args->getObject();// if this subscriber only applies to certain entity types,// add some code to check the entity type as early as possibleif(!$entityinstanceofProduct){return;}// ... get the entity information and log it somehow}}

The next step is to enable the Doctrine subscriber in the Symfony application by
creating a new service for it and tagging it
with the doctrine.event_subscriber tag:

Symfony loads (and instantiates) Doctrine subscribers whenever the
application executes; whereas Doctrine listeners are only loaded when the
related event is actually fired, making them more performant.