概览:创建自定义字段类型
本教程最初发布在 Web Wash。不过 Berdir 要求我在这里也发布,所以它就在这里了。
在 Drupal 7 中,该模块允许将代码示例/片段存储在一个字段中。它带有一个名为「代码片段字段」的自定义字段,并显示三个表单元素:描述、源代码和语法高亮模式(编程语言)。
但现在是时候将该模块升级到 Drupal 8 了。
在本教程中,我将向你展示如何在 Drupal 8 中创建一个「基础」的自定义字段。我不会深入讲解 PSR–4、注解 或 插件,否则本教程会变得非常庞大。
相反,我会附上其他网站的链接,它们更详细地解释了这些概念。
同时,如果你正在寻找关于 Drupal 8 中 Field API 的详细文档,可以参考以下系列:
在 Drupal 8 中,字段不像 Drupal 7 那样通过 hook 来实现,而是通过新的 插件 API 创建的。这意味着我们不再实现 hook,而是为小部件、格式化器和字段项定义类。Drupal 7 中的大多数 hook(如 hook_field_schema、hook_field_is_empty 等),现在都变成了类中的方法。
步骤 1:实现字段项
我们需要做的第一件事是定义一个名为 SnippetsItem 的字段项类,它扩展 FieldItemBase。
1. 在 Drupal 8 中,类是通过 PSR-4 加载的。
因此,要定义 SnippetsItem 类,我们需要创建 SnippetsItem.php 文件,并将其放在「模块」/src/Plugin/Field/FieldType/SnippetsItem.php 下。
/** * @file * Contains \Drupal\snippets\Plugin\Field\FieldType\SnippetsItem. */ namespace Drupal\snippets\Plugin\Field\FieldType; use Drupal\Core\Field\FieldItemBase; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\TypedData\DataDefinition;
然后在文件中添加命名空间 Drupal\snippets\Plugin\Field\FieldType 以及以下三个 use:
- Drupal\Core\Field\FieldItemBase
- Drupal\Core\Field\FieldStorageDefinitionInterface
- Drupal\Core\TypedData\DataDefinition
2. 接下来,我们需要定义字段的实际细节,比如字段 ID、标签、默认小部件和默认格式化器。这相当于 Drupal 7 中的 hook_field_info。
在 Drupal 8 中,许多(如果不是全部)信息类 hook 都被 注解 替代了。
/**
* Plugin implementation of the 'snippets' field type.
*
* @FieldType(
* id = "snippets_code",
* label = @Translation("Snippets field"),
* description = @Translation("This field stores code snippets in the database."),
* default_widget = "snippets_default",
* default_formatter = "snippets_default"
* )
*/
class SnippetsItem extends FieldItemBase { }
因此,我们不再使用 hook_field_info,而是在类上方的注解中定义字段。
注解的属性含义显而易见。需要注意的是,default_widget 和 default_formatter 必须引用小部件和格式化器注解中的 ID,而不是类名。
如果你想了解更多关于注解的知识,可以访问 drupal.org 上的 基于注解的插件文档。
3. 接下来,我们需要定义 schema() 方法。
在 Drupal 7 中,定义字段 schema 使用的是 hook_field_schema。在 Drupal 8 中,我们在 SnippetsItem 类中实现 schema() 方法。
Schema API 文档 详细说明了 schema 数组的结构及其可能的值。
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field) {
return array(
'columns' => array(
'source_description' => array(
'type' => 'varchar',
'length' => 256,
'not null' => FALSE,
),
'source_code' => array(
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
),
'source_lang' => array(
'type' => 'varchar',
'length' => 256,
'not null' => FALSE,
),
),
);
}
4. 我们还需要实现 isEmpty() 方法,定义字段项在何种情况下为空。这相当于 Drupal 7 的 hook_field_is_empty。
/**
* {@inheritdoc}
*/
public function isEmpty() {
$value = $this->get('source_code')->getValue();
return $value === NULL || $value === '';
}
5. 最后我们要添加 propertyDefinitions() 方法。
/**
* {@inheritdoc}
*/
static $propertyDefinitions;
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['source_description'] = DataDefinition::create('string')
->setLabel(t('Snippet description'));
$properties['source_code'] = DataDefinition::create('string')
->setLabel(t('Snippet code'));
$properties['source_lang'] = DataDefinition::create('string')
->setLabel(t('Programming Language'))
->setDescription(t('Snippet code language'));
return $properties;
}
该方法用于定义字段值中的数据类型。「代码片段字段」有三个值:描述、代码和语言。因此我们在方法中添加了三个字符串类型的定义。
想了解更多内容,请查看 Entity API 如何实现 Typed Data API 的文档。
点击这里 查看完整文件。注意:该文件需要更新为符合 PSR-4 规范,详情见 https://www.drupal.org/node/2128865。
步骤 2:实现字段小部件
定义完字段项后,我们需要创建一个字段小部件。我们将创建一个名为 SnippetsDefaultWidget 的类,它扩展 WidgetBase。
1. 创建 SnippetsDefaultWidget.php 文件,并放在「module」/src/Plugin/Field/FieldWidget/SnippetsDefaultWidget.php。
/** * @file * Contains \Drupal\snippets\Plugin\Field\FieldWidget\SnippetsDefaultWidget. */ namespace Drupal\snippets\Plugin\Field\FieldWidget; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface;
命名空间必须是 Drupal\snippets\Plugin\Field\FieldWidget,并添加以下 use:
- Drupal\Core\Field\FieldItemListInterface
- Drupal\Core\Field\WidgetBase
- Drupal\Core\Form\FormStateInterface
2. 接下来通过注解定义小部件。这相当于 Drupal 7 的 hook_field_widget_info。
/**
* Plugin implementation of the 'snippets_default' widget.
*
* @FieldWidget(
* id = "snippets_default",
* label = @Translation("Snippets default"),
* field_types = {
* "snippets_code"
* }
* )
*/
class SnippetsDefaultWidget extends WidgetBase { }
请注意,注解中的 field_types 必须引用字段类型的 ID。这里是 snippets_code,因为我们在 @FieldType 注解中定义了 id = "snippets_code"。
3. 最后实现 formElement() 方法,定义小部件的表单。它相当于 Drupal 7 的 hook_field_widget_form。
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element['source_description'] = array(
'#title' => $this->t('Description'),
'#type' => 'textfield',
'#default_value' => isset($items[$delta]->source_description) ? $items[$delta]->source_description : NULL,
);
$element['source_code'] = array(
'#title' => $this->t('Code'),
'#type' => 'textarea',
'#default_value' => isset($items[$delta]->source_code) ? $items[$delta]->source_code : NULL,
);
$element['source_lang'] = array(
'#title' => $this->t('Source language'),
'#type' => 'textfield',
'#default_value' => isset($items[$delta]->source_lang) ? $items[$delta]->source_lang : NULL,
);
return $element;
}
点击这里 查看完整文件。注意:需要更新为符合 PSR-4 规范,详情见 https://www.drupal.org/node/2128865。
步骤 3:实现字段格式化器
最后一步是定义字段格式化器。我们创建一个名为 SnippetsDefaultFormatter 的类,它扩展 FormatterBase。
1. 创建 SnippetsDefaultFormatter.php 文件,并放在「module」/src/Plugin/Field/FieldFormatter/SnippetsDefaultFormatter.php。
/** * @file * Contains \Drupal\snippets\Plugin\field\formatter\SnippetsDefaultFormatter. */ namespace Drupal\snippets\Plugin\Field\FieldFormatter; use Drupal\Core\Field\FormatterBase; use Drupal\Core\Field\FieldItemListInterface;
命名空间必须是 Drupal\snippets\Plugin\Field\FieldFormatter,并添加以下 use:
- Drupal\Core\Field\FieldItemListInterface
- Drupal\Core\Field\FormatterBase
2. 接着使用注解定义格式化器,相当于 Drupal 7 的 hook_field_formatter_info。
/**
* Plugin implementation of the 'snippets_default' formatter.
*
* @FieldFormatter(
* id = "snippets_default",
* label = @Translation("Snippets default"),
* field_types = {
* "snippets_code"
* }
* )
*/
class SnippetsDefaultFormatter extends FormatterBase { }
3. 最后实现 viewElements() 方法,定义格式化器的渲染逻辑。它相当于 Drupal 7 的 hook_field_formatter_view。
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
foreach ($items as $delta => $item) {
// 使用 snippets_default 模板渲染输出
$source = array(
'#theme' => 'snippets_default',
'#source_description' => $item->source_description,
'#source_code' => $item->source_code,
);
$elements[$delta] = array('#markup' => drupal_render($source));
}
return $elements;
}
需要注意的是,我使用了自定义模板 snippets_default 来渲染代码片段,而不是直接在 viewElements() 方法中写入逻辑或 HTML。
点击这里 查看完整文件。注意:需要更新为符合 PSR-4 规范,详情见 https://www.drupal.org/node/2128865。
总结
如前所述,Drupal 8 中的最大变化是字段通过 插件 API 创建,而不是 hook。一旦理解了这一点,创建字段的概念与 Drupal 7 非常相似。Drupal 8 中的许多方法都对应 Drupal 7 中的 hook。
如果你想要 测试代码片段模块,可以下载 8.x-dev 版本并试一试。