13. 核心处理流程HttpKernel(drupal8执行流程)

有朋友问云客drupal8源码分析系列文章有顺序规律吗?我的回答是有!从第一篇开始以来她是按照drupal的执行流程分主题进行的,除前言外,已经发布12篇了,如果你是跟着drupal8的执行流程进行研究学习的,那么这些文章就像站在路旁等着你的引路人为你揭开一些迷惑的问题,drupal很大,前方很远,有时候你可能感觉站在一望无际的平原,眼前只有遥远天际的落日昏辉,希望这些引路人让走在这条路上的你不那么孤独,应该先读她,再去看代码,这样心中有大局,既舒适又快速。很多开发者学习一个系统时喜欢跟着代码执行流程走,如果你也是,那么在这里我很激动的告诉你:“本篇!你到了一个大站,在这里你将看到整个系统主干执行流程的全貌!”,前一个大站应该是容器的形成,然后经过HttpKernel堆栈中一层一层处理核心的执行到达这里;这是堆栈中最后一个处理核心,也是系统最主要的核心,剩余所有的工作都在这个核心里面进行,本篇就是介绍这个核心的。

所谓大道至简,用在这里是再合适不过了,本核心在容器中的服务名是http_kernel.basic,主干程序非常简单,它完全使用SymfonyHTTP核心组件:Symfony\Component\HttpKernel\HttpKernel,这个组件的官方文档请看这里:http://symfony.com/doc/2.8/components/http_kernel.html,源代码位于:\vendor\symfony\http-kernel\HttpKernel.php

Symfony处理核心定义了一个处理流程:请求开始》解析得到控制器》修正控制器》解析控制器参数》执行控制器》根据控制器返回情况:要么产生了响应,要么得到渲染数组去开启模板引擎渲染一个响应》最后发送响应;

这个过程可能出现异常那么就处理异常产生一个响应,整个过程可能循环很多遍以得到最终响应(每遍得到一个内容块)。

Symfony处理核心使用事件派发机制,这是一个非常强大灵活的机制,在软件设计模式中叫做监听者模式。有一个事件派发器,它内部记录着不同的事件,及对应需要启动的程序,这些程序叫做监听者,伴随上面定义的处理流程,在每一个点派发对应的事件,这些监听者程序依据优先级依次获得事件对象并执行(事件对象携带着大量数据),从而让众多的程序参与到这个主干流程中来;drupal的执行流程就像一条大河,最开始产生的请求对象就像一艘小船,弯弯曲曲顺流而下,到这里每一个监听者程序就是一条小小的支流,这条船可能在这些支流中进进出出,不管怎样这些支流最终又汇集到主干,最终一个响应对象产生并发送给客户端;这看起来像不像一副美丽优雅而广袤的绿色画卷?

下面看一看代码实现:

Symfony处理核心的源码文件是\vendor\symfony\http-kernel\HttpKernel.php,这个核心类接受三个参数:事件派发器对象、解析器对象、请求堆栈;

这个HttpKernel类并不复杂,主要流程定义在handleRaw方法中,在这个方法里你可以清楚的看到前文描述的流程定义,这里讲的重点是事件派发器,是它让众多的程序参与到这个主干流程中来。

drupal8没有使用Symfony提供的事件派发器,而是尊从Symfony定义的事件派发器接口自己实现了一个有容器意识的事件派发器(就是内部包含容器对象的事件派发器),它里面管理着事件名和对应的需要响应这些事件的容器服务(监听者)。

Symfony处理核心中定义了七种核心事件,他们位于\vendor\symfony\http-kernel\KernelEvents.php中,如下:

const REQUEST = 'kernel.request'; 在核心接到请求还没任何动作时派发这个事件

const CONTROLLER = 'kernel.controller';解析器得到控制器后派发该事件,允许监听者修改要执行的控制器

const VIEW = 'kernel.view';控制器返回渲染数组,需要启动模板引擎时派发这个事件,模板引擎就是一个监听者

const RESPONSE = 'kernel.response';得到响应后派发该事件,允许监听者修改响应

const FINISH_REQUEST = 'kernel.finish_request';完成响应后派发,允许监听者重置全局环境等动作

