支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
如果你现在已经使用了我们这个框架,你可能不得不添加一些对“自定义错误信息”的支持。我们有支持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 创作共用授权。