The EventDispatcher Component

The EventDispatcher component provides tools that allow your application components to communicate with each other by dispatching events and listening to them.

EventDispatcher コンポーネントは、アプリケーション コンポーネントがイベントをディスパッチしてリッスンすることで相互に通信できるようにするツールを提供します。

Introduction

Object-oriented code has gone a long way to ensuring code extensibility. By creating classes that have well-defined responsibilities, your code becomes more flexible and a developer can extend them with subclasses to modify their behaviors. But if they want to share the changes with other developers who have also made their own subclasses, code inheritance is no longer the answer.

オブジェクト指向のコードは、コードの拡張性を確保するために長い道のりを歩んできました.明確に定義された責任を持つクラスを作成することにより、コードはより柔軟になり、開発者はサブクラスでそれらを拡張して動作を変更できます.しかし、独自のサブクラスを作成した他の開発者と変更を共有したい場合、コードの継承はもはや答えではありません。

Consider the real-world example where you want to provide a plugin system for your project. A plugin should be able to add methods, or do something before or after a method is executed, without interfering with other plugins. This is not an easy problem to solve with single inheritance, and even if multiple inheritance was possible with PHP, it comes with its own drawbacks.

プロジェクトにプラグイン システムを提供する実際の例を考えてみましょう。プラグインは、他のプラグインに干渉することなく、メソッドを追加したり、メソッドの実行前または実行後に何かを実行したりできる必要があります。これは、単一継承では簡単に解決できる問題ではなく、PHP で多重継承が可能であったとしても、自分の欠点。

The Symfony EventDispatcher component implements the Mediator and Observer design patterns to make all these things possible and to make your projects truly extensible.

Symfony EventDispatcher コンポーネントは、Mediator および Observer 設計パターンを実装して、これらすべてを可能にし、プロジェクトを真に拡張可能にします。

Take an example from the HttpKernel component. Once a Response object has been created, it may be useful to allow other elements in the system to modify it (e.g. add some cache headers) before it's actually used. To make this possible, the Symfony kernel throws an event - kernel.response. Here's how it works:

HttpKernel コンポーネントの例を見てみましょう。Response オブジェクトが作成されたら、実際に使用する前に、システム内の他の要素がそれを変更できるようにすると便利な場合があります (キャッシュ ヘッダーを追加するなど)。これを可能にするために、Symfony カーネルはイベント - kernel.response をスローします。仕組みは次のとおりです。
  • A listener (PHP object) tells a central dispatcher object that it wants to listen to the kernel.response event;
    リスナー (PHP オブジェクト) は、中央のディスパッチャー オブジェクトに、kernel.response イベントをリッスンしたいことを伝えます。
  • At some point, the Symfony kernel tells the dispatcher object to dispatch the kernel.response event, passing with it an Event object that has access to the Response object;
    ある時点で、Symfony カーネルは Dispatcher オブジェクトに kernel.response イベントをディスパッチするよう指示し、Response オブジェクトにアクセスできる Event オブジェクトを渡します。
  • The dispatcher notifies (i.e. calls a method on) all listeners of the kernel.response event, allowing each of them to make modifications to the Response object.
    ディスパッチャーは、kernel.response イベントのすべてのリスナーに通知 (つまり、メソッドを呼び出し) し、各リスナーが Response オブジェクトを変更できるようにします。

Installation

1
$ composer require symfony/event-dispatcher

Note

ノート

If you install this component outside of a Symfony application, you must require the vendor/autoload.php file in your code to enable the class autoloading mechanism provided by Composer. Read this article for more details.

このコンポーネントを Symfony アプリケーションの外部にインストールする場合は、Composer が提供するクラス自動ロード メカニズムを有効にするために、コード内に vendor/autoload.php ファイルを必要とする必要があります。詳細については、この記事をお読みください。

Usage

See also

こちらもご覧ください

This article explains how to use the EventDispatcher features as an independent component in any PHP application. Read the Events and Event Listeners article to learn about how to use it in Symfony applications.

