How to Set Up Before and After Filters

It is quite common in web application development to need some logic to be performed right before or directly after your controller actions acting as filters or hooks.

Web アプリケーション開発では、フィルターやフックとして機能するコントローラー アクションの直前または直後に何らかのロジックを実行する必要があることはよくあります。

Some web frameworks define methods like preExecute() and postExecute(), but there is no such thing in Symfony. The good news is that there is a much better way to interfere with the Request -> Response process using the EventDispatcher component.

一部の Web フレームワークでは、preExecute() や postExecute() などのメソッドを定義していますが、Symfony にはそのようなものはありません。良いニュースは、EventDispatcher コンポーネントを使用して Request -> Response プロセスを妨害する、はるかに優れた方法があることです。

Token Validation Example

Imagine that you need to develop an API where some controllers are public but some others are restricted to one or some clients. For these private features, you might provide a token to your clients to identify themselves.

一部のコントローラーがパブリックで、一部のコントローラーが 1 つまたはいくつかのクライアントに制限されている API を開発する必要があるとします。これらの非公開機能については、クライアントにトークンを提供して自分自身を識別することができます。

So, before executing your controller action, you need to check if the action is restricted or not. If it is restricted, you need to validate the provided token.

したがって、コントローラー アクションを実行する前に、アクションが制限されているかどうかを確認する必要があります。制限されている場合は、提供されたトークンを検証する必要があります。

Note

ノート

Please note that for simplicity in this recipe, tokens will be defined in config and neither database setup nor authentication via the Security component will be used.

このレシピを簡単にするために、トークンは config で定義され、データベースのセットアップもセキュリティコンポーネントによる認証も使用されないことに注意してください。

Before Filters with the kernel.controller Event

First, define some token configuration as parameters:

まず、いくつかのトークン構成をパラメーターとして定義します。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
1
2
3
4
5
# config/services.yaml
parameters:
    tokens:
        client1: pass1
        client2: pass2

Tag Controllers to Be Checked

A kernel.controller (aka KernelEvents::CONTROLLER) listener gets notified on every request, right before the controller is executed. So, first, you need some way to identify if the controller that matches the request needs token validation.

kernel.controller (別名 KernelEvents::CONTROLLER) リスナーは、コントローラーが実行される直前に、すべてのリクエストについて通知を受けます。そのため、まず、リクエストに一致するコントローラーがトークンの検証を必要としているかどうかを特定する方法が必要です。

A clean and easy way is to create an empty interface and make the controllers implement it:

クリーンで簡単な方法は、空のインターフェイスを作成し、コントローラーでそれを実装することです。
1
2
3
4
5
6
namespace App\Controller;

interface TokenAuthenticatedController
{
    // ...
}

A controller that implements this interface looks like this:

このインターフェイスを実装するコントローラーは次のようになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
namespace App\Controller;

use App\Controller\TokenAuthenticatedController;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class FooController extends AbstractController implements TokenAuthenticatedController
{
    // An action that needs authentication
    public function bar()
    {
        // ...
    }
}

Creating an Event Subscriber

Next, you'll need to create an event subscriber, which will hold the logic that you want to be executed before your controllers. If you're not familiar with event subscribers, you can learn more about them at Events and Event Listeners:

次に、コントローラーの前に実行するロジックを保持するイベント サブスクライバーを作成する必要があります。イベント サブスクライバーに慣れていない場合は、イベントとイベント リスナーで詳細を確認できます。
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
// src/EventSubscriber/TokenSubscriber.php
namespace App\EventSubscriber;

use App\Controller\TokenAuthenticatedController;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;

class TokenSubscriber implements EventSubscriberInterface
{
    private $tokens;

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

    public function onKernelController(ControllerEvent $event)
    {
        $controller = $event->getController();

        // when a controller class defines multiple action methods, the controller
        // is returned as [$controllerInstance, 'methodName']
        if (is_array($controller)) {
            $controller = $controller[0];
        }

        if ($controller instanceof TokenAuthenticatedController) {
            $token = $event->getRequest()->query->get('token');
            if (!in_array($token, $this->tokens)) {
                throw new AccessDeniedHttpException('This action needs a valid token!');
            }
        }
    }

    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::CONTROLLER => 'onKernelController',
        ];
    }
}

