云客Drupal8源码分析之控制器执行及其解析器controller_resolver

来源:互联网 发布:淘宝财务报表 编辑:程序博客网 时间:2024/05/18 19:40

在drupal的HttpKernel核心中使用控制器解析器来取得要执行的控制器,以及解析出控制器需要的参数
除此之外也在多个地方用到它,比如渲染数组的回调解析,是一个重点内容

它的服务ID为:controller_resolver,接受以下两个参数:

psr7.http_message_factory:用于创建psr7描述的请求对象(关于这个请看:http://www.php-fig.org/psr/psr-7/)
class_resolver:从容器里面取服务

下面来看一看它是怎么工作的:
它需要实现ControllerResolverInterface接口,里面有两个方法定义:
public function getController(Request $request); //返回一个回调作为控制器
public function getArguments(Request $request, $controller); //求出控制器的参数,并以索引数组的方式返回
这个是Symfony的接口定义,此外drupal加了一个方法:
public function getControllerFromDefinition($controller); //以路由中对控制器的定义来获取控制器,同样返回回调

所以drupal的控制器解析器一共要实现接口的以上三个方法。

先看一看它怎么解析出控制器:
这必须依赖于路由过程中在请求对象属性包上设置的_controller参数,代码如下:
$request->attributes->get('_controller')),解析就是基于_controller的内容。
所有经过路由处理的请求对象上一定会有_controller变量,它来自路由定义的defaults字段
你可能奇怪在有些路由定义中没有定义_controller呢?
没有定义_controller的路由会在路由增强器里面定义,比如处理表单的路由里面定义了_form,而没有定义_controller,
但在增强器FormRouteEnhancer中就定义了$defaults['_controller'] = 'controller.form:getContentResult';
其中:controller.form表示服务ID,实体也是类似情况,这个用法见下文
当流程到达这里时,总之一定有_controller这个变量设置在请求属性包中。

_controller的定义:它可以是函数名或者具备__invoke方法的对象,也可以只是一个类,但它必须包含__invoke方法
类名必须是全限定的名字空间类名,控制器设置不能使用路径名,系统会通过类加载器找到控制器
大多数情况是用:或者::分割的字符串,它表示某个类的某方法,在这里:和::没有区别
这个分隔符还有个特殊的用法,前面部分不一定是类名也可以是容器的服务名,所以可以执行容器的服务方法

这个特殊用法是在class_resolver服务中实现的,上文已经说了它作为参数传递给控制器解析器。
类定义在\core\lib\Drupal\Core\DependencyInjection\ClassResolver.php
这里需要注意的是在:或者::之前的部分如果是一个类,并且它实现了以下接口:
Drupal\Core\DependencyInjection\ContainerInjectionInterface
Symfony\Component\DependencyInjection\ContainerAwareInterface
那么它会被实例化并自动注入服务容器,在drupal中几乎全部服务都在容器中,
ControllerBase实现了ContainerInjectionInterface,它就是此时自动注入容器,所以能提供那些常用的服务
明白为什么控制器继承Drupal\Core\Controller\ControllerBase就会有容器了吧!

以上就是获得控制器的过程,经过控制器解析器处理返回了一个回调
紧接着核心在得到回调后会派发kernel.controller事件,系统默认提供了两个侦听器:

服务id:path_subscriber 方法:onKernelController 作用是让别名管理器设置缓存键
服务id:early_rendering_controller_wrapper_subscriber 方法:onController 作用如下:

这个侦听器特别重要,但也让人感觉特别别扭,在控制器执行的时候要渲染页面本应该返回渲染数组
但可能也会在控制器中直接调用drupal_render()来渲染,这样过早的渲染称为:"early rendering"
这样做会让系统侦测不到渲染上下文,丢失一些元数据,影响到缓存等等,所以drupal8使用这个侦听器来解决这个问题
(过早渲染在drupa9中会被移除,禁止使用),那么它是怎么解决的呢?

其实所有的控制器都被这个侦听器包装在了一个闭包里面,在闭包中解决,Symfony HttpKernel得到的控制器全是经过包装的
真正的参数解析工作也在这里进行,在\Symfony\Component\HttpKernel\HttpKernel::handleRaw()中进行的参数解析纯属浪费资源,但它毕竟是第三方库,代码不受drupal控制,白白浪费计算资源了,读者可以试试在handleRaw()中参数解析后面重置参数为空数组,系统运行照样是没有任何问题的,这里建议drupal8的控制器解析器要设置缓存机制,避免这种浪费,参数解析的反射操作是很耗资源的。

由于这个侦听器的特殊作用,当我们在注册kernel.controller事件侦听器时一定要注意优先级,此侦听器优先级为默认的0

下面来看一看控制器解析器是怎么获取参数的,一句话总结:使用php的反射机制。

