HttpKernel组件:HttpKernel类

3.3 版本
维护中的版本

如果你现在已经使用了我们这个框架,你可能不得不添加一些对“自定义错误信息”的支持。我们有支持404和500错误,但响应是写死在框架里面的。使它们变得“可定制”是非常简单的:派遣一个新的事件,然后监听它。正确使用的话意味着监听需要调用一个常规控制器。但如果这个错误控制器(error controller)也抛出异常怎么办?你将卡在一个无尽的循环之中。一定有某种简单方式,对吧?

进入到HttpKernel类中。不同于一遍又一遍地解决相同问题而去重复发明轮子,HttpKernel类是对HttpKernelInterface接口的一个“通用的、可扩展的、灵活的”实现。

这个类,很像当前我们编写的Framework类:它在“处理请求”过程中的一些策略关键点上派发事件,它使用控制器解析器来选择控制器以发送请求,另外作为一个奖励,它照顾到了极端情况下的场景,在出现问题时可以提供很好的反馈信息。

这是全新的framework类代码:

1
2
3
4
5
6
7
8
// example.com/src/Simplex/Framework.php
namespace Simplex;
 
use Symfony\Component\HttpKernel\HttpKernel;
 
class Framework extends HttpKernel
{
}

下面是新的前端控制器:

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
// example.com/web/front.php
require_once __DIR__.'/../vendor/autoload.php';
 
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel;
use Symfony\Component\Routing;
 
$request = Request::createFromGlobals();
$requestStack = new RequestStack();
$routes = include __DIR__.'/../src/app.php';
 
$context = new Routing\RequestContext();
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
 
$controllerResolver = new HttpKernel\Controller\ControllerResolver();
$argumentResolver = new HttpKernel\Controller\ArgumentResolver();
 
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher, $requestStack));
 
$framework = new Simplex\Framework($dispatcher, $controllerResolver, $requestStack, $argumentResolver);
 
$response = $framework->handle($request);
$response->send();

RouterListener实现的是和我们的框架中相同的逻辑:匹配进入的请求,再以路由参数来装载请求的属性。

现在我们的代码非常简洁,异常健壮,比以往任何时候都强大。例如,使用内置的ExceptionListener可以让你的错误管理(error management)可配置:

1
2
3
4
5
6
$errorHandler = function (Symfony\Component\Debug\Exception\FlattenException $exception) {
    $msg = 'Something went wrong! ('.$exception->getMessage().')';
 
    return new Response($msg, $exception->getStatusCode());
};
$dispatcher->addSubscriber(new HttpKernel\EventListener\ExceptionListener($errorHandler));

ExceptionListener给你的是一个FlattenException实例,而不是抛出Exception实例,为的是简化异常的操作和显示。它接受任何有效的控制器来作为“异常处理器”(exception handler),因此你可以创建一个ErrorController类来替换掉Closure:

1
2
3
4
$listener = new HttpKernel\EventListener\ExceptionListener(
    'Calendar\\Controller\\ErrorController::exceptionAction'
);
$dispatcher->addSubscriber($listener);

错误控制器(error controller)的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// example.com/src/Calendar/Controller/ErrorController.php
namespace Calendar\Controller;
 
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Debug\Exception\FlattenException;
 
class ErrorController
{
    public function exceptionAction(FlattenException $exception)
    {
        $msg = 'Something went wrong! ('.$exception->getMessage().')';
 
        return new Response($msg, $exception->getStatusCode());
    }
}

瞧!唾手可得的整洁、可定制的错误管理。当然了,如果你的控制器抛出一个异常,HttpKernel将能够轻松应对。

在本系列第二章,我们讨论了Response::prepare()方法,它可以确保响应兼容HTTP协议。这是一个很好的方法,适合每次在发送响应给客户端之前,进行调用;这也正是ResponseListener所做的事:

1
$dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8'));

这里同样很简单!我们来看看另一个:你希望对流响应(streamed responses)进行支持吗?只需订阅StreamResponseListener即可:

1
$dispatcher->addSubscriber(new HttpKernel\EventListener\StreamedResponseListener());

在你的控制器中,返回的是一个StreamResponse实例,而不是Response实例。

阅读Symfony框架的事件参考,以了解更多关于HttpKernel的“事件派遣”以及它们是“如何令你改变一个请求的工作流”之相关内容。

现在,我们创建一个监听,一个允许控制器返回字符串而不是一个完整响应对象的监听器:

1
2
3
4
5
6
7
8
9
10
11
12
class LeapYearController
{
    public function indexAction(Request $request, $year)
    {
        $leapyear = new LeapYear();
        if ($leapyear->isLeapYear($year)) {
            return 'Yep, this is a leap year! ';
        }
 
        return 'Nope, this is not a leap year.';
    }
}

要实现这个功能,我们要监听kernel.view事件,它在控制器被调用之后触发。其目标是把控制器返回值转换成一个适当的Response响应实例,仅在需要的时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// example.com/src/Simplex/StringResponseListener.php
namespace Simplex;
 
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpFoundation\Response;
 
class StringResponseListener implements EventSubscriberInterface
{
    public function onView(GetResponseForControllerResultEvent $event)
    {
        $response = $event->getControllerResult();
 
        if (is_string($response)) {
            $event->setResponse(new Response($response));
        }
    }
 
    public static function getSubscribedEvents()
    {
        return array('kernel.view' => 'onView');
    }
}

代码足够简单,因为kernel.view事件只在控制器的返回值并非一个Response对象是才触发,另外也因为设置事件的响应会中止事件宣传(event propagation,我们的监听器会拦截其他的view监听)。

别忘了在前端控制器中注册它:

1
$dispatcher->addSubscriber(new Simplex\StringResponseListener());

如果你忘了注册订阅器(subscriber),HttpKernel会抛出一个美观的异常如下:The controller must return a response (Nope, this is not a leap year. given).

至此,我们的整个framework代码已是尽可能的简洁,它主要是由“现有的类库(existing libraries)”组合而成。扩展性则是通过注册事件监听/事件订阅来完成。

希望现在你已能理解,为何看上去如此简单的HttpKernelInterface却是如此强大。它的默认实现,HttpKernel,使你能访问到大量炫酷功能,即拿即用,毫不费力。因为HttpKernel真的是武装Symfony和Silex框架的代码,你将拥有两样好东西:一个“随需定制”的微框架,却是基于早已为无数网站所证明的“坚如磐石”、“组织良好”的low-level底层架构;一套禁得起安全审核并已被证明“可升级”的代码。

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)