跳到内容

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 实例,而不是抛出的 ExceptionError 实例,以简化异常操作和显示。它可以将任何有效的控制器作为异常处理程序,因此您可以创建一个 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 框架提供支持的代码,因此您拥有两全其美的优势:一个根据您的需求量身定制的自定义框架,但基于坚如磐石且维护良好的底层架构,该架构已被证明可以为许多网站工作;一段代码已经过安全问题审核,并已被证明可以很好地扩展。

这项工作,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
TOC
    版本