События Doctrine
Дата обновления перевода 2023-01-18
События Doctrine
Doctrine, набор PHP библиотек используемый в Symfony для работы с базами данных, предоставляет легковесную систему событий для обновления сущностей во время выполнения приложения. Эти события называются lifecycle events и дают возможность выполнять задачи вроде "обновить свойство createdAt автоматически прямо перед persist сущности данного типа".
Doctrine запускает события до/после выполнения наиболее частых операций с сущностью
(например, prePersist/postPersist
, preUpdate/postUpdate
) а также
при других частых задачах (e.g. loadClassMetadata
, onClear
).
Есть несколько способов слушать эти события Doctrine:
- Обратные вызовы жизненного цикла, они определяются как методы в классах сущностей и вызываются, когда срабатывают события;
- Слушатели и подписчики жизненного цикла, это классы с callback методами для одного или нескольких событий и вызываются для всех сущностей;
- Слушатели сущностей, они похожи на lifecycle listeners, но они вызываются только для сущностей определённого класса.
У каждого из них есть недостатки и преимущества:
- У обратных вызовов лучше производительность, потому что они применяются только к сущностям одного класса, но вы не можете переиспользовать логику в разных классах и они не имеют доступа к сервисам Symfony;
- Слушатели и подписчики жизненного цикла могут переиспользовать логику в разных сущностях и имеют доступ к сервисам Symonfy, но их производительность хуже, так как они вызываются для всех сущностей;
- Слущатели сущностей имеют те же преимущества, что и lifecycle listeners и у них лучше производительность, потому что они применяются к одному классу сущности.
Эта статья объясняет только основы того, как события Doctrine используются в приложениях Symfony. Прочитайте официальную документацию о событиях Doctrine чтобы узнать о них детальнее.
See also
Эта статья покрывает listeners и subscribers для Doctrine ORM. Если вы используете ODM для MongoDB, прочитайте документацию к DoctrineMongoDBBundle.
Обратные вызовы жизненного цикла Doctrine
Lifecycle callbacks определяются как методы внутри сущности, которую вы хотите изменить.
Например, предположим, что вы хотите установить колонку с датой createdAt
в текущую
дату, то только когда к сущности применяется первый раз persist (то есть, добавление новой записи). Чтобы сделать это,
определите callback для события Doctrine prePersist
:
- Attributes
- YAML
- XML
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;
// При использовании аннотаций не забудьте добавить @ORM\HasLifecycleCallbacks()
// к классу сущности там, где вы определяете обратный вызов
#[ORM\Entity]
#[ORM\HasLifecycleCallbacks]
class Product
{
// ...
#[ORM\PrePersist]
public function setCreatedAtValue(): void
{
$this->createdAt = new \DateTimeImmutable();
}
}
Note
Некоторые обратные вызовы жизненного цикла получают аргумент, который предоставляет доступ к
полезной информации, такой как текущий entity manager (например,preUpdate
callback получает аргумент PreUpdateEventArgs $event
).
Слушатели жизненного цикла Doctrine
Слушатели жизненного цикла определяются как PHP-классы, которые слушают одно событие Doctrine
для всех сущностей приложения. Например, предположим, что вы хотите
обновить поисковый индекс, каждый раз, когда новая 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\Persistence\Event\LifecycleEventArgs;
class SearchIndexer
{
// методы слушателя получают аргумент, который дает вам доступ и к
// сущности объекта события, и к сущности самого менеджера
public function postPersist(LifecycleEventArgs $args): void
{
$entity = $args->getObject();
// если этот слушатель применяется только к определенным типам сущностей,
// добавьте код для проверки сущности как можно раньше
if (!$entity instanceof Product) {
return;
}
$entityManager = $args->getObjectManager();
// ... сделать что-то с сущностью Product
}
}
Следующий шаг - включить Doctrine listener в приложение Symfony
создав новый сервис для него и добавить тег
doctrine.event_listener
:
- YAML
- XML
- PHP
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'
# это единственная обязательная опция тега слушателя жизненного цикла
event: 'postPersist'
# слушатели могут определять свою приоритетность в случае, если несколько слушателей
# связаны с одним событием (приоритет по умолчанию = 0; чем больше цифра = тем раньше запускается слушатель)
priority: 500
# вы также можете ограничить слушателей по определенному соединению Doctrine
connection: 'default'
Tip
Symfony загружает (и инстанциирует) Doctrine listeners только, когда связанное событие Doctrine действительно вызывается; а Doctrine subscribers всегда загружаются (и инстанциируются) Symfony, делая их менее производительными.
Tip
Значение опции connection
также может быть
параметром конфигурации .
Слушатели сущностей Doctrine
Слушатели сущностей определяются как 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\Persistence\Event\LifecycleEventArgs;
class UserChangedNotifier
{
// методы слушателя сущности получают два аргумента:
// экземпляр сущности и событие жизненного цикла
public function postUpdate(User $user, LifecycleEventArgs $event)
{
// ... сделайте что-то, чтобы уведомить об изменениях
}
}
Следующий шаг - включить Doctrine listener в приложении Symfony
создав новый сервис и добавить тег
doctrine.orm.entity_listener
:
- YAML
- XML
- PHP
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:
-
# это опции, необходимые для определения слушателя сущности
name: 'doctrine.orm.entity_listener'
event: 'postUpdate'
entity: 'App\Entity\User'
# это другие опции, которые вы можете определить при необходимости
# установите опцию 'lazy' как TRUE, чтобы инстанциировать слушателей только при использовании
# lazy: true
# установите опцию 'entity_manager', если слушатель не ассоциирован с менеджером по умолчанию
# entity_manager: 'custom'
# по умолчанию, Symfony ищет метод, вызываемый после события (например, postUpdate())
# если он не существует, она пытается выполнить метод '__invoke()', но вы можете
# сконфигурировать пользовательское имя метода с опцией 'method'
# method: 'checkUserChanges'
Подписчики жизненного цикла Doctrine
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\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface;
use Doctrine\ORM\Events;
use Doctrine\Persistence\Event\LifecycleEventArgs;
class DatabaseActivitySubscriber implements EventSubscriberInterface
{
// этот метод может вернуть только имена событий; вы не можете определить
// пользовательское имя метода для выполнения при запуске каждого события
public function getSubscribedEvents(): array
{
return [
Events::postPersist,
Events::postRemove,
Events::postUpdate,
];
}
// методы обратного вызова должны быть вызваны точно так же, как и события, которые они слушают;
// они получают аргумент типа LifecycleEventArgs, который дает вам доступ
// и к объекту сущности события, и к самому менеджеру сущности
public function postPersist(LifecycleEventArgs $args): void
{
$this->logActivity('persist', $args);
}
public function postRemove(LifecycleEventArgs $args): void
{
$this->logActivity('remove', $args);
}
public function postUpdate(LifecycleEventArgs $args): void
{
$this->logActivity('update', $args);
}
private function logActivity(string $action, LifecycleEventArgs $args): void
{
$entity = $args->getObject();
// если этот подписчик применяется только к определенному типу сущностей,
// добавьте код, чтобы проверить тип сущности как можно раньше
if (!$entity instanceof Product) {
return;
}
// ... получите информацию о сущности и запишите ее каким-либо образом
}
}
Если вы используете конфигурацию services.yaml по умолчанию
и DoctrineBundle 2.1 (дата релиза 25 мая, 2020) или новее, этот пример уже будет работать!
В других случаях, , создайте сервис для этого
подписчика и добавьте к нему тег doctrine.event_subscriber
.
Если вам нужно сконфигурировать некоторую опцию подписчика (например, его приоритетность или соединение Doctrine для использования), вы должны сделать это в конфигурации сервиса вручную:
- YAML
- XML
- PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# config/services.yaml
services:
# ...
App\EventListener\DatabaseActivitySubscriber:
tags:
- name: 'doctrine.event_subscriber'
# подписчики определяют приоритетность в случае асоциирования нескольких подписчиков или слушателей
# с одним и тем же событием (приоритет по умолчанию = 0; чем больше цифра = тем раньше запускается слушатель)
priority: 500
# вы также можете ограничить слушателей по определенному соединению Doctrine
connection: 'default'
Tip
Symfony загружает (и инстанциирует) Doctrine subscribers каждый раз при запуске приложения; а Doctrine listeners загружаются только тогда, когда связанное событие действительно срабатывает, поэтому они меньше влияют на производительность.