Events and Event Listeners

During the execution of a Symfony application, lots of event notifications are triggered. Your application can listen to these notifications and respond to them by executing any piece of code.

Symfony アプリケーションの実行中に、多くのイベント通知がトリガーされます。アプリケーションはこれらの通知をリッスンし、任意のコードを実行して応答できます。

Symfony triggers several events related to the kernel while processing the HTTP Request. Third-party bundles may also dispatch events, and you can even dispatch custom events from your own code.

symfony は、HTTP リクエストの処理中に、カーネルに関連するいくつかのイベントをトリガーします。サードパーティのバンドルもイベントをディスパッチする場合があり、独自のコードからカスタム イベントをディスパッチすることもできます。

All the examples shown in this article use the same KernelEvents::EXCEPTION event for consistency purposes. In your own application, you can use any event and even mix several of them in the same subscriber.

この記事に示すすべての例では、一貫性を保つために同じ KernelEvents::EXCEPTIONevent を使用しています。独自のアプリケーションでは、任意のイベントを使用でき、同じサブスクライバーでそれらのいくつかを混在させることもできます。

Creating an Event Listener

The most common way to listen to an event is to register an event 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
25
26
27
28
29
30
31
32
33
34
35
36
// src/EventListener/ExceptionListener.php
namespace App\EventListener;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

class ExceptionListener
{
    public function onKernelException(ExceptionEvent $event)
    {
        // You get the exception object from the received event
        $exception = $event->getThrowable();
        $message = sprintf(
            'My Error says: %s with code: %s',
            $exception->getMessage(),
            $exception->getCode()
        );

        // Customize your response object to display the exception details
        $response = new Response();
        $response->setContent($message);

        // HttpExceptionInterface is a special type of exception that
        // holds status code and header details
        if ($exception instanceof HttpExceptionInterface) {
            $response->setStatusCode($exception->getStatusCode());
            $response->headers->replace($exception->getHeaders());
        } else {
            $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
        }

        // sends the modified response object to the event
        $event->setResponse($response);
    }
}

Tip

ヒント

Each event receives a slightly different type of $event object. For the kernel.exception event, it is ExceptionEvent. Check out the Symfony events reference to see what type of object each event provides.

各イベントは、わずかに異なるタイプの $event オブジェクトを受け取ります。 kernel.exception イベントの場合、それは ExceptionEvent です。Symfony イベント リファレンスをチェックして、各イベントが提供するオブジェクトのタイプを確認してください。

Now that the class is created, you need to register it as a service and notify Symfony that it is a "listener" on the kernel.exception event by using a special "tag":

クラスが作成されたので、それをサービスとして登録し、特別な「タグ」を使用して、kernel.exception イベントの「リスナー」であることを Symfony に通知する必要があります。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
1
2
3
4
5
# config/services.yaml
services:
    App\EventListener\ExceptionListener:
        tags:
            - { name: kernel.event_listener, event: kernel.exception }

Symfony follows this logic to decide which method to call inside the event listener class:

symfony はこのロジックに従って、eventlistener クラス内で呼び出すメソッドを決定します。
  1. If the kernel.event_listener tag defines the method attribute, that's the name of the method to be called;
    kernel.event_listener タグが method 属性を定義する場合、それは呼び出されるメソッドの名前です。
  2. If no method attribute is defined, try to call the method whose name is on + "PascalCased event name" (e.g. onKernelException() method for the kernel.exception event);
    メソッド属性が定義されていない場合は、名前が on + "PascalCased イベント名" であるメソッドの呼び出しを試みます (たとえば、kernel.exception イベントの onKernelException() メソッド)。
  3. If that method is not defined either, try to call the __invoke() magic method (which makes event listeners invokable);
    そのメソッドも定義されていない場合は、__invoke() マジックメソッド (イベントリスナーを呼び出し可能にする) を呼び出してみてください。
  4. If the __invoke() method is not defined either, throw an exception.
    __invoke() メソッドも定義されていない場合は、例外をスローします。

Note

ノート

There is an optional attribute for the kernel.event_listener tag called priority, which is a positive or negative integer that defaults to 0 and it controls the order in which listeners are executed (the higher the number, the earlier a listener is executed). This is useful when you need to guarantee that one listener is executed before another. The priorities of the internal Symfony listeners usually range from -256 to 256 but your own listeners can use any positive or negative integer.

priority と呼ばれる kernel.event_listener タグのオプションの属性があります。これは正または負の整数で、デフォルトは 0 で、リスナーが実行される順序を制御します (数値が大きいほど、リスナーは早く実行されます)。これは、あるリスナーが別のリスナーの前に実行されることを保証する必要がある場合に役立ちます。内部 Symfony リスナーの優先順位は通常 -256 から 256 の範囲ですが、独自のリスナーは任意の正または負の整数を使用できます。

