跳转到主要内容
zheojian 提交于 13 October 2014

原文链接:Annotations-based plugins

Drupal 8 中大部分插件使用注解来注册和描述元数据。也有一些插件类型由核心提供:

  • Entities (未来可能不作为插件)
  • Blocks (在 */lib/Drupal/*/Plugin/Block/* 查看更多示例)
  • 字段格式器,字段小工具>(在 */lib/Drupal/*/Plugin/field/* 查看更多示例)
  • 所有Views插件(在 */lib/Drupal/*/Plugin/views/* 查看更多示例)

你应该在除此之外的文档中去查看一些实际案例。

psr-4

插件使用的注解被注册在PHP文件中使用psr-4标准,Drupal核心也是使用这个标准。

要注册插件,基于你的Drupal module root放置文件:src/Plugin/$plugin_type/Example.php

例如:core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php

为了告诉系统这是一个插件,你需要为你的class添加如下注释:

<?php /** * @Plugin( * * ) */ ?>

使用这种annotations class的一个具体的插件是UserNameUnique,在 core/modules/user/src/Plugin/Validation/Constraint/UserNameUnique.php文件中

这个注解仅包含id和label。

<?php /** * Checks if a user name is unique on the site. * * @Plugin( * id = "UserNameUnique", * label = @Translation("User name unique", context = "Validation") * ) */ class UserNameUnique extends Constraint { ... } ?>

为何使用注解?

相对于其他的发现机制,注解的元数据存在于同一个文件,并作为该插件的实现类的一个组成部分。这使得插件更容易被找到,也更容易被自定义,仅需简单地复制一个现有的插件。

注解允许复杂的结构化数据,你也可以指明某些字符串是被翻译的。很多情况下插件的相关联的自定义注解类既能作为文档,也能够为元数据设置默认值。

另外,注解能额外提升Drupal的性能,它使Drupal在发现插件时使用很少的内存。原来的实例中每个类都有一个getinfo()方法,类似Drupal 7 的test classes。这意味着每一个类都必须被加载到内存来获取其信息,并且内存不会被释放除非请求结束,这会大大增加PHP对Peek Memory的需求。相反,Drupal解析注解的实现只是简单的对文件的文本进行分词,而不是作为PHP文件加载,所以使内存的使用率降到最低。

注解语法

简而言之,注解是一种天然的key-value数据结构,包括嵌套的。

注解的语法来自于Doctrine项目(具体参考http://docs.doctrine-project.org/en/2.0.x/reference/annotations-referenc...),尽管Drupal的语法稍有不同,比如每个值后面都换行,但这任然有用。

  • 必须以插件id开始,相当于Drupal 7 的机器代码,作为插件的唯一键。
  • 顶级的KEYS可以使用双引号
  • 下级的KEYS必须使用双引号
  • 只能使用双引号,不能使用单引号,否则抛出异常
  • 值可用的数据类型
    • String:必须使用引号(例如:"foo"
    • Numbers:不能使用引号(例如:21)——使用了引号会被转化成字符串
    • Boolean:不能使用引号(例如:TRUE 或 FALSE)——使用了引号会被转化成字符串
    • Lists:使用大括号,末尾不要用逗号 base = { "node", "foo" }
    • Maps:使用大括号,用等号分隔键和值,末尾不要用逗号 edit = { "editor" = "direct" }
  • 可以使用常量

自定义注解类

基础的插件注解能被用于发现(插件),但是如果你想在插件定义中添加默认值和文档,你可以使用自定义注解类。

我们来看一个实际案例,plaintext格式器,位于text/src/Plugin/field/formatter/TextPlainFormatter.php

它使用自己的注解类,FieldFormatter,继承自\Drupal\Component\Annotation\Plugin

<?php /** * Plugin implementation of the 'text_plain' formatter. * * @FieldFormatter( * id = "text_plain", * label = @Translation("Plain text"), * field_types = { * "text", * "text_long", * "text_with_summary" * }, * edit = { * "editor" = "direct" * } * ) */ class TextPlainFormatter { ?>

在你自己的插件类型中使用注解

如果你自己写了一个插件类型,并且想要使用注解,只需要在你的plugin manager里面引用AnnotatedClassDiscovery——见下面代码。

AnnotatedClassDiscovery构造函数的第一个参数是这个插件类型被存放的子目录/子命名空间。所以下例中,插件会被在$module/src/Plugin/field/formatter文件夹中搜索到。

<?php use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery; class FormatterPluginManager extends PluginManagerBase { /** * Constructs a FormatterPluginManager object. * * @param array $namespaces * An array of paths keyed by their corresponding namespaces. */ public function __construct(array $namespaces) { // This is the essential line you have to use in your manager. $this->discovery = new AnnotatedClassDiscovery('Plugin/field/formatter', $namespaces); // Every other line is a good practice. $this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition')); $this->discovery = new AlterDecorator($this->discovery, 'field_formatter_info'); $this->discovery = new CacheDecorator($this->discovery, 'field_formatter_types', 'field'); } } ?>

注入的命名空间来自于依赖注入容器。例如FieldBundle:

<?php /** * @file * Contains Drupal\field\FieldBundle. */ namespace Drupal\field; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; /** * Field dependency injection container. */ class FieldBundle extends Bundle { /** * Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build(). */ public function build(ContainerBuilder $container) { // Register the plugin managers for our plugin types with the dependency injection container. $container->register('plugin.manager.field.widget', 'Drupal\field\Plugin\Type\Widget\WidgetPluginManager') ->addArgument('%container.namespaces%'); $container->register('plugin.manager.field.formatter', 'Drupal\field\Plugin\Type\Formatter\FormatterPluginManager') ->addArgument('%container.namespaces%'); } } ?>

调用你自定义的插件管理器,你需要在调用构造函数的时候注入命名空间。

<?php $type = new CustomPluginManager(\Drupal::getContainer()->getParameter('container.namespaces')); ?>