服务容器(Service Container)
第一章里,我们学习了Drupal的总体结构和它与Symfony框架的关系。Drupal 8和Symfony都由组件组成。这章里你将会学到什么是服务容器以及Drupal 8是如何使用它的。这对于学习日程分发(routing)非常重要。
Symfony使用服务容器在应用中高效地管理服务。这种方式被称为依赖注入(使用接口在服务间解除耦合)。
服务容器是一个全局对象,由核心在请求被处理前创建持有。它会在之后程序运行中使用懒惰加载的方式取得服务。服务是用来完成特定任务的全局对象,例如邮件服务器或是数据库连接器。一个服务由仅一个类实现。服务容器是非常重要的,因为它包含了可用的服务,了解服务间的关系和服务的配置,甚至于服务的结构!
依赖和实参(Dependencies and arguments)
一个服务也许会依赖其它的服务。Symfony文档用了如下例子说明,NewsletterManager服务需要Mailer服务来发送邮件。服务间的依赖关系也由服务容器管理。当创建一个服务时,它的依赖关系通过类构造函数的实参提供。接口用来定义被依赖的服务应该提供哪些方法,所以当需要时服务可以由另一个类来实现。
配置(Configuration)
服务容器中的服务们可以由如下几种方式配置:通过扩展代码,通过XML,通过YAML等等。Symfony组合使用这几种方式,但多数的内核服务的配置在框架捆绑的代码中存储。Drupal的服务配置与之不同,多数存储在YAML文件中。它使用services.core.yml文件保存所有内核服输的配置。这些配置能够被模块和ServiceProvider类们扩展,后一种方法类似Symfony中的扩展。配置的加载由核心处理。
YAML提供了一种简洁、可读的语法。下面是一个例子,来自Drupal 8的services.core.yml文件。
services:
...
router_listener:
class: Symfony\Component\HttpKernel\EventListener\RouterListener
tags:
- { name: event_subscriber }
arguments: ['@router']
...
router:
class: Symfony\Cmf\Component\Routing\ChainRouter
calls:
- [setContext, ['@router.request_context']]
- [add, ['@router.dynamic']]
...
上面是router_listener服务的定义。加载监听器需要与服务对应的类。Arguments项定义了RouterListener类构造函数的实参即url或请求的匹配器应该是“@router”。 “@router”表示匹配器的服务id为“router”。router服务也在文件中进行了定义,它的对应类和Symfony中的不同。
已标记服务
tags项用来加载被它标记的服务。在实践中,Drupal有时用这种方式实现钩子(a 'hooky' way 此处存疑)。在下面的例子(node.services.yml)中,node模块为“add node(添加节点)”页面添加了一个访问权限检查器。这个检查器基于access_check服务模式,它将在日程分发之后确定访问是否被允许。
services:
...
access_check.node.add:
class: Drupal\node\Access\NodeAddAccessCheck
arguments: ['@plugin.manager.entity']
tags:
- { name: access_check }
...
编译过程(Compiler passes)
Drupal是如何理解tags项中的内容并明白如何处理它们的呢?Symfony有另一个动态配置服务容器的方法——编译过程(Compiler passes)。服务容器依据静态配置建立之后会被直接编译。这其中有几点十分重要,它允许对象通过实现CompilerPassInterface修改配置。在Drupal中,CoreServiceProvider注册了一些重要的编译过程。像是RegisterAccessChecksPass,它尝试查找所有标记access_check的服务并将它们通过服务容器指令(container service directive 此处存疑)addMethodCall方法添加到AccessManager中。在日程分发的过程中,AccessManager服务被要求检查各种访问权限,NodeAddAccessCheck也包含其中。Drupal内核中还有其它的编译过程,像是将日程参数转换为对象的或是翻译字符串的。在实践中,你有时会在模块中使用这些被标记的服务来添加访客检查或是转换路径参数。
交换服务(Swapping services)
服务容器使得灵活的服务配置成为可能。Drupal 8使用服务实参更改Symfony的运作方式以实现自己的目的。举例来说,Druapl需要的url日程分发机制与Symfony不同。因此它提供了一个修改后的'router(日程处理器)'服务。Drupal使用(Symfony\Cmf\Component\Routing\ChainRouter),而Symfony使用(Symfony\Bundle\FrameworkBundle\Routing\Router)。这样的话不用对router_listener服务做一点修改就可以达成目标!这样做完全是可行的,因为两个日程处理器都实现了RequestMatcherInterface,它才是RouterListener的实参所真正需要的。
总结
这一章中我们学习了服务容器(Service Container)组件。你对Druapl如何使用注入地方式在不改变其它代码的情况下替换了Symfony2的服务有了了解。如果以后Symfony2升级这将为Drupal内核工作组的人节约大量的时间。在学习Drupal 8的过程中翻阅core.services.yml文件你将会发现Drupal使用了哪些服务。
下一章将会详述Drupal 8的工作流和日程分发。