logo

パレット - カラフルに🎨

Palette — ビジュアルページビルダー、デザインの専門知識は不要です。

ライブデモ パレットをダウンロード

Scroll

フィールドタイプ、フィールドウィジェット、フィールドフォーマッター

20/05/2020, by maria

概要

Drupal 8 には、独自のコンテンツを扱うための基本クラスの大きなライブラリが同梱されています。コンテンツエンティティに関しては、フィールドを使用したいものです。エンティティがデータを保存する場所であるため、フィールドを理解することが重要です。

フィールドタイプ(FieldTypes)

主なフィールドタイプ:

 

カスタムフィールドタイプ
Drupal が提供していない方法でデータを表現したい場合は、データ用の新しいフィールドタイプを作成したいと思うかもしれません。

機密データを含むコンテンツオブジェクトがあるとします。このコンテンツの作成者は、ユーザーごとに異なるパスワードを使用して、特定のユーザーのオブジェクトへのアクセスを許可できます。データベースのテーブルで言えば、次のようなものを作成したいでしょう:

| entity_id | uid | password      |
-----------------------------------
| 1         | 1   | 'helloworld'  |
| 1         | 2   | 'goodbye'     |

ご覧のとおり、ID 1 のエンティティには、2 人の異なるユーザーに対して 2 つの異なるパスワードがあります。では、手動でテーブルを作成せずに、これを Drupal で実装するにはどうすればよいでしょうか。新しいフィールドタイプを作成します。

Drupal はフィールドのロジックをプラグインとして実装するため、Drupal で動作させるために継承できる基本クラスが常にあります。新しいフィールドタイプでは、モジュール内に以下のフォルダ構造を作成します:
modules/custom/MODULENAME/src/Plugin/Field/FieldType
このパスはかなり長く、少し面倒ですが、Drupal はモジュール内で共存できるさまざまな機能を使いやすくしています。

この例では、EntityUserAccessField.php ファイルを作成します。

namespace Drupal\MODULENAME\Plugin\Field\FieldType;
     
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
     
/**
 * @FieldType(
 *   id = "entity_user_access",
 *   label = @Translation("Entity User Access"),
 *   description = @Translation("This field stores a reference to a user and a password for this user on the entity."),
 * )
*/
     
class EntityUserAccessField extends FieldItemBase {
  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    //ToDo: Implement this.
  }
     
  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    //ToDo: Implement this.
  }
}

ご覧のとおり、フィールドタイプはコンテンツエンティティによく似ています。実際、この 2 つの間に違いはありませんが、これは別のノードのトピックです;)

まず最初に、フィールドタイプ用のアノテーションがあります

  • @FieldType: これは Drupal ライブラリの FieldType アノテーションクラスを呼び出します
  • id: これはフィールドタイプのマシン名であり、再利用できます。php などの事前定義された名前を上書きしないようにしてください。
  • label: これはマシン名をユーザーが読める形に翻訳したものです。
  • description: ラベルだけでは十分でない場合、フィールドタイプの説明も追加できます。

 

次に、当社のクラスは FieldItemBase を拡張します。これにより、このフィールドタイプを正しく使用できるように 2 つのメソッドを実装する必要があります:

  • propertyDefinitions(): このメソッドは、コンテンツオブジェクトの baseFieldDefinition に似ています(同じものではありません!)。このフィールドタイプが使用されるエンティティのフォームに表示されるデータを定義できます。
  • schema(): エンティティではこのメソッドは非推奨ですが、フィールドではまだ使用されています。このメソッドは、フィールドのデータのデータベースでの表現を実装する必要があります。プロパティとは異なる場合があります。

これらのメソッドに何を書くべきかはあまり明確ではないため、便宜上、いくつかのコードを追加してみましょう。