That's it! Your services.yaml file should already be setup to load services from the EventSubscriber directory. Symfony takes care of the rest. Your TokenSubscriber onKernelController() method will be executed on each request. If the controller that is about to be executed implements TokenAuthenticatedController, token authentication is applied. This lets you have a "before" filter on any controller you want.

それでおしまい! services.yaml ファイルは、EventSubscriber ディレクトリからサービスをロードするように設定されているはずです。 symfony が残りを処理します。 YourTokenSubscriber onKernelController() メソッドは、リクエストごとに実行されます。実行しようとしているコントローラーが TokenAuthenticatedController を実装している場合、トークン認証が適用されます。これにより、必要なコントローラーに「前」フィルターを設定できます。

Tip

ヒント

If your subscriber is not called on each request, 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 タグを手動で追加することもできます。

After Filters with the kernel.response Event

In addition to having a "hook" that's executed before your controller, you can also add a hook that's executed after your controller. For this example, imagine that you want to add a sha1 hash (with a salt using that token) to all responses that have passed this token authentication.

コントローラーの前に実行される「フック」に加えて、コントローラーの後に実行されるフックを追加することもできます。この例では、このトークン認証を通過したすべての応答に sha1 ハッシュ (そのトークンを使用するソルトを使用) を追加するとします。

Another core Symfony event - called kernel.response (aka KernelEvents::RESPONSE) - is notified on every request, but after the controller returns a Response object. To create an "after" listener, create a listener class and register it as a service on this event.

別のコア Symfony イベント - kernel.response (別名 KernelEvents::RESPONSE) と呼ばれる - はすべてのリクエストで通知されますが、コントローラーが Response オブジェクトを返した後に通知されます。このイベント。

For example, take the TokenSubscriber from the previous example and first record the authentication token inside the request attributes. This will serve as a basic flag that this request underwent token authentication:

たとえば、前の例から TokenSubscriber を取得し、最初にリクエスト属性内の認証トークンを記録します。これは、このリクエストがトークン認証を受けたことを示す基本的なフラグとして機能します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function onKernelController(ControllerEvent $event)
{
    // ...

    if ($controller instanceof TokenAuthenticatedController) {
        $token = $event->getRequest()->query->get('token');
        if (!in_array($token, $this->tokens)) {
            throw new AccessDeniedHttpException('This action needs a valid token!');
        }

        // mark the request as having passed token authentication
        $event->getRequest()->attributes->set('auth_token', $token);
    }
}

Now, configure the subscriber to listen to another event and add onKernelResponse(). This will look for the auth_token flag on the request object and set a custom header on the response if it's found:

ここで、サブスクライバーが別のイベントをリッスンするように構成し、onKernelResponse() を追加します。これにより、リクエスト オブジェクトで auth_token フラグが検索され、見つかった場合はレスポンスに customheader が設定されます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// add the new use statement at the top of your file
use Symfony\Component\HttpKernel\Event\ResponseEvent;

public function onKernelResponse(ResponseEvent $event)
{
    // check to see if onKernelController marked this as a token "auth'ed" request
    if (!$token = $event->getRequest()->attributes->get('auth_token')) {
        return;
    }

    $response = $event->getResponse();

    // create a hash and set it as a response header
    $hash = sha1($response->getContent().$token);
    $response->headers->set('X-CONTENT-HASH', $hash);
}

public static function getSubscribedEvents()
{
    return [
        KernelEvents::CONTROLLER => 'onKernelController',
        KernelEvents::RESPONSE => 'onKernelResponse',
    ];
}

That's it! The TokenSubscriber is now notified before every controller is executed (onKernelController()) and after every controller returns a response (onKernelResponse()). By making specific controllers implement the TokenAuthenticatedController interface, your listener knows which controllers it should take action on. And by storing a value in the request's "attributes" bag, the onKernelResponse() method knows to add the extra header. Have fun!

それでおしまい! TokenSubscriber は、すべてのコントローラーが実行される前 (onKernelController()) およびすべてのコントローラーが応答を返した後 (onKernelResponse()) に通知されるようになりました。特定のコントローラーに TokenAuthenticatedController インターフェースを実装させることで、リスナーはアクションを実行する必要があるコントローラーを認識します。また、リクエストの「属性」バッグに値を格納することで、onKernelResponse() メソッドは余分なヘッダーを追加することを認識します。楽しむ!