События Doctrine

Дата обновления перевода 2024-07-19

События Doctrine

Doctrine, набор PHP библиотек используемый в Symfony для работы с базами данных, предоставляет легковесную систему событий для обновления сущностей во время выполнения приложения. Эти события называются lifecycle events и дают возможность выполнять задачи вроде "обновить свойство createdAt автоматически прямо перед persist сущности данного типа".

Doctrine запускает события до/после выполнения наиболее частых операций с сущностью (например, prePersist/postPersist, preUpdate/postUpdate) а также при других частых задачах (e.g. loadClassMetadata, onClear).

Есть несколько способов слушать эти события Doctrine:

  • Обратные вызовы жизненного цикла, они определяются как публичные методы в классах сущностей Они не могут использовать сервисы, поэтому предназначены для очень простой логики, связанной с с одной сущностью;
  • Слушатели сущностей, они определяются как классы с методами обратного вызова для событий, на которые вы хотите реагировать. Они могут использовать сервисы, но вызываются только для сущностей определенного класса, поэтому они идеально подходят для сложной логики событий, связанной с одной сущностью;
  • Слушатели жизненного цикла, они похожи на слушателей сущностей, но их методы событий вызываются для всех сущностей, а не только для тех, которые относятся к определенному типу. Они идеально подходят для обмена логикой событий между сущностями.

Производительность каждого типа слушателей зависит от того, к какому количеству сущностей они применяются: обратные вызовы жизненного цикла быстрее, чем слушатели сущностей, которые, в свою очередь, быстрее чем слушатели жизненного цикла.

Эта статья объясняет только основы того, как события Doctrine используются в приложениях Symfony. Прочитайте официальную документацию о событиях Doctrine чтобы узнать о них детальнее.

See also

Эта статья покрывает listeners и subscribers для Doctrine ORM. Если вы используете ODM для MongoDB, прочитайте документацию к DoctrineMongoDBBundle.

Обратные вызовы жизненного цикла Doctrine

Lifecycle callbacks определяются как методы внутри сущности, которую вы хотите изменить. Например, предположим, что вы хотите установить колонку с датой createdAt в текущую дату, то только когда к сущности применяется первый раз persist (то есть, добавление новой записи). Чтобы сделать это, определите callback для события Doctrine prePersist:

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. Например, предположим, что вы хотите отправить несколько уведомлений, когда 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\ORM\Event\PostUpdateEventArgs;

class UserChangedNotifier
{
    // методы слушателя сущности получают два аргумента:
    // экземпляр сущности и событие жизненного цикла
    public function postUpdate(User $user, PostUpdateEventArgs $event): void
    {
        // ... сделайте что-то, чтобы уведомить об изменениях
    }
}

Затем, добавьте атрибут #[AsEntityListener] к классу, чтобы включить его в качестве слушателя сущностей Doctrine в вашем приложении:

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
{
    // ...
}

Как вариант, если вы предпочитаете не использовать PHP-атрибуты, вы должны сконфигурировать сервис для слушателя сущностей и добавить тег doctrine.orm.entity_listener следующим образом:

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

Слушатели жизненного цикла определяются как 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\ORM\Event\PostPersistEventArgs;

class SearchIndexer
{
    // методы слушателя получают аргумент, который дает вам доступ и к
    // сущности объекта события, и к сущности самого менеджера
    public function postPersist(PostPersistEventArgs $args): void
    {
        $entity = $args->getObject();

        // если этот слушатель применяется только к определенным типам сущностей,
        // добавьте код для проверки сущности как можно раньше
        if (!$entity instanceof Product) {
            return;
        }

        $entityManager = $args->getObjectManager();
        // ... сделать что-то с сущностью Product
    }
}

Note

В предыдущих версиях Doctrine, вместо PostPersistEventArgs вам нужно было использовать LifecycleEventArgs, что устарело в 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
{
    // ...
}

Как вариант, если вы не хотите использовать атрибуты PHP, вы должны включить слушателя в приложении Symfony, создав для него новый сервис и
добавив тег doctrine.event_listener:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/App/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
    {
        // ...
    }
}

2.7.2

Атрибут AsDoctrineListener был представлен в DoctrineBundle 2.7.2.

Tip

Значение опции connection также может быть параметром конфигурации .