Defining Event Listeners with PHP Attributes

An alternative way to define an event listener is to use the AsEventListener PHP attribute. This allows to configure the listener inside its class, without having to add any configuration in external files:

イベントリスナーを定義する別の方法は、AsEventListenerPHP 属性を使用することです。これにより、外部ファイルに構成を追加することなく、クラス内でリスナーを構成できます。
1
2
3
4
5
6
7
8
9
10
11
12
namespace App\EventListener;

use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener]
final class MyListener
{
    public function __invoke(CustomEvent $event): void
    {
        // ...
    }
}

You can add multiple #[AsEventListener()] attributes to configure different methods:

複数の #[AsEventListener()] 属性を追加して、さまざまなメソッドを構成できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace App\EventListener;

use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener(event: CustomEvent::class, method: 'onCustomEvent')]
#[AsEventListener(event: 'foo', priority: 42)]
#[AsEventListener(event: 'bar', method: 'onBarEvent')]
final class MyMultiListener
{
    public function onCustomEvent(CustomEvent $event): void
    {
        // ...
    }

    public function onFoo(): void
    {
        // ...
    }

    public function onBarEvent(): void
    {
        // ...
    }
}

Creating an Event Subscriber

Another way to listen to events is via an event subscriber, which is a class that defines one or more methods that listen to one or various events. The main difference with the event listeners is that subscribers always know the events to which they are listening.

イベントをリッスンするもう 1 つの方法は、イベント サブスクライバーを使用することです。これは、1 つまたはさまざまなイベントをリッスンする 1 つまたは複数のメソッドを定義するクラスです。イベント リスナとの主な違いは、サブスクライバがリッスンしているイベントを常に認識していることです。

If different event subscriber methods listen to the same event, their order is defined by the priority parameter. This value is a positive or negative integer which defaults to 0. The higher the number, the earlier the method is called. Priority is aggregated for all listeners and subscribers, so your methods could be called before or after the methods defined in other listeners and subscribers. To learn more about event subscribers, read The EventDispatcher Component.

異なるイベント サブスクライバー メソッドが同じイベントをリッスンする場合、それらの順序は優先順位パラメーターによって定義されます。この値は正または負の整数で、デフォルトは 0 です。数値が大きいほど、メソッドは早く呼び出されます。優先順位はすべてのリスナーとサブスクライバーに対して集計されるため、他のリスナーとサブスクライバーで定義されたメソッドの前または後にメソッドを呼び出すことができます。イベント サブスクライバーの詳細については、EventDispatcher コンポーネントを参照してください。

The following example shows an event subscriber that defines several methods which listen to the same kernel.exception event:

次の例は、同じ kernel.exception イベントをリッスンするいくつかのメソッドを定義するイベント サブスクライバーを示しています。
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
// src/EventSubscriber/ExceptionSubscriber.php
namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class ExceptionSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        // return the subscribed events, their methods and priorities
        return [
            KernelEvents::EXCEPTION => [
                ['processException', 10],
                ['logException', 0],
                ['notifyException', -10],
            ],
        ];
    }

    public function processException(ExceptionEvent $event)
    {
        // ...
    }

    public function logException(ExceptionEvent $event)
    {
        // ...
    }

    public function notifyException(ExceptionEvent $event)
    {
        // ...
    }
}

That's it! Your services.yaml file should already be setup to load services from the EventSubscriber directory. Symfony takes care of the rest.

それでおしまい! services.yaml ファイルは、EventSubscriber ディレクトリからサービスをロードするように設定されているはずです。 symfony が残りを処理します。

Tip

ヒント

If your methods are not called when an exception is thrown, double-check that you're loading services from the EventSubscriber directory and have autoconfigure enabled. You can also manually add the kernel.event_subscriber tag.

例外がスローされたときにメソッドが呼び出されない場合は、EventSubscriber ディレクトリからサービスをロードしていることと、autoconfigure が有効になっていることを再確認してください。 kernel.event_subscriber タグを手動で追加することもできます。

Request Events, Checking Types

A single page can make several requests (one main request, and then multiple sub-requests - typically when embedding controllers in templates). For the core Symfony events, you might need to check to see if the event is for a "main" request or a "sub request":

1 つのページで複数のリクエストを行うことができます (1 つのメイン リクエスト、次に複数のサブリクエスト - 通常はテンプレートにコントローラーを埋め込む場合)。コア Symfony イベントについては、イベントが「メイン」リクエストまたは「サブリクエスト」:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/EventListener/RequestListener.php
namespace App\EventListener;