许多drupal的初学者应该都好奇过控制器的参数问题吧,核心代码如下:

 protected function doGetArguments(Request $request, $controller, array $parameters) {    $attributes = $request->attributes->all();    $raw_parameters = $request->attributes->has('_raw_variables') ? $request->attributes->get('_raw_variables') : [];    $arguments = array();    foreach ($parameters as $param) {      if (array_key_exists($param->name, $attributes)) {        $arguments[] = $attributes[$param->name];      }      elseif (array_key_exists($param->name, $raw_parameters)) {        $arguments[] = $attributes[$param->name]; //在8.2.3及之前的版本中,这里是一个bug,此处应该是$raw_parameters[$param->name],笔者已经提交了bug报告      }      elseif ($param->getClass() && $param->getClass()->isInstance($request)) {        $arguments[] = $request;      }      elseif ($param->getClass() && $param->getClass()->name === ServerRequestInterface::class) {        $arguments[] = $this->httpMessageFactory->createRequest($request);      }      elseif ($param->getClass() && ($param->getClass()->name == RouteMatchInterface::class || is_subclass_of($param->getClass()->name, RouteMatchInterface::class))) {        $arguments[] = RouteMatch::createFromRequest($request);      }      elseif ($param->isDefaultValueAvailable()) {        $arguments[] = $param->getDefaultValue();      }      else {        if (is_array($controller)) {          $repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]);        }        elseif (is_object($controller)) {          $repr = get_class($controller);        }        else {          $repr = $controller;        }        throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $param->name));      }    }    return $arguments;  }

参数:$parameters是一个ReflectionParameter对象组成的数组,见:http://php.net/manual/zh/class.reflectionparameter.php
它来自以下方法中:

   public function getArguments(Request $request, $controller)    {        if (is_array($controller)) {            $r = new \ReflectionMethod($controller[0], $controller[1]);        } elseif (is_object($controller) && !$controller instanceof \Closure) {            $r = new \ReflectionObject($controller);            $r = $r->getMethod('__invoke');        } else {            $r = new \ReflectionFunction($controller);        }        return $this->doGetArguments($request, $controller, $r->getParameters());    }
从以上解析过程中可以知道控制器参数采用的顺序、命名规则、类型暗示:

先从$request->attributes找同名参数,这也是为什么路由定义中额外参数能作为控制器参数的原因,路由定义的占位符名要和控制器参数名一致
再从$request->attributes中的_raw_variables找同名参数,它是未经过转换的原始变量
再看参数是不是有类型暗示为请求对象,如果是则将请求作为参数
再看是不是psr7描述的请求类型,它实现ServerRequestInterface接口,如果是则转化请求并传递
再看类型暗示是不是路由匹配器(RouteMatchInterface或其子类),如果是则传递路由匹配器
最后传递参数的默认值,如果不能找到参数将报错

以上就是控制器解析器的全部内容了,以下是一些补充:

1:在drupal8中所有控制器是放在闭包里面执行的,这样做是避免过早渲染带来的问题,此特性在D9中移除
2:控制器最好定义默认值,否则当找不到值时会报错
3:控制器最好继承Drupal\Core\Controller\ControllerBase,这样比较方便使用
4:请求对象及路由匹配器可以在参数中获得也可以使用容器获得


我是云客,【云游天下,做客四方】欢迎转载,但须注明出处,讨论请加qq群203286137





0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 第三方支付存在的金融风险怎么办 貔貅鼻子摔坏了怎么办 貔貅鼻子磕破了怎么办 红警2游戏出错怎么办 猛犸牙上油花了怎么办 吃了细菌的食物怎么办 易拉罐罐头拉环断了怎么办 衣服上有火锅味怎么办 衣服沾上火锅味怎么办 做杨梅罐头里面好多小白虫怎么办 一地两检手续怎么办 剩米饭变干硬了怎么办 吃剩的米饭变硬怎么办 误食发热包的水怎么办 玻璃饭盒加热后盖子打不开怎么办 玻璃饭盒盖子被吸住了怎么办 微波炉加热饭盒盖子打不开怎么办 铁饭盒盖子打不开了怎么办 塑料玻璃饭盒打不开了怎么办 方便火锅没有发热包怎么办 加热包的水溢出怎么办 军用黄脸盆坏了怎么办 白瓷洗手盆发黄怎么办 挎包没有拉链东西容易掉怎么办 斜挎包肩带长了怎么办 3个月宝宝不吃奶怎么办 米饭扔厕所堵了怎么办 门过梁搭接不够怎么办 华为g7开不了机怎么办 警务通手机丢了怎么办 手机被伪基站覆盖怎么办 听了高频率声音怎么办 qq音乐签到没了怎么办 手机qq音乐不能播放怎么办 台式电脑放歌没有声音怎么办 微信图片上传大愎怎么办 行车记录仪内存卡丢了怎么办 投资项目失败lp的钱怎么办 无线网无ip分配怎么办 为什么电脑的暴风影音打不开怎么办 电枪充电板进水怎么办