Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • Platform.sh for Symfony Best platform to deploy Symfony apps
    • SymfonyInsight Automatic quality checks for your apps
    • Symfony Certification Prove your knowledge and boost your career
    • SensioLabs Professional services to help you with Symfony
    • Blackfire Profile and monitor performance of your apps
  • Other
  • Blog
  • Download
sponsored by
  1. Home
  2. Documentation
  3. Doctrine
  4. Doctrine Events

Doctrine Events

Edit this page

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 right 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 public methods on the entity classes. They can't use services, so they are intended for very simple logic related to a single entity;
  • Entity listeners, they are defined as classes with callback methods for the events you want to respond to. They can use services, but they are only called for the entities of a certain class, so they are ideal for complex event logic related to a single entity;
  • Lifecycle listeners, they are similar to entity listeners but their event methods are called for all entities, not only those of a certain type. They are ideal to share event logic between entities.

The performance of each type of listener depends on how many entities applies to: lifecycle callbacks are faster than entity listeners, which in turn are faster than lifecycle listeners.

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.

See also

This article covers listeners for Doctrine ORM. If you are using ODM for MongoDB, read the DoctrineMongoDBBundle documentation.

Doctrine Lifecycle Callbacks

Lifecycle callbacks are defined as public 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Entity/Product.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

// When using attributes, don't forget to add #[ORM\HasLifecycleCallbacks]
// to the class of the entity where you define the callback

#[ORM\Entity]
#[ORM\HasLifecycleCallbacks]
class Product
{
    // ...

    #[ORM\PrePersist]
    public function setCreatedAtValue(): void
    {
        $this->createdAt = new \DateTimeImmutable();
    }
}
1
2
3
4
5
6
# config/doctrine/Product.orm.yml
App\Entity\Product:
    type: entity
    # ...
    lifecycleCallbacks:
        prePersist: ['setCreatedAtValue']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- config/doctrine/Product.orm.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
        https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

    <entity name="App\Entity\Product">
        <!-- ... -->
        <lifecycle-callbacks>
            <lifecycle-callback type="prePersist" method="setCreatedAtValue"/>
        </lifecycle-callbacks>
    </entity>
</doctrine-mapping>

Note

Some lifecycle callbacks receive an argument that provides access to useful information such as the current entity manager (e.g. the preUpdate callback receives a PreUpdateEventArgs $event argument).

Doctrine Entity Listeners

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.

First, define a PHP class that handles the postUpdate Doctrine event:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/EventListener/UserChangedNotifier.php
namespace App\EventListener;

use App\Entity\User;
use Doctrine\ORM\Event\PostUpdateEventArgs;

class UserChangedNotifier
{
    // the entity listener methods receive two arguments:
    // the entity instance and the lifecycle event
    public function postUpdate(User $user, PostUpdateEventArgs $event): void
    {
        // ... do something to notify the changes
    }
}

Then, add the #[AsEntityListener] attribute to the class to enable it as a Doctrine entity listener in your application:

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/EventListener/UserChangedNotifier.php
namespace App\EventListener;

// ...
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
use Doctrine\ORM\Events;

#[AsEntityListener(event: Events::postUpdate, method: 'postUpdate', entity: User::class)]
class UserChangedNotifier
{
    // ...
}

Alternatively, if you prefer to not use PHP attributes, you must configure a service for the entity listener and tag it with the doctrine.orm.entity_listener tag as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# config/services.yaml
services:
    # ...

    App\EventListener\UserChangedNotifier:
        tags:
            -
                # these are the options required to define the entity listener
                name: 'doctrine.orm.entity_listener'
                event: 'postUpdate'
                entity: 'App\Entity\User'

                # these are other options that you may define if needed

                # set the 'lazy' option to TRUE to only instantiate listeners when they are used
                # lazy: true

                # set the 'entity_manager' option if the listener is not associated to the default 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'
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
27
28
29
30
<!-- config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:doctrine="http://symfony.com/schema/dic/doctrine">
    <services>
        <!-- ... -->

        <service id="App\EventListener\UserChangedNotifier">
            <!--
                * These are the options required to define the entity listener:
                *   * name
                *   * event
                *   * entity
                *
                * These are other options that you may define if needed:
                *   * lazy: if TRUE, listeners are only instantiated when they are used
                *   * entity_manager: define it if the listener is not associated to the default 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
            -->
            <tag name="doctrine.orm.entity_listener"
                event="postUpdate"
                entity="App\Entity\User"
                lazy="true"
                entity_manager="custom"
                method="checkUserChanges"/>
        </service>
    </services>
</container>
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
27
28
29
30
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\Entity\User;
use App\EventListener\UserChangedNotifier;

return static function (ContainerConfigurator $container): void {
    $services = $container->services();

    $services->set(UserChangedNotifier::class)
        ->tag('doctrine.orm.entity_listener', [
            // These are the options required to define the entity listener:
            'event' => 'postUpdate',
            'entity' => User::class,

            // These are other options that you may define if needed:

            // set the 'lazy' option to TRUE to only instantiate listeners when they are used
            // 'lazy' => true,

            // set the 'entity_manager' option if the listener is not associated to the default 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',
        ])
    ;
};

