【翻译】Drupal 8 自定义Field Type

原文链接:http://blog.netgloo.com/2016/04/16/drupal-8-creating-a-new-custom-field-...

如果你需要在一个内容类型(Content Type)里面添加复杂的字段(Fields),比如一组由文本,数字,图片组成的字段,而且有可能在一篇内容里面重复多次,最简单而且最快的方法就是联合使用这些优秀的Drupal模块来处理这种需求:ParagraphsField collectionField group.

如果你正在搜索一些特殊的字段类型,那你应该看一下这些出色的字段类型模块里面是否有满足你的需求的模块: Double field,Color fieldAddressTaxonomy containerAdvanced text formatter.

但是,如果上面列出的模块都不能满足你的需求,可能因为你真的需要一个非常特别的字段类型,或者你希望能更多地控制这个字段类型,比如它的验证,存储,编辑和显示,或者你只想要一个小小的功能,但是你不想要安装一整个模块,或者还有其他的理由,那么你就可能想要创建一个你自己的自定义字段类型了。

自定义一个字段类型

创建一个新的模块

drupal里面需要在一个模块里面定义一个字段类型,所以我们需要创建一个新的模块。

如果目录modules/custom不存在的话,则创建它。

在custom目录下面,我们创建以下控制字段类型的目录结构。

address/
  + src/
  |   + Plugin/
  |   |    + Field/
  |   |    |   + FieldFormatter/
  |   |    |   + FieldType/
  |   |    |   + FieldWidget/   
  + address.info.yml

重要:模块名称必须是小写,否则drupal会忽视它。

File .info.yml

在这个以.info.yml扩展名的文件里面,我们设置一些关于这个模块的信息,例如模块名,介绍,包名必须是 Field Type 和依赖列表里面必须包含 field

name: Address
description: Defines a simple address field type.
# The type must be 'module' and the package must be 'Field types'
type: module
package: Field types
core: 8.x

# The list of dependencies must contains the 'field' value
dependencies:
  - field

Field type

这个目录下面创建一个新的php文件用来定义字段类型:

src/Plugin/Field/FieldType/Address.php
<?php
namespace Drupal\address\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface as StorageDefinition;
/**
 * Plugin implementation of the 'address' field type.
 *
 * @FieldType(
 *   id = "Address",
 *   label = @Translation("Address"),
 *   description = @Translation("Stores an address."),
 *   category = @Translation("Custom"),
 *   default_widget = "AddressDefaultWidget",
 *   default_formatter = "AddressDefaultFormatter"
 * )
 */
 class Address extends FieldItemBase {  
 
 /**
   * Field type properties definition.
   * 
   * Inside this method we defines all the fields (properties) that our 
   * custom field type will have.
   * 
   * Here there is a list of allowed property types: https://goo.gl/sIBBgO
   */
  public static function propertyDefinitions(StorageDefinition $storage) {
      $properties = [];    
      $properties['street'] = DataDefinition::create('string')
      ->setLabel(t('Street'));
          
      $properties['city'] = DataDefinition::create('string')
      ->setLabel(t('City'));    
      return $properties;
  }  
  
  /**
   * Field type schema definition.
   * 
   * Inside this method we defines the database schema used to store data for 
   * our field type.
   * 
   * Here there is a list of allowed column types: https://goo.gl/YY3G7s
   */
  public static function schema(StorageDefinition $storage) {
      $columns = [];    
      $columns['street'] = [
           'type' => 'char',      
           'length' => 255,
      ];
      $columns['city'] = [
          'type' => 'char',      
          'length' => 255,
      ];    
      return [
          'columns' => $columns,      
          'indexes' => [],
      ];
  }
  
  /**
   * Define when the field type is empty. 
   * 
   * This method is important and used internally by Drupal. Take a moment
   * to define when the field fype must be considered empty.
   */
  public function isEmpty() {
      $isEmpty = 
      empty($this->get('street')->getValue()) &&
      empty($this->get('city')->getValue());    
      return $isEmpty;
  }

} // class

