Слушатели и подписчики событий Doctrine

Пакеты Doctrine имеют богатую систему событий, которая выдаёт события почти всегда, когда в системе что-либо происходит. Для вас, это значит, что вы можете создавать произвольные сервисы и говорить Doctrine уведомлять эти объекты, когда с Doctrine происходит некоторое действие (например, prePersist()). Это будет полезным, например, чтобы создать независимый поисковый индекс при сохранении объекта в вашу базу данных.

Doctrine определяет два типа объектов, которые могут слушать события Doctrine: слушатели и подписчики. Оба типа схожи, но слушатели слегка более прямые. Чтобы узнать больше, смотрите Система событий на сайте Doctrine.

Веб-страница Doctrine также разъясняет все существующие события, которые можно слушать.

Конфигурация слушателя/подписчика

Чтобы зарегистрировать сервис так, чтобы он вёл себя как слушатель событий или подписчик, вам нужно всего-лишь тегировать его с помощью правильного имени. В зависимости от вашего примера использования, вы можете подключить слушателя к каждому соединению DBAL и менеджеру сущностей ORM, или только к одному конкретному соединению DBAL и ко всем менеджерам сущностей, которые это соединение используют.

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    services:
        # ...
    
        App\EventListener\SearchIndexer:
            tags:
                - { name: doctrine.event_listener, event: postPersist }
        App\EventListener\SearchIndexer2:
            tags:
                - { name: doctrine.event_listener, event: postPersist, connection: default }
        App\EventListener\SearchIndexerSubscriber:
            tags:
                - { name: doctrine.event_subscriber, connection: default }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <?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\SearchIndexer">
                <tag name="doctrine.event_listener" event="postPersist" />
            </service>
            <service id="App\EventListener\SearchIndexer2">
                <tag name="doctrine.event_listener" event="postPersist" connection="default" />
            </service>
            <service id="App\EventListener\SearchIndexerSubscriber">
                <tag name="doctrine.event_subscriber" connection="default" />
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    use App\EventListener\SearchIndexer;
    use App\EventListener\SearchIndexer2;
    use App\EventListener\SearchIndexerSubscriber;
    
    $container->autowire(SearchIndexer::class)
        ->addTag('doctrine.event_listener', array('event' => 'postPersist'))
    ;
    $container->autowire(SearchIndexer2::class)
        ->addTag('doctrine.event_listener', array(
            'event' => 'postPersist',
            'connection' => 'default',
        ))
    ;
    $container->autowire(SearchIndexerSubscriber::class)
        ->addTag('doctrine.event_subscriber', array('connection' => 'default'))
    ;
    

Создание класса слушателя

В предыдущем примере, сервис SearchIndexer был сконфигуррован как Doctrine слушатель события postPersist. Класс, стоящий за этим сервисом, должен иметь метод postPersist(), который будет вызван при осуществлении события:

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

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

class SearchIndexer
{
    public function postPersist(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();

        // only act on some "Product" entity
        if (!$entity instanceof Product) {
            return;
        }

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

В каждом событии, у вас есть доступ к объекту LifecycleEventArgs, который даёт вам доступ и к объекту сущности события, и к самому менеджеру.

Важно заметить одну вещь: слушатель будет слушать все сущности в вашем приложении. Так что если вы заинтересованы только в каком-то конкретном типе сущностей (например, сушности Product, но не сущности``BlogPost``), вам нужно проверить тип класса сущности в вашем методе (как показано выше).

Tip

В версии Doctrine 2.4, была представлена функция под названием "Слушатели сущностей". Это класс слушателя жизненного цикла, используемый для сущности. Вы можете прочитать о нём в Документации Doctrine.

Создание класса подписчика

Подписчик событий Doctrine должен внедрять интерфейс Doctrine\Common\EventSubscriber и иметь метод события для каждого события, на которое он подписывается:

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

use Doctrine\Common\EventSubscriber;
// для Doctrine < 2.4: используйте Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use App\Entity\Product;

class SearchIndexerSubscriber implements EventSubscriber
{
    public function getSubscribedEvents()
    {
        return array(
            'postPersist',
            'postUpdate',
        );
    }

    public function postUpdate(LifecycleEventArgs $args)
    {
        $this->index($args);
    }

    public function postPersist(LifecycleEventArgs $args)
    {
        $this->index($args);
    }

    public function index(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();

        // возможно, вы хотите действовать только в одной сущности "Product"
        if ($entity instanceof Product) {
            $entityManager = $args->getEntityManager();
            // ... сделать что-то с Товаром
        }
    }
}

Tip

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

Для полной справки, смотрите главу Система событий в документации Doctrine.

Ленивая загрузка слушателей событий

Небольшой разницей между слушателями и подписчиками является то, что Symfony может загружать слушателей сущностей лениво. Это означает, что ваш класс слушателя будет вызван из сервис-контейнера (следовательно, инстанциирован) только тогда, когда связанное с ним событие действительно произойдёт.

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

Чтобы отметить сервис слушателя как лениво загружаемый, просто добавьте к тегу атрибут lazy:

  • YAML
    1
    2
    3
    4
    services:
        App\EventListener\SearchIndexer:
            tags:
                - { name: doctrine.event_listener, event: postPersist, lazy: true }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <?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\SearchIndexer" autowire="true">
                <tag name="doctrine.event_listener" event="postPersist" lazy="true" />
            </service>
        </services>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    use App\EventListener\SearchIndexer;
    
    $container
        ->autowire(SearchIndexer::class)
        ->addTag('doctrine.event_listener', array('event' => 'postPersist', 'lazy' => 'true'))
    ;
    

Note

Отметка``lazy`` слушателя событий не имеет никакого отношения к ленивым определениям сервиса, который описаны в своём разделе

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.