支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
HttpKernel组件利用EventDispatcher组件提供了一个结构化的处理进程来将
Request
转换为Response
。该组件的灵活程度足以打造一个全功能的框架(Symfony)、一个微框架(Silex)以及一个高级CMS发布系统(Drupal)。
你可以通过下述两种方式安装:
通过Composer安装(Packagist上的symfony/http-kernel
)
通过官方Git宝库(https://github.com/symfony/http-kernel)
然后,包容vendor/autoload.php
文件,以开启Composer提供的自动加载机制。否则,你的程序将无法找到这个Symfony组件的类。
每一次HTTP web交互,始于一个请求,结束于一个响应。身为开发者你要做的是利用PHP代码读取请求信息(比如URL),然后创建并返回一个响应(如一个HTML页面或JSON串)。
上图中的文字,描述了WEB运行次第:
用户在浏览器中请求某个资源
浏览器发送请求到服务器
Symfony向开发者提供一个Request对象
开发者要将这个Request对象“转换”成一个Response对象
服务器发送响应给浏览器
浏览器显示资源给用户
通常,某些类型的框架或者系统,被设计成处理全部的重复劳动(如路由、安全等),这样开发者可以轻松创建程序的每一个页面。但究竟这些系统是如何 构建的则千差万别。HttpKernel组件提供了一个接口,把“始于请求,结束于合适的响应”这一过程,正式定型。本组件是任何程序和框架的心脏,无论那些系统的构建是多么的不同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace Symfony\Component\HttpKernel;
use Symfony\Component\HttpFoundation\Request;
interface HttpKernelInterface
{
// ...
/**
* @return Response A Response instance / 返回一个Response实例
*/
public function handle(
Request $request,
$type = self::MASTER_REQUEST,
$catch = true
);
} |
HttpKernel::handle()
- 该方法是HttpKernelInterface::handle()
接口方法的具体实现,定义了一个“始于Request
对象,结束于Response
对象”的工作流。
这一工作流的具体细节是理解kernel(包括Symfony框架和其他使用了kernel的库)如何工作的关键。
HttpKernel::handle()
方法在内部是靠“派遣事件”来完成工作的。这不光能令此方法灵活,还能更加抽象,因为在一个框架/程序中,所有用HttpKernel派生出来的“工作”,统统是被event listener(事件监听)来完成的。
为了更好地解释这个过程,本文对进程中的每一步予以关照,讨论HttpKernel的某个具体实现——也就是Symfony框架——究竟是如何工作的。
在内部,使用HttpKernel
是极其简单的,涉及一个
event dispatcher和一个控制器及其参数的“解析器”。为了完成你的可用内核(working kernel),你需要为下面讨论中所提及的事件,添加更多的事件监听(译注:本文是要告诉大家,如何去单独地使用HttpKernel组件,并非是对Symfony自身的相关代码进行分析——尽管每一个用本组件构建而成的kernel看起来都很像):
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 28 29 30 31 | use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
// create the Request object 创建Request对象
$request = Request::createFromGlobals();
$dispatcher = new EventDispatcher();
// ... add some event listeners 添加一些监听
// create your controller and argument resolvers
// 创建你的控制器及其参数的解析器
$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver();
// instantiate the kernel 对kernel实例化
$kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
// actually execute the kernel, which turns the request into a response
// by dispatching events, calling a controller, and returning the response
// 真正执行内核,通过事件派遣、调用控制器、返回response,来把请求转换为响应
$response = $kernel->handle($request);
// send the headers and echo the content 发送头信息并输出内容
$response->send();
// triggers the kernel.terminate event 触发kernel.terminate事件
$kernel->terminate($request, $response); |
参考完整示例以了解更多的具体实现办法。
若要了解关于如何为下文中的事件“添加监听”,可参考创建一个事件监听
截至3.1,HttpKernel
接收了一个“第四参”,它必须是ArgumentResolverInterface
的一个实例。在4.0版框架中此参数将变为必须。
有个极好的入门系列,讲的是利用HttpKernel组件和Symfony其他组件来创建你自己的微框架。参考这个介绍。
主要目的:添加更多的信息给Request
,对部分系统进行初始化或在可能的情况下返回一个Response
(如,安全层[security layer]拒绝访问等)。
在HttpKernel::handle()
方法中
第一个被派遣的事件是kernel.request
,它可以有多种不同的监听。
这个事件的监听器可以是多种多样的。其中的一些——像是某个security listener——已经有足够多的信息来立即创建一个Response
对象。例如,如果一个securty listener决定不让用户访问,那么这个监听会返回一个RedirectResponse
到登陆页,或是返回一个403拒绝访问的响应。
如果一个Response
对象在这种情况下被返回,则(HttpKernel::handle()
)进程直接跳到kernel.response事件。
其他的监听,只是对请求进行初始化操作或者添加更多信息进来。例如,一个监听器有可能对Request
对象确定和设置locale(区域信息)。
另一个常见的监听器是,路由监听。一个router listener可以处理Request
并决定controller是否应该被渲染(参见下一小节)。实际上,Request
对象有一个attributes bag(属性包),它是存放“与请求相关的附加数据和程序级特定数据”的好地方。这意味着你的路由监听通过某种方式来控制controller,它可以存储Request
对象之attributes(供控制器的resolver[解析器]来使用)。
总地说,kernel.request
事件的用意,是“既能直接创建、又可直接返回”一个Response
对象,或者,对Request
对象添加信息(如,设置locale以及其他一些信息到Request
的attributes中)
当(在监听中)为kernel.request
事件设置响应时,propagation(宣传)被停止了。这意味着低优先级的监听,将不再继续执行。
假设kernel.request
的监听没有能创建一个Response
,HttpKernel的下一步是决定和准备(如,resolve[解析])控制器。控制器是末级程序(end-application,即由Symfony用户所创建的)代码中,对某一特定页面创建和返回Response
的那部分。唯一的需求,是一个PHP回调(callable)——它可以是一个函数,对象的方法,或是一个closure
。
但是对于一个请求,你如何 才能确定它属于哪个控制器,则完全取决于你的程序。这也是“控制器解析器(controller resolver)”所要承担的任务——它是实现了ControllerResolverInterface
接口的类,也是HttpKernel
的构造参数之一。
你的任务是创建一个类去实现该接口的两个方法:getController()
和getArguments()
。实际上,默认的实现已经存在了,你可以直接使用,或者参考:ControllerResolver
。这个实现将在下文中详细解释:
1 2 3 4 5 6 7 8 9 10 | namespace Symfony\Component\HttpKernel\Controller;
use Symfony\Component\HttpFoundation\Request;
interface ControllerResolverInterface
{
public function getController(Request $request);
public function getArguments(Request $request, $controller);
} |
ControllerResolver
中的getArguments()
方法连同其ControllerResolverInterface
接口已经从3.1版开始被弱化(deprecated),并且将从4.0中移除。你可以使用ArgumentResolver
来替代,它实现的是ArgumentResolverInterface
接口。
在内部,HttpKernel::handle()
方法首先调用controller resolver(控制器解析器)中的getController()
方法。该方法被传入一个Request
对象,其职责是基于请求中的信息,来以某种方式决定并返回一个PHP callable(即控制器)。
第二个方法,getArguments()
,将在另外一个事件 - kernel.controller
- 被派遣之后被调用。
主要目的:在控制器被执行之前,初始化一些东西,或改变控制器。
在控制器的callable被确定下来之后,HttpKernel::handle()
将派遣kernel.controller
事件。监听此事件的listener可能会初始化系统的某些部分,因为它们需要在特定的东西被确定之后(比如controller、routing信息等)同时又在控制器被执行之前被初始化。下文中有一些Symfony框架内部针对此事件进行监听的例子。
此事件的监听,通过调用传入该监听的事件对象中的FilterControllerEvent::setController
方法,也可以完全改变控制器的callable。
接下来,HttpKernel::handle()
开始调用ArgumentResolverInterface::getArguments()
。牢记一点,在getController()
中返回的控制器是一个回调(callable)。而getArguments()
的作用是要返回“需要传入控制器”的参数(arguments)数组。此过程如何实现取决于你的设计,不过内置的ArgumentResolver
是一个好例子。
在这个节点,kernel已经有了一个PHP回调(即controller),以及一个在执行此回调时“应当传入”的参数数组。
下一步就简单了!HttpKernel::handle()
要执行控制器了。
控制器的任务是为给定的资源创建响应。它可以是一个HTML页面,JSON串或任何东东。不像handle()进程的其他步骤,这一步是由“末级开发者”(end-developer)来实现的,以构建每一个页面。
通常,控制器要返回一个Response
对象。如果是这种情况,那么kernel的工作差不多就完成了!在本例中,下一步是kernel.response事件。
但如果控制器返回的是Response
以外的任何东西,则内核(kernel)还有少许工作要做——kernel.view事件(因为kernel乃至框架的终极使命始终是要生成一个Response
对象)。
一个控制器必须要返回一些东西。如果控制器返回null
,则异常会被立即抛出。
主要目的:把控制器的一个非Response
的返回值转换成一个Response
对象。
如果控制器没能返回一个Response
对象,那么kernel会派遣另一个事件 - kernel.view
。监听此事件的listener要做的就是接管控制器的这个返回值(比如,一个data数组或某个对象)来创建一个Response
对象。
当你希望使用一个view层时这是非常有用的:毋须从控制器中返回Response
,你可以返回渲染页面所需之数据。本事件的监听可以利用这些数据以正确格式(如HTML,JSON等)来创建Response
。
在这个节点,如果没有监听器利用事件(译注:指的是传入listener的事件对象)来设置Response
,会有一个异常抛出:无论是控制器还是针对view的某个监听,必须要返回一个Response
。
当(在监听中)为kernel.view
事件设置响应时,propagation(宣传)被停止了。这意味着低优先级的监听,将不再继续执行。
主要目的:在Response
对象被发送之前修改它。
kernel的最终目标是把Request
转换成Response
。Response
可能被创建于kernel.request事件的派遣过程中,也可能是从controller中返回,或者从kernel.view事件的某个监听中返回。
不管是谁来创建Response
,另一个事件,也就是kernel.response
旋即被派遣。该事件的一个典型监听将以某些方式来修改Response
对象,比如修改头信息,添加cookie,甚至改变Response
自身的内容(比如在HTML响应的</body>
结束标签之前,注入一些JavaScript代码)等等。
本事件被派遣后,最终版Response
对象将从handle()
方法中被返回。在多数“代表性用法”中,你可以继续调用send()
方法,来发送头信息并打出Response
中的内容。
主要目的:在响应被流化到(streamed to)用户之后,执行一些重载操作。
HttpKernel进程中的最后一个事件是kernel.terminate
,它是独特的,因为它是在HttpKernel::handle()
方法之后,以及响应被发送到用户之后才发生。
1 2 3 4 5 | // send the headers and echo the content 发送头信息并打出内容
$response->send();
// triggers the kernel.terminate event 触发terminate事件
$kernel->terminate($request, $response); |
如同你看到的,在发送响应之后去调用$kernel->terminate
,将触发kernel.terminate
事件,进而可以执行特定的“延时操作”(如,发送邮件),这是为了尽可能快地将响应返回到客户端。
在内部,HttpKernel利用了fastcgi_finish_request
PHP函数。这意味着在这一刻,只有PHP FPM server API才能够发送响应给客户端,同时服务器端的PHP进程仍在执行着一些任务。对于所有其他的服务器APIs,针对kernel.terminate
事件的监听也仍然会被执行,只是无法发送响应到客户端,直到(监听中的)所有任务被执行完毕(才能发送)。
使用kernel.terminate
是可选的,仅当你的kernel实现了TerminableInterface
接口时方可调用。
主要目的:用于处理各种类型的异常,并创建一个适当的Response
来返回该异常。
在HttpKernel::handle()
过程中,若任何一个时间点抛出异常,则kernel.exception
事件被触发。在内部,handle()
方法体被打包成一个try-catch块儿。当异常抛出时,kernel.exception
事件被派遣,以便你的系统能够以某种方式来回应该异常。
针对此事件的每一个监听都将被传入一个GetResponseForExceptionEvent
事件,你可以用它的getException()
来访问原始异常。本事件的监听通常都会检查异常的所属类型,并创建一个合适的错误Response
。
例如,为了生成一个404页,你可能要抛出一个特殊类型的异常,然后为exception事件添加监听,利用listener来寻找这个异常,然后创建并返回一个404Response
。实际上,HttpKernel组件内置了一个ExceptionListener
,你可以选择使用,它将为你做这些事,乃至更多(参考下方区块里的内容以了解细节)。
当(在监听中)为kernel.exception
事件设置响应时,propagation(宣传)被停止了。这意味着低优先级的监听,将不再继续执行。
至此,你已经能创建一个监听,并将其附于“在HttpKernel::handle()
周期内”被派遣的事件之上。通常,监听(listener)就是一个“带有一些需要被执行的方法”的PHP类,但它也可以是任何东西。创建和附着事件监听的详细内容,请参考EventDispatcher组件。
每一个“kernel”事件的名字,被以常量的方式被定义在KernelEvents
类中。此外,每一个监听器都要传入一个唯一参数,即KernelEvent
的子类。该参数对象(译注:监听器的事件参数)包含了关于系统当前状态的信息,同时每一个事件都有自己的事件对象(event object):
Name 事件名 |
KernelEvents ConstantKernelEvents 类常量 |
Argument passed to the listener 传入监听器的(事件)参数 |
---|---|---|
kernel.request | KernelEvents::REQUEST |
GetResponseEvent |
kernel.controller | KernelEvents::CONTROLLER |
FilterControllerEvent |
kernel.view | KernelEvents::VIEW |
GetResponseForControllerResultEvent |
kernel.response | KernelEvents::RESPONSE |
FilterResponseEvent |
kernel.finish_request | KernelEvents::FINISH_REQUEST |
FinishRequestEvent |
kernel.terminate | KernelEvents::TERMINATE |
PostResponseEvent |
kernel.exception | KernelEvents::EXCEPTION |
GetResponseForExceptionEvent |
当使用HttpKernel组件时,你可以附着任意监听到(上表中的)核心事件;你可以使用任意controller resolver(控制器解析器),只要它实现了ControllerResolverInterface
接口;你还可以使用任意argument resolver(参数解析器),只要它实现了
ArgumentResolverInterface
接口。但是,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 28 29 30 31 32 33 34 35 36 37 38 | use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
$routes = new RouteCollection();
$routes->add('hello', new Route('/hello/{name}', array(
'_controller' => function (Request $request) {
return new Response(
sprintf("Hello %s", $request->get('name'))
);
})
));
$request = Request::createFromGlobals();
$matcher = new UrlMatcher($routes, new RequestContext());
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack()));
$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver();
$kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response); |
除了被传至HttpKernel::handle()
的“主要”请求之外,你也可以传入一个被称为“sub-request”的子请求。子请求看上去和用起来都很像其他请求,但通常被用在渲染页面小段内容而不是整个页面。你可以从控制器中发起子请求(或者干脆在模板中发动,而这也是由你的控制器负责渲染)。
为了执行一次子请求,使用HttpKernel::handle()
方法,但要改变第二个参数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
// ...
// create some other request manually as needed
// 根据需要,手动创建某个其他请求
$request = new Request();
// for example, possibly set its _controller manually
// 作为示例,可以手动设置其_controller属性
$request->attributes->set('_controller', '...');
$response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST);
// do something with this response
// 对这个响应进行操作 |
这会创建另外一个完整的“请求-响应”周期,来把这个新的Request
转换成一个Response
。内部唯一的不同,就是某些监听(比如Security的)可能只对master request进行操作。每个监听被传入的都是Kernel.Event
的子类,其isMasterRequest()
方法可以被用于检查当前的请求是“主”还是“子”请求。
例如,若一个监听仅需对主请求进行操作的话,可能像下面这样:
1 2 3 4 5 6 7 8 9 10 11 | use Symfony\Component\HttpKernel\Event\GetResponseEvent;
// ...
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
// ...
} |
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。