public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
  $properties['uid'] = DataDefinition::create('integer')
      ->setLabel(t('User ID Reference'))
      ->setDescription(t('The ID of the referenced user.'))
      ->setSetting('unsigned', TRUE);

  $properties['password'] = DataDefinition::create('string')
      ->setLabel(t('Password'))
      ->setDescription(t('A password saved in plain text. That is not safe dude!'));

  $properties['created'] = DataDefinition::create('timestamp')
    ->setLabel(t('Created Time'))
    ->setDescription(t('The time that the entry was created'));

    // ToDo: Add more Properties.
 
    return $properties;
}

DataReferenceDefinition を使用してユーザー ID を保存することも可能ですが、これについては将来ここで検討されるかもしれません。

public static function schema(FieldStorageDefinitionInterface $field_definition) {
  $columns = array(
    'uid' => array(
      'description' => 'The ID of the referenced user.',
      'type' => 'int',
      'unsigned' => TRUE,
    ),
    'password' => array(
      'description' => 'A plain text password.',
      'type' => 'varchar',
      'length' => 255,
    ),
    'created' => array(
      'description' => 'A timestamp of when this entry has been created.',
      'type' => 'int',
    ),

    // ToDo: Add more columns.
  );
 
  $schema = array(
    'columns' => $columns,
    'indexes' => array(),
    'foreign keys' => array(),
  );

  return $schema;
}

Schema() は、Drupal がデータの保存方法を把握するために必要です。スキーマのカラムは、propertyDefinitions() で定義されたプロパティのサブセットである必要があります。

これで、まったく新しいフィールドタイプができました。これはデータ入力を処理するロジックを持ちませんが、任意のコンテンツオブジェクトでフィールドとして使用できます。必要に応じて、エンティティの baseField としても使用できます:

public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
  // Some fields above.
 
  $fields['entity_user_access'] = BaseFieldDefinition::create('entity_user_access')
    ->setLabel(t('Entity User Access'))
    ->setDescription(t('Specify passwords for any user that want to see this entity.'))
    ->setCardinality(-1); // Ensures that you can have more than just one member
 
  // Even more fields below.
 
  return $fields;
}
  • BaseFieldDefinition::create(): create() にフィールドタイプのマシン名を使用する必要があります
  • setCardinality(-1): カーディナリティは、1 つのエンティティが持つことができるフィールドデータの数を表します。たとえば、2 と書くと 2 人のユーザーだけがエンティティにアクセスでき、3 と書くと 3 人のユーザーになります。−1 は無制限のユーザーを表します。

 

フィールドウィジェット(FieldWidget)

カスタムデータがある場合、そのデータのカスタム表現が必要になることがあります。ウィジェットは、ユーザーがこれらのカスタムデータをフォームに入力する方法を表現するために使用されます。たとえば

  • フォームで整数を指定する必要があるが、ユーザーがチェックボックスしかオンにできない場合
  • データの自動入力を希望する場合
  • パスワードの入力が特別なグラフィカルインターフェイスを介して行われる場合

など。

Drupal のフィールドウィジェットは以下にあります:
modules/custom/MODULENAME/src/Plugin/Field/FieldWidget
これも非常に長いパスです。この時点で、Drupal が .php ファイルを分離するためにこのスタイルを使用している理由が理解できるはずです。どのファイルがどこに属しているかを見落としがちです。

EntityUserAccessWidget.php にフィールドウィジェットを作成します。

<code>namespace Drupal\MODULENAME\Plugin\Field\FieldWidget;
 
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
 
/**
 * Plugin implementation of the 'entity_user_access_w' widget.
 *
 * @FieldWidget(
 *   id = "entity_user_access_w",
 *   label = @Translation("Entity User Access - Widget"),
 *   description = @Translation("Entity User Access - Widget"),
 *   field_types = {
 *     "entity_user_access",
 *   },
 *   multiple_values = TRUE,
 * )
 */
 
class EntityUserAccessWidget extends WidgetBase {
  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    // ToDo: Implement this.
  }
}</code>