この記事では、任意の PHP アプリケーションで EventDispatcher 機能を独立したコンポーネントとして使用する方法について説明します。イベントとイベント リスナーの記事を読んで、Symfony アプリケーションで使用する方法を学んでください。

Events

When an event is dispatched, it's identified by a unique name (e.g. kernel.response), which any number of listeners might be listening to. An Event instance is also created and passed to all of the listeners. As you'll see later, the Event object itself often contains data about the event being dispatched.

イベントがディスパッチされると、一意の名前 (kernel.response など) で識別されます。これは、任意の数のリスナーがリッスンしている可能性があります。イベント インスタンスも作成され、すべてのリスナーに渡されます。後で説明するように、Event オブジェクト自体には、ディスパッチされるイベントに関するデータが含まれていることがよくあります。

Naming Conventions

The unique event name can be any string, but optionally follows a few naming conventions:

一意のイベント名は任意の文字列にすることができますが、オプションでいくつかの命名規則に従います。
  • Use only lowercase letters, numbers, dots (.) and underscores (_);
    小文字、数字、ドット (.)、アンダースコア (_) のみを使用してください。
  • Prefix names with a namespace followed by a dot (e.g. order.*, user.*);
    ネームスペースの後にドットを付けたプレフィックス名 (例: order.*、user.*)。
  • End names with a verb that indicates what action has been taken (e.g. order.placed).
    どのようなアクションが実行されたかを示す動詞で終わる名前 (例: order.placed)。

Event Names and Event Objects

When the dispatcher notifies listeners, it passes an actual Event object to those listeners. The base Event class contains a method for stopping event propagation, but not much else.

ディスパッチャーがリスナーに通知すると、実際の Event オブジェクトがそれらのリスナーに渡されます。基本 Event クラスには、イベントの伝播を停止するためのメソッドが含まれていますが、それ以外にはあまりありません。

See also

こちらもご覧ください

Read "The Generic Event Object" for more information about this base event object.

この基本イベント オブジェクトの詳細については、「汎用イベント オブジェクト」を参照してください。

Often times, data about a specific event needs to be passed along with the Event object so that the listeners have the needed information. In such case, a special subclass that has additional methods for retrieving and overriding information can be passed when dispatching an event. For example, the kernel.response event uses a ResponseEvent, which contains methods to get and even replace the Response object.

多くの場合、リスナーが必要な情報を取得できるように、特定のイベントに関するデータを Event オブジェクトと一緒に渡す必要があります。このような場合、イベントをディスパッチするときに、情報を取得およびオーバーライドするための追加のメソッドを持つ特別なサブクラスを渡すことができます。たとえば、kernel.response イベントは、ResponseEvent を使用します。ResponseEvent には、Response オブジェクトを取得して置き換えるメソッドが含まれています。

The Dispatcher

The dispatcher is the central object of the event dispatcher system. In general, a single dispatcher is created, which maintains a registry of listeners. When an event is dispatched via the dispatcher, it notifies all listeners registered with that event:

ディスパッチャは、イベント ディスパッチャ システムの中心的なオブジェクトです。通常、単一のディスパッチャーが作成され、リスナーのレジストリが維持されます。ディスパッチャーを介してイベントがディスパッチされると、そのイベントに登録されているすべてのリスナーに通知されます。
1
2
3
use Symfony\Component\EventDispatcher\EventDispatcher;

$dispatcher = new EventDispatcher();

Connecting Listeners

To take advantage of an existing event, you need to connect a listener to the dispatcher so that it can be notified when the event is dispatched. A call to the dispatcher's addListener() method associates any valid PHP callable to an event:

既存のイベントを利用するには、リスナーをディスパッチャに接続して、イベントがディスパッチされたときに通知できるようにする必要があります。ディスパッチャの addListener() メソッドを呼び出すと、有効な PHP callable がイベントに関連付けられます。
1
2
$listener = new AcmeListener();
$dispatcher->addListener('acme.foo.action', [$listener, 'onFooAction']);

