Дата обновления перевода: 2020-01-19
События Doctrine¶
Doctrine, набор PHP библиотек используемый в Symfony для работы с базами данных, предоставляет легковесную систему событий для обновления сущностей во время выполнения приложения. Эти события называются lifecycle events и дают возможность выполнять задачи вроде "обновить свойство createdAt автоматически прямо перед persist сущности данного типа".
Doctrine запускает события до/после выполнения наиболее частых операций с сущностью
(например, prePersist/postPersist
, preUpdate/postUpdate
) а также
при других частых задачах (e.g. loadClassMetadata
, onClear
).
Есть несколько способов слушать эти события Doctrine:
- Lifecycle callbacks, они определяются как методы в классах сущностей и вызываются, когда срабатывают события;
- Lifecycle listeners and subscribers, это классы с callback методами для одного или нескольких событий и вызываются для всех сущностей;
- Entity listeners, они похожи на lifecycle listeners, но они вызываются только для сущностей определённого класса.
У каждого из них есть недостатки и преимущества:
- У callbacks лучше производительность, потому что они применяются только к сущностям одного класса, но вы не можете переиспользовать логику в разных классах и они не имеют доступа к сервисам Symfony;
- Lifecycle listeners и subscribers могут переиспользовать логику в разных сущностях и имеют доступ к сервисам Symonfy, но их производительность хуже, так как они вызываются для всех сущностей;
- Entity listeners имеют те же преимущества, что и lifecycle listeners и у них лучше производительность, потому что они применяются к одному классу сущности.
Эта статья объясняет только основы того, как события Doctrine используются в приложениях Symfony. Прочитайте официальную документацию о событиях Doctrine чтобы узнать о них детальнее.
Doctrine Lifecycle Callbacks¶
Lifecycle callbacks определяются как методы внутри сущности, которую вы хотите изменить.
Например, предположим, что вы хотите установить колонку с датой createdAt
в текущую
дату, то только когда к сущности применяется первый раз persist (то есть, добавление новой записи). Чтобы сделать это,
определите callback для события Doctrine prePersist
:
- 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.php use Doctrine\ORM\Mapping as ORM; // 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() */ class Product { // ... /** * @ORM\PrePersist */ public function setCreatedAtValue() { $this->createdAt = new \DateTime(); } }
- YAML
1 2 3 4 5 6
# config/doctrine/Product.orm.yml App\Entity\Product: type: entity # ... lifecycleCallbacks: prePersist: ['setCreatedAtValue']
- XML
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
Некоторые lifecycle callbacks получают аргумент, который предоставляет доступ к
полезной информации, такой как текущий entity manager (например,``preUpdate``
callback получает аргумент PreUpdateEventArgs $event
).
Doctrine Lifecycle Listeners¶
Lifecycle listeners определяются как PHP-классы, которые слушают одно событие Doctrine
для всех entities приложения. Например, предположим, что вы хотите
обновить поисковый индекс, каждый раз, когда новая entity добавляется (persist) в DB. Чтобы
сделать это, объявите listener для события Doctrine postPersist
:
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\Common\Persistence\Event\LifecycleEventArgs;
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(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 possible
if (!$entity instanceof Product) {
return;
}
$entityManager = $args->getObjectManager();
// ... do something with the Product entity
}
}
|
Следующий шаг - включить Doctrine listener в приложение Symfony
создав новый сервис для него и добавить тег
doctrine.event_listener
:
- YAML
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 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'
- XML
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" ?> <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>
- PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// config/services.php use App\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 загружает (и инициализирует) Doctrine listeners только, когда связанное событие Doctrine действительно вызывается; а Doctrine subscribers всегда загружаются (и инициализируются) Symfony, делая их менее производительными.
Doctrine Entity Listeners¶
Entity listeners определяются как PHP-классы, которые слушают одно событие Doctrine
для однго класса entity. Например, предположим, что вы хотите отправить несколько
уведомлений, когда entity User
изменяется в DB. Для этого
определите listener для Doctrine события postUpdate
:
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\Common\Persistence\Event\LifecycleEventArgs;
class UserChangedNotifier
{
// the entity listener methods receive two arguments:
// the entity instance and the lifecycle event
public function postUpdate(User $user, LifecycleEventArgs $event)
{
// ... do something to notify the changes
}
}
|
Следующий шаг - включить Doctrine listener в приложении Symfony
создав новый сервис и добавить тег
doctrine.orm.entity_listener
:
- 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.yaml services: # ... App\EventListener\UserChangedNotifier: tags: - # these are the basic options that define the entity listener name: 'doctrine.orm.entity_listener' event: 'postUpdate' entity: 'App\Entity\User' # 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'
- 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" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:doctrine="http://symfony.com/schema/dic/doctrine"> <services> <!-- ... --> <service id="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 --> <tag name="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.php use App\Entity\User; use App\EventListener\UserChangedNotifier; $container->autowire(UserChangedNotifier::class) ->addTag('doctrine.orm.event_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', ]) ;
Doctrine Lifecycle Subscribers¶
Lifecycle subscribers определяются как PHP-классы которые реализуют интерфейс
Doctrine\Common\EventSubscriber
и которые слушают один или несколько
событий Doctrine на все entity приложения. Например, предположим вы
хотите логировать всю активность DB. Для этого определите subscriber для
событий Doctrine postPersist
, postRemove
и postUpdate
:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | // src/EventListener/DatabaseActivitySubscriber.php
namespace App\EventListener;
use App\Entity\Product;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
class DatabaseActivitySubscriber implements EventSubscriber
{
// this method can only return the event names; you cannot define a
// custom method name to execute when each event triggers
public function getSubscribedEvents()
{
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 itself
public function postPersist(LifecycleEventArgs $args)
{
$this->logActivity('persist', $args);
}
public function postRemove(LifecycleEventArgs $args)
{
$this->logActivity('remove', $args);
}
public function postUpdate(LifecycleEventArgs $args)
{
$this->logActivity('update', $args);
}
private function logActivity(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 possible
if (!$entity instanceof Product) {
return;
}
// ... get the entity information and log it somehow
}
}
|
Затем включите Doctrine subscriber в приложение Symfony
создав новый сервис для него и добавив тег
doctrine.event_subscriber
:
- YAML
1 2 3 4 5 6 7
# config/services.yaml services: # ... App\EventListener\DatabaseActivitySubscriber: tags: - { name: 'doctrine.event_subscriber' }
- XML
1 2 3 4 5 6 7 8 9 10 11 12
<!-- config/services.xml --> <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:doctrine="http://symfony.com/schema/dic/doctrine"> <services> <!-- ... --> <service id="App\EventListener\DatabaseActivitySubscriber"> <tag name="doctrine.event_subscriber"/> </service> </services> </container>
- PHP
1 2 3 4 5 6
// config/services.php use App\EventListener\DatabaseActivitySubscriber; $container->autowire(DatabaseActivitySubscriber::class) ->addTag('doctrine.event_subscriber') ;
Если вам нужно привязать subscriber к определённому соединению Doctrine, вы можете сделать это в конфигурации сервиса:
- YAML
1 2 3 4 5 6 7
# config/services.yaml services: # ... App\EventListener\DatabaseActivitySubscriber: tags: - { name: 'doctrine.event_subscriber', connection: 'default' }
- XML
1 2 3 4 5 6 7 8 9 10 11 12
<!-- config/services.xml --> <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:doctrine="http://symfony.com/schema/dic/doctrine"> <services> <!-- ... --> <service id="App\EventListener\DatabaseActivitySubscriber"> <tag name="doctrine.event_subscriber" connection="default"/> </service> </services> </container>
- PHP
1 2 3 4 5 6
// config/services.php use App\EventListener\DatabaseActivitySubscriber; $container->autowire(DatabaseActivitySubscriber::class) ->addTag('doctrine.event_subscriber', ['connection' => 'default']) ;
Tip
Symfony загуржает (и инициализирует) Doctrine subscribers каждый раз при запуске приложения; а Doctrine listeners загружаются только тогда, когда связанное событие действительно срабатывает, поэтому они меньше влияют на производительность.
Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.