Doctrine Events

Doctrine, the set of PHP libraries used by Symfony to work with databases, provides a lightweight event system to update entities during the application execution. These events, called lifecycle events, allow to perform tasks such as "update the createdAt property automatically right before persisting entities of this type".

Symfony がデータベースを操作するために使用する PHP ライブラリのセットである Doctrine は、アプリケーションの実行中にエンティティを更新する軽量のイベント システムを提供します。ライフサイクル イベントと呼ばれるこれらのイベントを使用すると、「このタイプのエンティティを永続化する直前に、createdAt プロパティを自動的に更新する」などのタスクを実行できます。

Doctrine triggers events before/after performing the most common entity operations (e.g. prePersist/postPersist, preUpdate/postUpdate) and also on other common tasks (e.g. loadClassMetadata, onClear).

Doctrine は、最も一般的なエンティティ操作 (例: prePersist/postPersist、preUpdate/postUpdate) を実行する前/後にイベントをトリガーし、他の一般的なタスク (例: loadClassMetadata、onClear) でもトリガーします。

There are different ways to listen to these Doctrine events:

これらの Doctrine イベントをリッスンするには、さまざまな方法があります。
  • 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;
    ライフサイクル リスナーとサブスクライバー。これらは 1 つ以上のイベントのコールバック メソッドを持つクラスであり、すべてのエンティティに対して呼び出されます。
  • 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 Symfony services;
    コールバックは単一のエンティティクラスにのみ適用されるため、パフォーマンスが向上しますが、異なるエンティティに対してロジックを再利用することはできず、Symfony サービスにアクセスできません。
  • 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;
    ライフサイクル リスナーとサブスクライバーは、異なるエンティティ間でロジックを再利用でき、Symfony サービスにアクセスできますが、すべてのエンティティに対して呼び出されるため、パフォーマンスが低下します。
  • Entity listeners have the same advantages of lifecycle listeners and they have better performance because they only apply to a single entity class.
    エンティティ リスナーには、ライフサイクル リスナーと同じ利点があり、単一のエンティティ クラスにのみ適用されるため、パフォーマンスが向上します。

This article only explains the basics about Doctrine events when using them inside a Symfony application. Read the official docs about Doctrine events to learn everything about them.

この記事では、Symfony アプリケーション内で使用する際の Doctrine イベントの基本のみを説明します。 Doctrine イベントに関する公式ドキュメントを読んで、それらに関するすべてを学びましょう。

See also

こちらもご覧ください

This article covers listeners and subscribers for Doctrine ORM. If you are using ODM for MongoDB, read the DoctrineMongoDBBundle documentation.

この記事では、Doctrine ORM のリスナーとサブスクライバーについて説明します。 MongoDB に ODM を使用している場合は、DoctrineMongoDBBundle のドキュメントをお読みください。

Doctrine Lifecycle Callbacks

Lifecycle callbacks are defined as public methods inside the entity you want to modify. For example, suppose you want to set a createdAt date column to the current date, but only when the entity is first persisted (i.e. inserted). To do so, define a callback for the prePersist Doctrine event:

ライフサイクル コールバックは、変更するエンティティ内のパブリック メソッドとして定義されます。これを行うには、prePersist Doctrine イベントのコールバックを定義します。
  • Attributes
    属性
  • YAML
    YAML
  • XML
    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;

// When using attributes, 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(): void
    {
        $this->createdAt = new \DateTimeImmutable();
    }
}

Note

ノート

Some lifecycle callbacks receive an argument that provides access to useful information such as the current entity manager (e.g. the preUpdate callback receives a PreUpdateEventArgs $event argument).

一部のライフサイクル コールバックは、現在のエンティティ マネージャなどの有用な情報へのアクセスを提供する引数を受け取ります (たとえば、preUpdatecallback は PreUpdateEventArgs $event 引数を受け取ります)。

Doctrine Lifecycle Listeners

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:

ライフサイクル リスナーは、すべてのアプリケーション エンティティで単一の Doctrineevent をリッスンする PHP クラスとして定義されます。たとえば、新しいエンティティがデータベースに永続化されるたびに検索インデックスを更新するとします。そうするには、postPersist Doctrine イベントのリスナーを定義します。
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
{
    // 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): 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
    }
}

The next step is to enable the Doctrine listener in the Symfony application by creating a new service for it and tagging it with the doctrine.event_listener tag:

