原文链接: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" }
- String:必须使用引号(例如:
- 可以使用常量
自定义注解类
基础的插件注解能被用于发现(插件),但是如果你想在插件定义中添加默认值和文档,你可以使用自定义注解类。
我们来看一个实际案例,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'));
?>