const TERMINATE = 'kernel.terminate';响应被发送后派发该事件,执行一些繁重的任务,比如处理POST数据

const EXCEPTION = 'kernel.exception';产生异常时派发这个事件

以上是核心会派发的七种事件,在drupal8中监听者还可以继续派发自己的事件,这带来极大的灵活性,如何操作见下文:

drupal的默认发行版本中注册的事件名见辅助内容区:

这些事件对应的监听者服务大致有一百个,这些监听者和事件是什么时候注册到事件派发器的呢?

是在容器编译阶段,对应的编译器是:\core\lib\Drupal\Core\DependencyInjection\Compiler\RegisterEventSubscribersPass.php

这个编译器收集所有具备event_subscriber标签的服务对象,把他们做成定义数组传递给事件派发器

这些具备event_subscriber标签的服务对象必须实现订阅者接口:

\vendor\symfony\event-dispatcher\EventSubscriberInterface.php

Array
(
    [0] => routing.route_finished
    [1] => config.save
    [2] => config.delete
    [3] => config.importer.missing_content
    [4] => routing.route_dynamic
    [5] => config.importer.validate
    [6] => routing.route_alter
    [7] => kernel.request
    [8] => kernel.response
    [9] => kernel.exception
    [10] => kernel.view
    [11] => kernel.finish_request
    [12] => kernel.controller
    [13] => kernel.terminate
    [14] => config.importer.import
    [15] => render.page_display_variant.select
    [16] => config.collection_info
    [17] => config.rename
    [18] => language.save_override
    [19] => language.delete_override
    [20] => locale.save_translation
    [21] => entity_type.definition.create
    [22] => entity_type.definition.update
    [23] => entity_type.definition.delete
)

 

所以在模块中要定义一个订阅者服务只需要打上event_subscriber标签即可,比如:

  options_request_listener:
    class: Drupal\Core\EventSubscriber\OptionsRequestSubscriber
    arguments: ['@router.route_provider']
    tags:
      - { name: event_subscriber }

在订阅器的getSubscribedEvents()方法中可以返回需要监听的事件名和优先级,是以下数组之一:

array('eventName' => 'methodName') //默认优先级为0
array('eventName' => array('methodName', $priority)) //指定优先级
array('eventName' => array(array('methodName1', $priority), array('methodName2'))) //指定多个监听者
//可以指定多个事件名 一起返回 每个事件名对应的监听者使用如上格式

怎么开发一个订阅者?参照:\core\lib\Drupal\Core\EventSubscriber 这个文件夹下存放多个核心订阅者。

以上是以服务方式静态定义事件和监听者,在运行中还可以动态定义,只需要调用事件派发器的相应方法即可,如下:

\Drupal::service("event_dispatcher")->addListener($event_name, $listener, $priority = 0);//添加监听者
\Drupal::service("event_dispatcher")->addSubscriber(EventSubscriberInterface $subscriber);//添加订阅者

更多对事件派发器的操作请看源代码:\core\lib\Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher.php

监听者和订阅者的区别是:订阅者用来注册监听者,它可以包含许多监听者。

如果你想知道你的系统中静态定义了多少监听者,请按照容器介绍中的方法到数据库中下载容器定义数据,然后执行下列代码:

$c=unserialize(file_get_contents("下载的容器定义数据yunke_cache_container-data.bin"));
$c=unserialize($c["services"]["event_dispatcher"]);
$c=$c["arguments"]->value;
$c=$c[1];
$event_name=array_keys($c);
$listeners=[];
foreach($c as  $key=>$value)
{
    foreach($value as  $k=>$v)
    {
        foreach($v as  $listener)
        {
            $listeners[]=$listener["service"];
        }
    }
}
print_r($event_name);
print_r($listeners);

 

好了,以上就是本主题的全部。

到这里你已经看到了整个drupal系统主干流程的全貌,本系列后续主题讲解每个分支流程,也就是监听者。

由于它们可能会交叉执行,所以不一定就是按执行流程来发布了,但主题是明确的,一个主题一个知识点。

本书共94小节:

评论 (写第一个评论)