Symfony 通过监听事件来实现类似 Middleware 功能

现在的很多框架比如 Slimphp,Sliex 等都支持 middleware,通过 middleware 可以很方便地操作或者过滤每一个 request,举例来说,比如认证功能,可以建立一个 middleware ,然后对每一个请求校验头部或者 session,错误就返回认证失败,这是非常常见的功能,不需要在每个 controller 中去检验。

然而 Symfony 貌似没有 middleware 功能,Symfony 采用了事件机制,这个有点像以前的 hook,但是功能更加强大和灵活,通过 event listener 也能实现类似 middleware 功能。Symfony 框架核心提供了很多内置的 event,我们可以监听这些 event 来扩展 Symfony。

要实现 middleware 功能,这里需要用到 Symfony\Component\HttpKernel\Event\FilterControllerEvent 这个 event,这个 event 会在执行 controller 的时候调用,现在建立一个 interface

<?php
namespace App\Controller;
interface TokenAuthenticatedInterface
{
}

在需要认证的 controller 继承这个接口,新建一个 TokenListener :

<?php
namespace App\EventListener;

use App\Model\User;
use App\Exception\AuthTokenException;
use App\Controller\TokenAuthenticatedInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;

class TokenListener
{
    private $user;

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

    /**
     * @param FilterControllerEvent $event
     * @throws AuthTokenException
     */
    public function onKernelController(FilterControllerEvent $event)
    {
        $controller = $event->getController();

        /*
         * $controller passed can be either a class or a Closure.
         * This is not usual in Symfony but it may happen.
         * If it is a class, it comes in array format
         */
        if (!is_array($controller)) {
            return;
        }

        if ($controller[0] instanceof TokenAuthenticatedInterface) {
            $request = $event->getRequest();
            $headers = $request->headers;
            //   ...  这里是具体的认证代码 
            if ($appToken !== $token) {
                throw new AuthTokenException('Auth invalid, Please check the token.');
            }
        }
    }
}

在这里继承一个 interface 的原因是可以通过 $controller[0] instanceof TokenAuthenticatedInterface 来判断过滤特定的 controller 。

最后在 services.yaml 中添加 listener 的配置:

    App\EventListener\TokenListener:
        class: App\EventListener\TokenListener
        arguments:
            - '@App\Model\User'
        tags:
            - {name: kernel.event_listener, event: kernel.controller}

现在可以通过:

 php bin\console debug:event-dispatcher

来查看所有的 event 的监听列表。