HttpKernel 组件:HttpKernel 类
如果您现在要使用我们的框架,您可能需要添加对自定义错误消息的支持。我们确实有 404 和 500 错误支持,但响应是在框架本身中硬编码的。使它们可自定义很简单:分发一个新事件并监听它。正确地执行意味着监听器必须调用常规控制器。但是,如果错误控制器抛出异常怎么办?您将最终陷入无限循环。应该有更简单的方法,对吧?
输入 HttpKernel
类。 HttpKernel
类不是一遍又一遍地解决相同的问题,也不是每次都重新发明轮子,而是 HttpKernelInterface
的通用、可扩展且灵活的实现。
此类与我们到目前为止编写的框架类非常相似:它在请求处理期间的某些战略点分发事件,它使用控制器解析器来选择要将请求分发到的控制器,并且作为额外的奖励,它处理了边缘情况,并在出现问题时提供出色的反馈。
这是新的框架代码
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
是我们在框架中拥有的相同逻辑的实现:它匹配传入的请求,并使用路由参数填充请求属性。
我们的代码现在更加简洁,并且出乎意料地比以往任何时候都更强大、更健壮。例如,使用内置的 ErrorListener
使您的错误管理可配置
1 2 3 4 5 6
$errorHandler = function (Symfony\Component\ErrorHandler\Exception\FlattenException $exception): Response {
$msg = 'Something went wrong! ('.$exception->getMessage().')';
return new Response($msg, $exception->getStatusCode());
};
$dispatcher->addSubscriber(new HttpKernel\EventListener\ErrorListener($errorHandler));
ErrorListener
为您提供了一个 FlattenException
实例,而不是抛出的 Exception
或 Error
实例,以简化异常操作和显示。它可以将任何有效的控制器作为异常处理程序,因此您可以创建一个 ErrorController 类,而不是使用闭包
1 2 3 4
$listener = new HttpKernel\EventListener\ErrorListener(
'Calendar\Controller\ErrorController::exception'
);
$dispatcher->addSubscriber($listener);
错误控制器的代码如下
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\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Response;
class ErrorController
{
public function exception(FlattenException $exception): Response
{
$msg = 'Something went wrong! ('.$exception->getMessage().')';
return new Response($msg, $exception->getStatusCode());
}
}
瞧! 干净且可自定义的错误管理,无需任何努力。如果您的 ErrorController
抛出异常,HttpKernel 也会很好地处理它。
在第二章中,我们讨论了 Response::prepare()
方法,该方法确保 Response 符合 HTTP 规范。在将 Response 发送到客户端之前始终调用它可能是一个好主意;这就是 ResponseListener
所做的事情
1
$dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8'));
在您的控制器中,返回一个 StreamedResponse
实例,而不是 Response
实例。
提示
阅读 内置 Symfony 事件 参考,以了解有关 HttpKernel 分发的事件以及它们如何允许您更改请求流程的更多信息。
现在,让我们创建一个监听器,该监听器允许控制器返回字符串而不是完整的 Response 对象
1 2 3 4 5 6 7 8 9 10 11 12
class LeapYearController
{
public function index(int $year): string
{
$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\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ViewEvent;
class StringResponseListener implements EventSubscriberInterface
{
public function onView(ViewEvent $event): void
{
$response = $event->getControllerResult();
if (is_string($response)) {
$event->setResponse(new Response($response));
}
}
public static function getSubscribedEvents(): array
{
return ['kernel.view' => 'onView'];
}
}
代码很简单,因为 kernel.view
事件仅在控制器返回值不是 Response 时触发,并且因为在事件上设置响应会停止事件传播(我们的监听器不会干扰其他视图监听器)。
不要忘记在前端控制器中注册它
1
$dispatcher->addSubscriber(new Simplex\StringResponseListener());
注意
如果您忘记注册订阅者,HttpKernel 将抛出一个带有友好消息的异常: The controller must return a response (Nope, this is not a leap year. given).
。
此时,我们的整个框架代码尽可能紧凑,并且主要由现有库的集合组成。扩展是注册事件监听器/订阅者的问题。
希望您现在更好地理解了为什么看起来简单的 HttpKernelInterface
如此强大。它的默认实现 HttpKernel
使您可以访问许多很酷的功能,这些功能开箱即用,无需任何努力。并且由于 HttpKernel 实际上是为 Symfony 框架提供支持的代码,因此您拥有两全其美的优势:一个根据您的需求量身定制的自定义框架,但基于坚如磐石且维护良好的底层架构,该架构已被证明可以为许多网站工作;一段代码已经过安全问题审核,并已被证明可以很好地扩展。