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\ControllerEvent 这个 event,这个 event 会在执行 controller 的时候调用,现在建立一个 interface

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

在需要认证的 controller 继承这个接口,在 EventSubscriber 目录下新建一个 AuthSubscriber.php 文件 ,没有那个目录就新建一个:

<?php

namespace App\EventSubscriber;

use App\Exception\AuthException;
use App\Controller\AuthenticatedInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class AuthSubscriber implements EventSubscriberInterface
{
    /**
     * @var SessionInterface
     */
    private $session;

    /**
     * AuthListener constructor.
     * @param SessionInterface $session
     */
    public function __construct(SessionInterface $session)
    {
        $this->session = $session;
    }

    /**
     * @param ControllerEvent $event
     * @throws AuthException
     */
    public function onKernelController(ControllerEvent $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 AuthenticatedInterface) {
            $user = $this->session->get('user');
            if (empty($user)) {
                throw new AuthException('Auth required.');
            }
        }
    }

    /**
     * @param ExceptionEvent $event
     */
    public function onKernelException(ExceptionEvent $event)
    {
        if ($event->getException() instanceof AuthException) {
            $res = new RedirectResponse('/login');
            $event->setResponse($res);
        }
    }

    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::CONTROLLER => 'onKernelController',
            KernelEvents::EXCEPTION => 'onKernelException',
        ];
    }

}

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

现在可以通过:

 php bin\console debug:event-dispatcher

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