From 4a4d9ded8c1ff11f8ee659dc42594e5a878a675d Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 18 Sep 2023 16:02:34 +0200 Subject: [PATCH] [Doctrine] Update the article about Doctrine events --- doctrine/events.rst | 365 ++++++++++++++------------------------------ 1 file changed, 113 insertions(+), 252 deletions(-) diff --git a/doctrine/events.rst b/doctrine/events.rst index 411b83745c2..4046191998a 100644 --- a/doctrine/events.rst +++ b/doctrine/events.rst @@ -13,23 +13,20 @@ 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 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 :doc:`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. +* **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`_ @@ -105,174 +102,6 @@ define a callback for the ``prePersist`` Doctrine event: useful information such as the current entity manager (e.g. the ``preUpdate`` callback receives a ``PreUpdateEventArgs $event`` argument). -.. _doctrine-lifecycle-listener: - -Doctrine Lifecycle Listeners ----------------------------- - -.. deprecated:: 6.3 - - Lifecycle subscribers are deprecated starting from Symfony 6.3 and will be - removed in Symfony 7.0. Use lifecycle listeners instead. - -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:: - - // 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:: - - // 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 -:doc:`tagging it ` with the ``doctrine.event_listener`` tag: - -.. configuration-block:: - - .. code-block:: php-attributes - - // 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 - { - // ... - } - } - - .. code-block:: yaml - - # 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 multiple subscribers or 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' - - .. code-block:: xml - - - - - - - - - - - - - - - .. code-block:: php - - // 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 subscribers or 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', - ]) - ; - }; - -.. versionadded:: 2.7.2 - - The `AsDoctrineListener`_ attribute was introduced in DoctrineBundle 2.7.2. - -.. 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. - -.. tip:: - - The value of the ``connection`` option can also be a - :ref:`configuration parameter `. - Doctrine Entity Listeners ------------------------- @@ -414,99 +243,103 @@ with the ``doctrine.orm.entity_listener`` tag as follows: ; }; -Doctrine Lifecycle Subscribers ------------------------------- +.. _doctrine-lifecycle-listener: -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:: +Doctrine Lifecycle Listeners +---------------------------- - // src/EventListener/DatabaseActivitySubscriber.php +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:: + + // src/EventListener/SearchIndexer.php namespace App\EventListener; use App\Entity\Product; - use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface; use Doctrine\ORM\Event\PostPersistEventArgs; - use Doctrine\ORM\Event\PostRemoveEventArgs; - use Doctrine\ORM\Event\PostUpdateEventArgs; - use Doctrine\ORM\Events; - class DatabaseActivitySubscriber implements EventSubscriberInterface + class SearchIndexer { - // this method can only return the event names; you cannot define a - // custom method name to execute when each event triggers - public function getSubscribedEvents(): array - { - 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 Post*EventArgs, which gives you access - // to both the entity object of the event and the entity manager itself + // 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 { - $this->logActivity('persist', $args->getObject()); - } - - public function postRemove(PostRemoveEventArgs $args): void - { - $this->logActivity('remove', $args->getObject()); - } - - public function postUpdate(PostUpdateEventArgs $args): void - { - $this->logActivity('update', $args->getObject()); - } + $entity = $args->getObject(); - private function logActivity(string $action, mixed $entity): void - { - // if this subscriber only applies to certain entity types, + // 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; } - // ... get the entity information and log it somehow + $entityManager = $args->getObjectManager(); + // ... do something with the Product entity } } .. note:: - In previous Doctrine versions, instead of ``Post*EventArgs`` classes, you had + In previous Doctrine versions, instead of ``PostPersistEventArgs``, you had to use ``LifecycleEventArgs``, which was deprecated in Doctrine ORM 2.14. -If you're using the :ref:`default services.yaml configuration ` -and DoctrineBundle 2.1 (released May 25, 2020) or newer, this example will already -work! Otherwise, :ref:`create a service ` for this -subscriber and :doc:`tag it ` with ``doctrine.event_subscriber``. +Then, add the ``#[AsDoctrineListener]`` attribute to the class to enable it as +a Doctrine listener in your application:: -If you need to configure some option of the subscriber (e.g. its priority or -Doctrine connection to use) you must do that in the manual service configuration: + // 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 +:doc:`tagging it ` with the ``doctrine.event_listener`` tag: .. configuration-block:: + .. code-block:: php-attributes + + // 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 + { + // ... + } + } + .. code-block:: yaml # config/services.yaml services: # ... - App\EventListener\DatabaseActivitySubscriber: + App\EventListener\SearchIndexer: tags: - - name: 'doctrine.event_subscriber' + - + name: 'doctrine.event_listener' + # this is the only required option for the lifecycle listener tag + event: 'postPersist' - # subscribers can define their priority in case multiple subscribers or listeners are associated - # to the same event (default priority = 0; higher numbers = listener is run earlier) - priority: 500 + # listeners can define their priority in case multiple subscribers or 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' + # you can also restrict listeners to a specific Doctrine connection + connection: 'default' .. code-block:: xml @@ -518,12 +351,16 @@ Doctrine connection to use) you must do that in the manual service configuration - - + + @@ -533,28 +370,52 @@ Doctrine connection to use) you must do that in the manual service configuration // config/services.php namespace Symfony\Component\DependencyInjection\Loader\Configurator; - use App\EventListener\DatabaseActivitySubscriber; + use App\EventListener\SearchIndexer; return static function (ContainerConfigurator $container): void { $services = $container->services(); - $services->set(DatabaseActivitySubscriber::class) - ->tag('doctrine.event_subscriber'[ - // subscribers can define their priority in case multiple subscribers or listeners are associated + // 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 subscribers or 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 + # you can also restrict listeners to a specific Doctrine connection 'connection' => 'default', ]) ; }; +.. versionadded:: 2.7.2 + + The `AsDoctrineListener`_ attribute was introduced in DoctrineBundle 2.7.2. + .. tip:: - 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. + 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. + +.. tip:: + + The value of the ``connection`` option can also be a + :ref:`configuration parameter `. + +Doctrine Lifecycle Subscribers +------------------------------ + +.. deprecated:: 6.3 + + Lifecycle subscribers are deprecated starting from Symfony 6.3. + +This was another way of listening to events provided by Doctrine. However, they +were deprecated in Symfony 6.3 and it's no longer recommended to use them. +Instead, use any of the other alternatives shown above. .. _`Doctrine`: https://www.doctrine-project.org/ .. _`lifecycle events`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/events.html#lifecycle-events