もう気づきましたか? Drupal 8 は、機能を実装したいときにこのコードスタイルを繰り返し使用します。アノテーションと、継承しなければならない基本クラスがあります。そうです、Drupal はこれを活用できるのです!

  • @FieldWidget: アノテーションクラスを定義します
  • id: ウィジェットのマシン名
  • field_types: このウィジェットを使用できるフィールドタイプのマシン名の配列
  • multiple_values: デフォルトは FALSE です。true の場合、エンティティフォームで複数の値を送信できます

このウィジェットをフィールドタイプで使用する場合は、フィールドタイプのアノテーションを次のように編集する必要があります

// ...

/**
 * @FieldType(
 *   id = "entity_user_access",
 *   label = @Translation("Entity User Access"),
 *   description = @Translation("This field stores a reference to a user and a password for this user on the entity."),
 *   default_widget = "entity_user_access_w",
 * )
 */
     
// ...

はい、これで完了です! いや、まだ何も起こりません。ウィジェットに formElement() を実装しなければならないからです。

public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $element['userlist'] = array(
      '#type' => 'select',
      '#title' => t('User'),
      '#description' => t('Select group members from the list.'),
      '#options' => array(
         0 => t('Anonymous'),
         1 => t('Admin'),
         2 => t('foobar'),
         // This should be implemented in a better way!
       ),
  
    );
  
    $element['passwordlist'] = array(
      '#type' => 'password',
      '#title' => t('Password'),
      '#description' => t('Select a password for the user'),
    );

    //setting default value to all fields from above
    $childs = Element::children($element);
    foreach ($childs as $child) {
        $element[$child]['#default_value'] = isset($items[$delta]->{$child}) ? $items[$delta]->{$child} : NULL;
    }
   
    return $element;
}

このウィジェットを含むフォームを開くと、少なくとも 2 つの入力フィールドが表示されます。1 つはユーザー選択、もう 1 つはパスワードフィールドです。データの保存方法を実装するには、このウィジェットまたはエンティティのフォームで検証メソッドを実装する必要があります。詳細については、 Drupal 8 Form API を参照してください。

ここまでで、カスタムフィールドの作業の大部分を完了しました。何が起こっているのか理解できない場合は、コードを試してみるか、トピックをより深く理解するためにコアモジュールを調べてください。

フィールドフォーマッター(FieldFormatters)

最後に欠けているのは、いわゆるエンティティのビューモードでのデータの表現です(ちなみに、ウィジェットはフォームモードです)。これは、yourdrupalpage.com/myentity/1/view を介してエンティティを呼び出す場合に最も頻繁に発生します。

ここではあまり議論がないので、コードに直接進みましょう。modules/custom/MODULENAME/src/Plugin/Field/FieldFormatter の下に、EntityUserAccessFormatter.php を作成します。

namespace Drupal\MODULENAME\Plugin\Field\FieldFormatter;
     
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
     
/**
 * Plugin implementation of the 'entity_user_access_f' formatter.
 *
 * @FieldFormatter(
 *   id = "entity_user_access_f",
 *   label = @Translation("Entity User Access - Formatter"),
 *   description = @Translation("Entity User Access - Formatter"),
 *   field_types = {
 *     "entity_user_access",
 *   }
 * )
 */
     
class EntityUserAccessFormatter extends FormatterBase {
  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = array();
     
    foreach ($items as $delta => $item) {
      $elements[$delta] = array(
        'uid' => array(
          '#markup' => \Drupal\user\Entity\User::load($item->uid)->getUsername(),
          ),
        // Add more content
      );
    }
     
    return $elements;
  }
}

この例のアノテーションはウィジェットと非常に似ているため、多くを説明する必要はありません。ViewElements() は、フィールドタイプに保存されたユーザー ID のユーザー名を表示するだけです。アクセスの実装はエンティティで行う必要があります。したがって、この実装は、エンティティにパスワードを持つすべてのユーザー名を表示します。