use Symfony\Component\HttpKernel\Event\RequestEvent;

class RequestListener
{
    public function onKernelRequest(RequestEvent $event)
    {
        if (!$event->isMainRequest()) {
            // don't do anything if it's not the main request
            return;
        }

        // ...
    }
}

Certain things, like checking information on the real request, may not need to be done on the sub-request listeners.

実際のリクエストに関する情報を確認するなど、特定のことは、サブリクエスト リスナーで行う必要がない場合があります。

Listeners or Subscribers

Listeners and subscribers can be used in the same application indistinctly. The decision to use either of them is usually a matter of personal taste. However, there are some minor advantages for each of them:

リスナーとサブスクライバーは、同じアプリケーション内で区別なく使用できます。どちらを使用するかは、通常、個人的な好みの問題です。ただし、それぞれにいくつかの小さな利点があります。
  • Subscribers are easier to reuse because the knowledge of the events is kept in the class rather than in the service definition. This is the reason why Symfony uses subscribers internally;
    イベントの知識がサービス定義ではなくクラスに保持されるため、サブスクライバーは再利用しやすくなります。これが、Symfony がサブスクライバーを内部的に使用する理由です。
  • Listeners are more flexible because bundles can enable or disable each of them conditionally depending on some configuration value.
    バンドルは、構成値に応じて条件付きで各リスナーを有効または無効にできるため、リスナーはより柔軟です。

Event Aliases

When configuring event listeners and subscribers via dependency injection, Symfony's core events can also be referred to by the fully qualified class name (FQCN) of the corresponding event class:

依存性注入を介してイベント リスナーとサブスクライバーを構成する場合、Symfony のコア イベントは、対応するイベント クラスの完全修飾クラス名 (FQCN) によって参照することもできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/EventSubscriber/RequestSubscriber.php
namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;

class RequestSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            RequestEvent::class => 'onKernelRequest',
        ];
    }

    public function onKernelRequest(RequestEvent $event)
    {
        // ...
    }
}

Internally, the event FQCN are treated as aliases for the original event names. Since the mapping already happens when compiling the service container, event listeners and subscribers using FQCN instead of event names will appear under the original event name when inspecting the event dispatcher.

内部的には、イベント FQCN は元のイベント名のエイリアスとして扱われます。マッピングはサービス コンテナーのコンパイル時に既に行われているため、イベント ディスパッチャーを検査すると、イベント名の代わりに FQCN を使用するイベント リスナーとサブスクライバーが元のイベント名の下に表示されます。

This alias mapping can be extended for custom events by registering the compiler pass AddEventAliasesPass:

このエイリアス マッピングは、コンパイラ パス AddEventAliasesPass を登録することにより、カスタム イベント用に拡張できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Kernel.php
namespace App;

use App\Event\MyCustomEvent;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;

class Kernel extends BaseKernel
{
    protected function build(ContainerBuilder $container)
    {
        $container->addCompilerPass(new AddEventAliasesPass([
            MyCustomEvent::class => 'my_custom_event',
        ]));
    }
}

The compiler pass will always extend the existing list of aliases. Because of that, it is safe to register multiple instances of the pass with different configurations.

コンパイラ パスは、常にエイリアスの既存のリストを拡張します。そのため、パスの複数のインスタンスを異なる構成で安全に登録できます。

Debugging Event Listeners

You can find out what listeners are registered in the event dispatcher using the console. To show all events and their listeners, run:

コンソールを使用して、イベント ディスパッチャーに登録されているリスナーを確認できます。すべてのイベントとそのリスナーを表示するには、次を実行します。
1
$ php bin/console debug:event-dispatcher

You can get registered listeners for a particular event by specifying its name:

名前を指定して、特定のイベントの登録済みリスナーを取得できます。
1
$ php bin/console debug:event-dispatcher kernel.exception

or can get everything which partial matches the event name:

または、イベント名に部分的に一致するすべてを取得できます。
1
2
$ php bin/console debug:event-dispatcher kernel // matches "kernel.exception", "kernel.response" etc.
$ php bin/console debug:event-dispatcher Security // matches "Symfony\Component\Security\Http\Event\CheckPassportEvent"

The security system uses an event dispatcher per firewall. Use the --dispatcher option to get the registered listeners for a particular event dispatcher:

セキュリティ システムは、ファイアウォールごとにイベント ディスパッチャを使用します。 --dispatcher オプションを使用して、特定のイベント ディスパッチャーの登録済みリスナーを取得します。
1
$ php bin/console debug:event-dispatcher --dispatcher=security.event_dispatcher.main

Learn more