概览:创建自定义字段类型
本教程最初发布在 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 版本并试一试。