logo

额外区块类型 (EBT) - 全新的布局构建器体验❗

额外区块类型 (EBT) - 样式化、可定制的区块类型:幻灯片、标签页、卡片、手风琴等更多类型。内置背景、DOM Box、JavaScript 插件的设置。立即体验布局构建的未来。

演示 EBT 模块 下载 EBT 模块

❗额外段落类型 (EPT) - 全新的 Paragraphs 体验

额外段落类型 (EPT) - 类似的基于 Paragraph 的模块集合。

演示 EPT 模块 滚动

滚动
04/10/2025, by Ivan

Menu

本教程最初发布在 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 版本并试一试。