次のステップは、Symfony アプリケーションで Doctrine リスナーを有効にすることです。新しいサービスを作成し、doctrine.event_listener タグでタグ付けします。
  • YAML
    YAML
  • XML
    XML
  • PHP
    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'
                # 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'

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.

Symfony は、関連するDoctrine イベントが実際に発生したときにのみ、Doctrine リスナーをロード (およびインスタンス化) します。一方、Doctrine サブスクライバーは常に Symfony によってロード (およびインスタンス化) されるため、パフォーマンスが低下します。

Tip

ヒント

The value of the connection option can also be a configuration parameter.

接続オプションの値は、構成パラメーターにすることもできます。

Doctrine Entity Listeners

Entity listeners are defined as PHP classes that listen to a single Doctrine event on a single entity class. For example, suppose that you want to send some notifications whenever a User entity is modified in the database.

エンティティ リスナーは、単一のエンティティ クラスで単一の Doctrineevent をリッスンする PHP クラスとして定義されます。たとえば、データベースで User エンティティが変更されるたびに何らかの通知を送信するとします。

First, define a PHP class that handles the postUpdate Doctrine event:

まず、postUpdate Doctrine イベントを処理する PHP クラスを定義します。
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
{
    // the entity listener methods receive two arguments:
    // the entity instance and the lifecycle event
    public function postUpdate(User $user, LifecycleEventArgs $event): void
    {
        // ... do something to notify the changes
    }
}

Then, add the #[AsDoctrineListener] attribute to the class to enable it as a Doctrine entity listener in your application:

次に、 #[AsDoctrineListener] 属性をクラスに追加して、アプリケーションで Doctrine エンティティ リスナーとして有効にします。
1
2
3
4
5
6
7
8
9
10
11
12
// src/EventListener/UserChangedNotifier.php
namespace App\EventListener;

// ...
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;

#[AsDoctrineListener(event: 'postUpdate',  method: 'postUpdate', entity: User::class)]
class UserChangedNotifier
{
    // ...
}

That's it. Alternatively, if you prefer to not use PHP attributes, you must configure a service for the entity listener and tag it with the doctrine.orm.entity_listener tag as follows:

それでおしまい。または、PHP 属性を使用したくない場合は、次のようにエンティティ リスナーのサービスを構成し、doctrine.orm.entity_listener タグでタグ付けする必要があります。
  • YAML
    YAML
  • XML
    XML
  • PHP
    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:
            -
                # these are the options required to define the entity listener
                name: 'doctrine.orm.entity_listener'
                event: 'postUpdate'
                entity: 'App\Entity\User'

                # these are other options that you may define if needed

                # set the 'lazy' option to TRUE to only instantiate listeners when they are used
                # lazy: true

                # set the 'entity_manager' option if the listener is not associated to the default 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 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\Common\EventSubscriber インターフェースを実装し、すべてのアプリケーション エンティティで 1 つ以上のDoctrine イベントをリッスンする PHP クラスとして定義されます。たとえば、すべてのデータベース アクティビティをログに記録するとします。これを行うには、postPersist、postRemove、および postUpdate Doctrine イベントのサブスクライバーを定義します。
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
{
    // 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 LifecycleEventArgs, which gives you access
    // to both the entity object of the event and the entity manager itself
    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 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
    }
}

If you're using the default services.yaml configuration and DoctrineBundle 2.1 (released May 25, 2020) or newer, this example will already work! Otherwise, create a service for this subscriber and tag it with doctrine.event_subscriber.

デフォルトの services.yaml 構成と DoctrineBundle 2.1 (2020 年 5 月 25 日リリース) 以降を使用している場合、この例は既に機能しています!それ以外の場合は、このサブスクライバーのサービスを作成し、doctrine.event_subscriber でタグ付けします。

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:

サブスクライバーのいくつかのオプションを構成する必要がある場合 (例えば、使用する優先順位またはDoctrine 接続)、手動サービス構成でそれを行う必要があります。
  • YAML
    YAML
  • XML
    XML
  • PHP
    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'

              # 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

              # you can also restrict listeners to a specific Doctrine connection
              connection: 'default'

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 は、アプリケーションが実行されるたびに Doctrine サブスクライバーをロード (およびインスタンス化) します。一方、Doctrine リスナーは、関連するイベントが実際に発生したときにのみロードされるため、パフォーマンスが向上します。