logo

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

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

演示 EBT 模块 下载 EBT 模块

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

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

演示 EPT 模块 滚动

滚动

9.10.3. 在 Drupal 中处理字段。创建自定义字段类型、小部件和格式化器,用于插入来自 YouTube 的视频。

17/10/2025, by Ivan

Menu

在之前的文章中,我们已经了解了链接字段类型(Link field type)的工作方式:存储(Storage)、小部件(Widget)、格式化器(Formatter)。在本篇文章中,我们将创建一个自定义的字段类型,用于在页面上输出来自 YouTube 的视频,并提供两种不同的显示格式和设置。

本文重点介绍 Fields API。如果你只是想为你的网站添加一个 YouTube 视频字段,那么更好的选择是直接使用现成的模块:

https://www.drupal.org/project/video_embed_field

我已经将所有代码上传到了 GitHub 中的 drupalbook_youtube 模块中,你可以下载该模块并将其添加到你的站点:

https://github.com/levmyshkin/drupalbook8

我们来看一下该模块的文件结构,并尝试解释这种字段类型是如何工作的:

modules/custom/drupalbook_youtube/drupalbook_youtube.info.yml

name: DrupalBook Youtube
type: module
description: Youtube embed field
core: 8.x
package: Custom

定义模块的元数据。

modules/custom/drupalbook_youtube/src/Plugin/Field/FieldType/DrupalbookYoutubeItem.php

<?php
 
namespace Drupal\drupalbook_youtube\Plugin\Field\FieldType;
 
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
 
/**
 * Plugin implementation of the 'drupalbook_youtube' field type.
 *
 * @FieldType(
 *   id = "drupalbook_youtube",
 *   label = @Translation("Embed Youtube video"),
 *   module = "drupalbook_youtube",
 *   description = @Translation("Output video from Youtube."),
 *   default_widget = "drupalbook_youtube",
 *   default_formatter = "drupalbook_youtube_thumbnail"
 * )
 */
class DrupalbookYoutubeItem extends FieldItemBase {
  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    return array(
      'columns' => array(
        'value' => array(
          'type' => 'text',
          'size' => 'tiny',
          'not null' => FALSE,
        ),
      ),
    );
  }
 
  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    $value = $this->get('value')->getValue();
    return $value === NULL || $value === '';
  }
 
  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties['value'] = DataDefinition::create('string')
      ->setLabel(t('Youtube video URL'));
 
    return $properties;
  }
 
}

创建字段类型,使 Drupal 知道我们将在该字段的数据库表中存储什么内容。

<?php
 
namespace Drupal\drupalbook_youtube\Plugin\Field\FieldType;
 
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
定义我们字段类型的命名空间。

为我们的类编写抽象描述,Drupal 将根据此注释获取字段类型名称和机器名称。

class DrupalbookYoutubeItem extends FieldItemBase {

类名最好以 Item 结尾。

/**
 * {@inheritdoc}
 */
public static function schema(FieldStorageDefinitionInterface $field_definition) {
  return array(
    'columns' => array(
      'value' => array(
        'type' => 'text',
        'size' => 'tiny',
        'not null' => FALSE,
      ),
    ),
  );
}

定义我们将在数据库中存储的列类型(text)。

/**
 * {@inheritdoc}
 */
public function isEmpty() {
  $value = $this->get('value')->getValue();
  return $value === NULL || $value === '';
}

当从第三方代码调用该字段时,如果字段为空,则返回一个空结果作为回退。

/**
 * {@inheritdoc}
 */
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
  $properties['value'] = DataDefinition::create('string')
    ->setLabel(t('Youtube video URL'));
 
  return $properties;
}

描述 MySQL 表的列和实体对象。最终我们会存储整个链接:

select

现在我们已经添加了字段类型,接下来创建一个 Widget 用于数据输入:

modules/custom/drupalbook_youtube/src/Plugin/Field/FieldWidget/DrupalbookYoutubeWidget.php

<?php
 
namespace Drupal\drupalbook_youtube\Plugin\Field\FieldWidget;
 
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
 
/**
 * Plugin implementation of the 'drupalbook_youtube' widget.
 *
 * @FieldWidget(
 *   id = "drupalbook_youtube",
 *   module = "drupalbook_youtube",
 *   label = @Translation("Youtube video URL"),
 *   field_types = {
 *     "drupalbook_youtube"
 *   }
 * )
 */
class DrupalbookYoutubeWidget extends WidgetBase {
 
  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $value = isset($items[$delta]->value) ? $items[$delta]->value : '';
    $element += array(
      '#type' => 'textfield',
      '#default_value' => $value,
      '#size' => 32,
      '#maxlength' => 256,
      '#element_validate' => array(
        array($this, 'validate'),
      ),
    );
    return array('value' => $element);
  }
 
  /**
   * Validate the color text field.
   */
  public function validate($element, FormStateInterface $form_state) {
    $value = $element['#value'];
    if (strlen($value) == 0) {
      $form_state->setValueForElement($element, '');
      return;
    }
    if(!preg_match("#(?<=v=)[a-zA-Z0-9-]+(?=&)|(?<=v\/)[^&\n]+(?=\?)|(?<=v=)[^&\n]+|(?<=youtu.be/)[^&\n]+#", $value, $matches)) {
      $form_state->setError($element, t("Youtube video URL is not correct."));
    }
  }
 
}

该 Widget 将允许我们在实体编辑表单中输入数据。

/**
 * Plugin implementation of the 'drupalbook_youtube' widget.
 *
 * @FieldWidget(
 *   id = "drupalbook_youtube",
 *   module = "drupalbook_youtube",
 *   label = @Translation("Youtube video URL"),
 *   field_types = {
 *     "drupalbook_youtube"
 *   }
 * )
 */

在类注解中,我们必须指定上面创建的字段类型(drupalbook_youtube)。

class DrupalbookYoutubeWidget extends WidgetBase {

类名以 Widget 结尾,表示该类用于字段小部件(Field Widget)。

/**
 * {@inheritdoc}
 */
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
  $value = isset($items[$delta]->value) ? $items[$delta]->value : '';
  $element += array(
    '#type' => 'textfield',
    '#default_value' => $value,
    '#size' => 32,
    '#maxlength' => 256,
    '#element_validate' => array(
      array($this, 'validate'),
    ),
  );
  return array('value' => $element);
}

通过 Form API 创建一个简单的文本字段,用于输入 YouTube 视频链接。

/**
 * Validate the color text field.
 */
public function validate($element, FormStateInterface $form_state) {
  $value = $element['#value'];
  if (strlen($value) == 0) {
    $form_state->setValueForElement($element, '');
    return;
  }
  if(!preg_match("#(?<=v=)[a-zA-Z0-9-]+(?=&)|(?<=v\/)[^&\n]+(?=\?)|(?<=v=)[^&\n]+|(?<=youtu.be/)[^&\n]+#", $value, $matches)) {
    $form_state->setError($element, t("Youtube video URL is not correct."));
  }
}

上面的 #element_validate 指定了验证回调,用于确保用户输入的确实是一个有效的 YouTube 链接。这个正则表达式取自 StackOverflow,如果不适用,你可以更换。

现在我们可以为字段输入数据了,接下来我们将创建 Field Formatter,用于输出这些数据。