Doctrine Lifecycle Listeners

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.php
namespace App\EventListener;

use App\Entity\Product;
use Doctrine\ORM\Event\PostPersistEventArgs;

class SearchIndexer
{
    // the listener methods receive an argument which gives you access to
    // both the entity object of the event and the entity manager itself
    public function postPersist(PostPersistEventArgs $args): void
    {
        $entity = $args->getObject();

        // if this listener only applies to certain entity types,
        // add some code to check the entity type as early as possible
        if (!$entity instanceof Product) {
            return;
        }

        $entityManager = $args->getObjectManager();
        // ... do something with the Product entity
    }
}

Note

In previous Doctrine versions, instead of PostPersistEventArgs, you had to use LifecycleEventArgs, which was deprecated in Doctrine ORM 2.14.

Then, add the #[AsDoctrineListener] attribute to the class to enable it as a Doctrine listener in your application:

1
2
3
4
5
6
7
8
9
10
11
// src/EventListener/SearchIndexer.php
namespace App\EventListener;

use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\ORM\Events;

#[AsDoctrineListener(event: Events::postPersist, priority: 500, connection: 'default')]
class SearchIndexer
{
    // ...
}

Alternatively, if you prefer to not use PHP attributes, you must enable the listener in the Symfony application by creating a new service for it and tagging it with the doctrine.event_listener tag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/EventListener/SearchIndexer.php
namespace App\EventListener;

use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\ORM\Event\PostPersistEventArgs;

#[AsDoctrineListener('postPersist'/*, 500, 'default'*/)]
class SearchIndexer
{
    public function postPersist(PostPersistEventArgs $event): void
    {
        // ...
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# config/services.yaml
services:
    # ...

    App\EventListener\SearchIndexer:
        tags:
            -
                name: 'doctrine.event_listener'
                # this is the only required option for the lifecycle listener tag
                event: 'postPersist'

                # listeners can define their priority in case 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'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:doctrine="http://symfony.com/schema/dic/doctrine">
    <services>
        <!-- ... -->

        <!--
            * 'event' is the only required option that defines the lifecycle listener
            * 'priority': used when multiple listeners are associated to the same event
            *             (default priority = 0; higher numbers = listener is run earlier)
            * 'connection': restricts the listener to a specific Doctrine connection
        -->
        <service id="App\EventListener\SearchIndexer">
            <tag name="doctrine.event_listener"
                event="postPersist"
                priority="500"
                connection="default"/>
        </service>
    </services>
</container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\EventListener\SearchIndexer;

return static function (ContainerConfigurator $container): void {
    $services = $container->services();

    // listeners are applied by default to all Doctrine connections
    $services->set(SearchIndexer::class)
        ->tag('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',
        ])
    ;
};

2.8.0

The AsDoctrineListener attribute was introduced in DoctrineBundle 2.8.0.

Tip

The value of the connection option can also be a configuration parameter.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version

    Symfony 7.1 is backed by

    Measure & Improve Symfony Code Performance

    Measure & Improve Symfony Code Performance

    Peruse our complete Symfony & PHP solutions catalog for your web development needs.

    Peruse our complete Symfony & PHP solutions catalog for your web development needs.

    Version:

    Table of Contents

    • Doctrine Lifecycle Callbacks
    • Doctrine Entity Listeners
    • Doctrine Lifecycle Listeners

    Symfony footer

    Avatar of Kirill Roskolii, a Symfony contributor

    Thanks Kirill Roskolii for being a Symfony contributor

    1 commit • 8 lines changed

    View all contributors that help us make Symfony

    Become a Symfony contributor

    Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

    Learn how to contribute

    Symfony™ is a trademark of Symfony SAS. All rights reserved.

    • What is Symfony?

      • What is Symfony?
      • Symfony at a Glance
      • Symfony Components
      • Symfony Releases
      • Security Policy
      • Logo & Screenshots
      • Trademark & Licenses
      • symfony1 Legacy
    • Learn Symfony

      • Symfony Docs
      • Symfony Book
      • Reference
      • Bundles
      • Best Practices
      • Training
      • eLearning Platform
      • Certification
    • Screencasts

      • Learn Symfony
      • Learn PHP
      • Learn JavaScript
      • Learn Drupal
      • Learn RESTful APIs
    • Community

      • Symfony Community
      • SymfonyConnect
      • Events & Meetups
      • Projects using Symfony
      • Contributors
      • Symfony Jobs
      • Backers
      • Code of Conduct
      • Downloads Stats
      • Support
    • Blog

      • All Blog Posts
      • A Week of Symfony
      • Case Studies
      • Cloud
      • Community
      • Conferences
      • Diversity
      • Living on the edge
      • Releases
      • Security Advisories
      • Symfony Insight
      • Twig
      • SensioLabs Blog
    • Services

      • SensioLabs services
      • Train developers
      • Manage your project quality
      • Improve your project performance
      • Host Symfony projects

      Powered by

    Follow Symfony