The addListener() method takes up to three arguments:

addListener() メソッドは、最大 3 つの引数を取ります。
  1. The event name (string) that this listener wants to listen to;
    このリスナーがリッスンするイベント名 (文字列)。
  2. A PHP callable that will be executed when the specified event is dispatched;
    指定されたイベントが送出されたときに実行される PHP 呼び出し可能オブジェクト。
  3. An optional priority, defined as a positive or negative integer (defaults to 0). The higher the number, the earlier the listener is called. If two listeners have the same priority, they are executed in the order that they were added to the dispatcher.
    正または負の整数として定義されるオプションの優先度 (デフォルトは 0)。数値が大きいほど、リスナーは早く呼び出されます。 2 つのリスナーの優先度が同じ場合、それらはディスパッチャに追加された順序で実行されます。

Note

ノート

A PHP callable is a PHP variable that can be used by the call_user_func() function and returns true when passed to the is_callable() function. It can be a \Closure instance, an object implementing an __invoke() method (which is what closures are in fact), a string representing a function or an array representing an object method or a class method.

PHP callable は、call_user_func() 関数で使用できる PHP 変数であり、is_callable() 関数に渡されると true を返します。これは、\Closure インスタンス、__invoke() メソッドを実装するオブジェクト (クロージャとは実際には同じものです)、関数を表す文字列、オブジェクト メソッドまたはクラス メソッドを表す配列のいずれかです。

So far, you've seen how PHP objects can be registered as listeners. You can also register PHP Closures as event listeners:

これまで、PHP オブジェクトをリスナーとして登録する方法を見てきました。PHP クロージャーをイベント リスナーとして登録することもできます。
1
2
3
4
5
use Symfony\Contracts\EventDispatcher\Event;

$dispatcher->addListener('acme.foo.action', function (Event $event) {
    // will be executed when the acme.foo.action event is dispatched
});

Once a listener is registered with the dispatcher, it waits until the event is notified. In the above example, when the acme.foo.action event is dispatched, the dispatcher calls the AcmeListener::onFooAction() method and passes the Event object as the single argument:

ディスパッチャーにリスナーが登録されると、リスナーはイベントが通知されるまで待機します。上記の例では、acme.foo.action イベントがディスパッチされると、ディスパッチャーは AcmeListener::onFooAction() メソッドを呼び出し、Event オブジェクトを単一の引数として渡します。
1
2
3
4
5
6
7
8
9
10
11
use Symfony\Contracts\EventDispatcher\Event;

class AcmeListener
{
    // ...

    public function onFooAction(Event $event)
    {
        // ... do something
    }
}

The $event argument is the event object that was passed when dispatching the event. In many cases, a special event subclass is passed with extra information. You can check the documentation or implementation of each event to determine which instance is passed.

$event 引数は、イベントのディスパッチ時に渡されたイベント オブジェクトです。多くの場合、特別なイベント サブクラスが追加情報とともに渡されます。どのインスタンスが渡されるかを判断するには、各イベントのドキュメントまたは実装を確認してください。
サービス コンテナへのイベント リスナとサブスクライバの登録

Registering service definitions and tagging them with the kernel.event_listener and kernel.event_subscriber tags is not enough to enable the event listeners and event subscribers. You must also register a compiler pass called RegisterListenersPass() in the container builder:

イベントリスナーとイベントサブスクライバーを有効にするには、サービス定義を登録してkernel.event_listenerタグとkernel.event_subscriberタグでタグ付けするだけでは不十分です。また、コンテナー ビルダーで RegisterListenersPass() というコンパイラ パスを登録する必要があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventDispatcher;

$containerBuilder = new ContainerBuilder(new ParameterBag());
// register the compiler pass that handles the 'kernel.event_listener'
// and 'kernel.event_subscriber' service tags
$containerBuilder->addCompilerPass(new RegisterListenersPass());

