logo

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

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

演示 EBT 模块 下载 EBT 模块

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

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

演示 EPT 模块 滚动

滚动

概览:创建自定义字段

30/09/2025, by Ivan
Этот教程最初发布在 Web Wash。不过 Berdir 问我能否在这里发布,所以这就是它。

Drupal 7 的一个模块允许在字段中存储代码示例/片段。它带有一个名为「片段字段」的自定义字段,并显示三个表单元素:描述、源代码和语法高亮模式(选择编程语言)。

但现在是时候将模块升级到 Drupal 8 了。

在本教程中,我将向你展示我是如何在 Drupal 8 中创建一个「基础」自定义字段的。我不会深入讲解 PSR – 4注解插件,否则教程会过于庞大。

相反,我会添加指向其他网站的链接,这些网站会更深入解释这些概念。

另外,如果你正在寻找 Drupal 8 Field API 的详细文档,请查看以下系列文章:

 

在 Drupal 8 中,字段不像 Drupal 7 那样通过 hook 实现,而是通过新的 Drupal 8 插件 API 创建。这意味着我们不再实现 hooks,而是为小部件、格式化器和字段元素定义类。Drupal 7 中的大多数 hooks(如 hook_field_schema、hook_field_is_empty 等),现在都变成了类中的方法。

步骤 1:实现字段元素

第一步,我们需要定义一个名为 SnippetsItem 的字段元素类,它扩展自 FieldItemBase。

1. 在 Drupal 8 中,类是通过 PSR-4 加载的。

因此,要定义 SnippetsItem 类,我们需要创建 SnippetsItem.php 文件,并将其放在「module」/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 中,许多(如果不是全部)信息类 hooks 都被 注解 取代了。

/**
 * 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 中,创建自定义字段时你通过 hook_field_schema 定义 schema。在 Drupal 8 中,我们通过在 SnippetsItem 类中添加 schema() 方法来定义 schema。

Schema API 文档包含数组结构和可能值的说明。
/**
 * {@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;
  }

该方法用于定义字段值中存在的数据类型。“片段字段”只有三个值:描述、代码和语言。因此我在方法中将这些值定义为字符串。

想要了解更多,请查看 drupal.org 上的 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. 最后,我们需要定义小部件的实际表单。通过在 SnippetsDefaultWidget 类中添加 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) {
    // Render output using snippets_default theme.
    $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 而不是 hooks 创建的。一旦你理解了这一点,创建字段的概念和 Drupal 7 非常相似。Drupal 8 中的许多方法都对应于 Drupal 7 的 hooks。

如果你想测试 代码片段,请下载 8.x-dev 版本并试用。