上面的这个类的注释里面有一个 @FieldType 的注释,这个注释定义了这个字段类型和一些必须配置的属性,例如 id,label 等等,还有两个属性,default_widget和default_formatter,分别对应编辑页的显示和结果页的显示,我们待会定义这个widget和formater(译者注:这两个不知道怎么翻译成中文,触发器什么的感觉都不合适).

这个类里面定义了这个字段类型:用 propertyDefinitions 方法定义了字段类型的有哪些字段,用schema方法定义了如何存储到数据库和用empty方法定义字段值为空的时候应该如何处理。

在这个类里面你还可以定义其他的逻辑处理方法,比如使用getConstraints方法验证数据,这儿有一个例子。

Field widget

在 src/Plugin/Field/FieldWidget 这个目录丽娜面创建一个新的php文件用来定义字段的widget。

src/Plugin/Field/FieldWidget/AddressDefaultWidget.php
<?php
namespace Drupal\address\Plugin\Field\FieldWidget;
use Drupal;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
/**
 * Plugin implementation of the 'AddressDefaultWidget' widget.
 *
 * @FieldWidget(
 *   id = "AddressDefaultWidget",
 *   label = @Translation("Address select"),
 *   field_types = {
 *     "Address"
 *   }
 * )
 */
 class AddressDefaultWidget extends WidgetBase {
   /**
   * Define the form for the field type.
   * 
   * Inside this method we can define the form used to edit the field type.
   * 
   * Here there is a list of allowed element types: https://goo.gl/XVd4tA
   */
  public function formElement(
    FieldItemListInterface $items,    
    $delta, 
    Array $element, 
    Array &$form, 
    FormStateInterface $formState
  ) {
  
    // Street
    $element['street'] = [
          '#type' => 'textfield',      
          '#title' => t('Street'),      
          // Set here the current value for this field, or a default value (or 
          // null) if there is no a value
          '#default_value' => isset($items[$delta]->street) ? $items[$delta]->street : null,      
          '#empty_value' => '',      
          '#placeholder' => t('Street'),
    ];
    
    // City
    $element['city'] = [
          '#type' => 'textfield',      
          '#title' => t('City'),      
          '#default_value' => isset($items[$delta]->city) ? $items[$delta]->city : null,      
          '#empty_value' => '',      
          '#placeholder' => t('City'),
    ];
    return $element;
  }

} // class

@FieldWidget 注释定义了这个字段的widget。在这个注释里面必须设置id,这个id的值是在 @FieldType注释里面default_widget使用的。我们使用类名作为这个id的值,但是同样可以使用其他不同的名称。field_types属性列出了所有使用这个widget的字段类型,在这里我们配置了前面定义的字段类型。

使用formElement方法定义了当这个Address字段类型被使用的时候编辑页显示的表单。在这个字段类型的定义里面,你能够定义更加复杂的逻辑并且给你的字段类型创建特殊的表单,这里有两个例子 - 例子一例子二

Field formatter

在 src/Plugin/Field/FieldFormatter 这个目录下面创建一个新的php文件用来定义字段的formatter。

src/Plugin/Field/FieldFormatter/AddressDefaultFormatter.php
<?php
namespace Drupal\address\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;use Drupal;

/**
 * Plugin implementation of the 'AddressDefaultFormatter' formatter.
 *
 * @FieldFormatter(
 *   id = "AddressDefaultFormatter",
 *   label = @Translation("Address"),
 *   field_types = {
 *     "Address"
 *   }
 * )
 */
 class AddressDefaultFormatter extends FormatterBase {
 
   /**
   * Define how the field type is showed.
   * 
   * Inside this method we can customize how the field is displayed inside 
   * pages.
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
      $elements = [];    
      foreach ($items as $delta => $item) {
            $elements[$delta] = [
                    '#type' => 'markup',        
                    '#markup' => $item->street . ', ' . $item->city
            ];
      }
      return $elements;
  }
  
} // class


@FieldFormatter 注释定义了这个字段的formatter,并且跟@FieldWidget的注释十分类似。

使用 viewElements 方法定义这个字段类型的数据如何在结果页面显示。如果想要使用 HTML 来显示这个字段类型,你可以为这个字段类型自定义一个theme,并且避免在 PHP 代码里面使用 HTML 的标签,这儿有一个例子。