$containerBuilder->register('event_dispatcher', EventDispatcher::class);

// registers an event listener
$containerBuilder->register('listener_service_id', \AcmeListener::class)
    ->addTag('kernel.event_listener', [
        'event' => 'acme.foo.action',
        'method' => 'onFooAction',
    ]);

// registers an event subscriber
$containerBuilder->register('subscriber_service_id', \AcmeSubscriber::class)
    ->addTag('kernel.event_subscriber');

RegisterListenersPass resolves aliased class names which for instance allows to refer to an event via the fully qualified class name (FQCN) of the event class. The pass will read the alias mapping from a dedicated container parameter. This parameter can be extended by registering another compiler pass, AddEventAliasesPass:

RegisterListenersPass は、たとえば、イベント クラスの完全修飾クラス名 (FQCN) を介してイベントを参照できるエイリアス クラス名を解決します。パスは、専用のコンテナ パラメータからエイリアス マッピングを読み取ります。このパラメーターは、別のコンパイラ パス AddEventAliasesPass を登録することで拡張できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventDispatcher;

$containerBuilder = new ContainerBuilder(new ParameterBag());
$containerBuilder->addCompilerPass(new AddEventAliasesPass([
    \AcmeFooActionEvent::class => 'acme.foo.action',
]));
$containerBuilder->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING);

$containerBuilder->register('event_dispatcher', EventDispatcher::class);

// registers an event listener
$containerBuilder->register('listener_service_id', \AcmeListener::class)
    ->addTag('kernel.event_listener', [
        // will be translated to 'acme.foo.action' by RegisterListenersPass.
        'event' => \AcmeFooActionEvent::class,
        'method' => 'onFooAction',
    ]);

Note

ノート

Note that AddEventAliasesPass has to be processed before RegisterListenersPass.

AddEventAliasesPass は、RegisterListenersPass の前に処理する必要があることに注意してください。

The listeners pass assumes that the event dispatcher's service id is event_dispatcher, that event listeners are tagged with the kernel.event_listener tag, that event subscribers are tagged with the kernel.event_subscriber tag and that the alias mapping is stored as parameter event_dispatcher.event_aliases.

リスナー パスは、イベント ディスパッチャーのサービス ID が event_dispatcher であること、イベント リスナーがkernel.event_listener タグでタグ付けされていること、イベント サブスクライバーが kernel.event_subscriber タグでタグ付けされていること、およびエイリアス マッピングがパラメーター event_dispatcher.event_aliases として格納されていることを前提としています。

Creating and Dispatching an Event

In addition to registering listeners with existing events, you can create and dispatch your own events. This is useful when creating third-party libraries and also when you want to keep different components of your own system flexible and decoupled.

リスナーを既存のイベントに登録するだけでなく、独自のイベントを作成してディスパッチできます。これは、サードパーティのライブラリを作成する場合や、独自のシステムのさまざまなコンポーネントを柔軟に分離した状態に保ちたい場合に役立ちます。

Creating an Event Class

Suppose you want to create a new event - order.placed - that is dispatched each time a customer orders a product with your application. When dispatching this event, you'll pass a custom event instance that has access to the placed order. Start by creating this custom event class and documenting it:

アプリケーションで顧客が製品を注文するたびにディスパッチされる新しいイベント order.placed を作成するとします。このイベントをディスパッチするときは、発注済みの注文にアクセスできるカスタム イベント インスタンスを渡します。このカスタム イベント クラスを作成して文書化することから始めます。
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
namespace Acme\Store\Event;

use Acme\Store\Order;
use Symfony\Contracts\EventDispatcher\Event;

/**
 * The order.placed event is dispatched each time an order is created
 * in the system.
 */
class OrderPlacedEvent extends Event
{
    public const NAME = 'order.placed';

    protected $order;

    public function __construct(Order $order)
    {
        $this->order = $order;
    }

    public function getOrder(): Order
    {
        return $this->order;
    }
}

Each listener now has access to the order via the getOrder() method.

各リスナーは、getOrder() メソッドを介して注文にアクセスできるようになりました。

Note

ノート

If you don't need to pass any additional data to the event listeners, you can also use the default Event class. In such case, you can document the event and its name in a generic StoreEvents class, similar to the KernelEvents class.

イベント リスナーに追加のデータを渡す必要がない場合は、defaultEvent クラスを使用することもできます。このような場合、KernelEvents クラスと同様に、汎用の StoreEvents クラスでイベントとその名前を文書化できます。

Dispatch the Event

The dispatch() method notifies all listeners of the given event. It takes two arguments: the Event instance to pass to each listener of that event and the name of the event to dispatch:

dispatch() メソッドは、指定されたイベントをすべてのリスナーに通知します。 2 つの引数を取ります。そのイベントの各リスナーに渡す Event インスタンスと、ディスパッチするイベントの名前です。
1
2
3
4
5
6
7
8
9
10
use Acme\Store\Event\OrderPlacedEvent;
use Acme\Store\Order;

// the order is somehow created or retrieved
$order = new Order();
// ...

// creates the OrderPlacedEvent and dispatches it
$event = new OrderPlacedEvent($order);
$dispatcher->dispatch($event, OrderPlacedEvent::NAME);

Notice that the special OrderPlacedEvent object is created and passed to the dispatch() method. Now, any listener to the order.placed event will receive the OrderPlacedEvent.

特別な OrderPlacedEvent オブジェクトが作成され、dispatch() メソッドに渡されることに注意してください。これで、order.placedevent のすべてのリスナーが OrderPlacedEvent を受け取ります。

Using Event Subscribers

The most common way to listen to an event is to register an event listener with the dispatcher. This listener can listen to one or more events and is notified each time those events are dispatched.

イベントをリッスンする最も一般的な方法は、ディスパッチャーにイベント リスナーを登録することです。このリスナーは 1 つ以上のイベントをリッスンでき、それらのイベントがディスパッチされるたびに通知されます。

Another way to listen to events is via an event subscriber. An event subscriber is a PHP class that's able to tell the dispatcher exactly which events it should subscribe to. It implements the EventSubscriberInterface interface, which requires a single static method called getSubscribedEvents(). Take the following example of a subscriber that subscribes to the kernel.response and order.placed events:

イベントをリッスンするもう 1 つの方法は、イベント サブスクライバーを使用することです。 eventsubscriber は、どのイベントをサブスクライブする必要があるかをディスパッチャに正確に伝えることができる PHP クラスです。これは、getSubscriberEvents() という単一の静的メソッドを必要とする EventSubscriberInterface インターフェースを実装します。kernel.response および order.placed イベントにサブスクライブするサブスクライバーの次の例を見てください。
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
namespace Acme\Store\Event;

use Acme\Store\Event\OrderPlacedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class StoreSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::RESPONSE => [
                ['onKernelResponsePre', 10],
                ['onKernelResponsePost', -10],
            ],
            OrderPlacedEvent::NAME => 'onStoreOrder',
        ];
    }

    public function onKernelResponsePre(ResponseEvent $event)
    {
        // ...
    }

    public function onKernelResponsePost(ResponseEvent $event)
    {
        // ...
    }

    public function onStoreOrder(OrderPlacedEvent $event)
    {
        // ...
    }
}

This is very similar to a listener class, except that the class itself can tell the dispatcher which events it should listen to. To register a subscriber with the dispatcher, use the addSubscriber() method:

これはリスナー クラスに非常に似ていますが、クラス自体がリッスンする必要があるイベントをディスパッチャーに伝えることができます。サブスクライバーをディスパッチャーに登録するには、addSubscriber() メソッドを使用します。
1
2
3
4
5
use Acme\Store\Event\StoreSubscriber;
// ...

$subscriber = new StoreSubscriber();
$dispatcher->addSubscriber($subscriber);

The dispatcher will automatically register the subscriber for each event returned by the getSubscribedEvents() method. This method returns an array indexed by event names and whose values are either the method name to call or an array composed of the method name to call and a priority (a positive or negative integer that defaults to 0).

ディスパッチャーは、getSubscribeEvents() メソッドによって返される各イベントのサブスクライバーを自動的に登録します。このメソッドは、イベント名でインデックス付けされた配列を返します。この配列の値は、呼び出すメソッド名、または呼び出すメソッド名と優先順位 (既定値が 0 の正または負の整数) で構成される配列です。

The example above shows how to register several listener methods for the same event in subscriber and also shows how to pass the priority of each listener method. The higher the number, the earlier the method is called. In the above example, when the kernel.response event is triggered, the methods onKernelResponsePre() and onKernelResponsePost() are called in that order.

上記の例は、サブスクライバーで同じイベントに対して複数のリスナー メソッドを登録する方法と、各リスナー メソッドの優先度を渡す方法を示しています。数値が大きいほど、メソッドは早く呼び出されます。上記の例では、kernel.response イベントがトリガーされると、メソッド onKernelResponsePre() と onKernelResponsePost() がこの順序で呼び出されます。

Stopping Event Flow/Propagation

In some cases, it may make sense for a listener to prevent any other listeners from being called. In other words, the listener needs to be able to tell the dispatcher to stop all propagation of the event to future listeners (i.e. to not notify any more listeners). This can be accomplished from inside a listener via the stopPropagation() method:

場合によっては、リスナーが他のリスナーが呼び出されないようにすることが理にかなっている場合があります。言い換えれば、リスナーはディスパッチャーに、今後のリスナーへのイベントの伝播をすべて停止するように指示できる必要があります (つまり、これ以上リスナーに通知しないようにします)。これは、stopPropagation() メソッドを介してリスナー内から実行できます。
1
2
3
4
5
6
7
8
use Acme\Store\Event\OrderPlacedEvent;

public function onStoreOrder(OrderPlacedEvent $event)
{
    // ...

    $event->stopPropagation();
}

Now, any listeners to order.placed that have not yet been called will not be called.

これで、まだ呼び出されていない order.placed のリスナーは呼び出されなくなります。

It is possible to detect if an event was stopped by using the isPropagationStopped() method which returns a boolean value:

ブール値を返す isPropagationStopped() メソッドを使用して、イベントが停止したかどうかを検出できます。
1
2
3
4
5
// ...
$dispatcher->dispatch($event, 'foo.event');
if ($event->isPropagationStopped()) {
    // ...
}

EventDispatcher Aware Events and Listeners

The EventDispatcher always passes the dispatched event, the event's name and a reference to itself to the listeners. This can lead to some advanced applications of the EventDispatcher including dispatching other events inside listeners, chaining events or even lazy loading listeners into the dispatcher object.

EventDispatcher は常に、ディスパッチされたイベント、イベントの名前、およびそれ自体への参照をリスナーに渡します。これにより、リスナー内の他のイベントのディスパッチ、イベントの連鎖、ディスパッチャー オブジェクトへのリスナーの遅延読み込みなど、EventDispatcher の高度なアプリケーションが発生する可能性があります。

Event Name Introspection

The EventDispatcher instance, as well as the name of the event that is dispatched, are passed as arguments to the listener:

EventDispatcher インスタンスと、ディスパッチされるイベントの名前が引数としてリスナーに渡されます。
1
2
3
4
5
6
7
8
9
10
use Symfony\Contracts\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

class MyListener
{
    public function myEventListener(Event $event, string $eventName, EventDispatcherInterface $dispatcher)
    {
        // ... do something with the event name
    }
}

Other Dispatchers

Besides the commonly used EventDispatcher, the component comes with some other dispatchers:

一般的に使用される EventDispatcher に加えて、コンポーネントには他のディスパッチャーが付属しています。

Learn More