logo

パレット - カラフルに🎨

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

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

Scroll

9.10.3. Drupal でのフィールドの操作。Youtube 動画を挿入するための独自のフィールドタイプ、ウィジェット、フォーマッターの作成。

02/10/2019, by mikhail

Menu

前の記事で、Link フィールドタイプがどう動作するかを見ました:Storage、Widget、Formatter。この記事では、2 つの異なる形式と設定を持つ、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;
Define namespaces for our type of field.

/**
 * 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"
 * )
 */

クラスのアノテーションを書きます。このアノテーションから、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 タイプの value フィールドを保存することを定義します。

/**
 * {@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"
 *   }
 * )
 */

クラスのアノテーションでは、上で作成した field_type、すなわち drupalbook_youtube を指定する必要があります。

class DrupalbookYoutubeWidget extends WidgetBase {

このクラスが Field Widget に必要であることを示すために、クラス名の末尾に 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 で指定した Validation コールバックです。これは、ユーザーが youtube 動画への正しいリンクを入力したことを保証するためのものです。正規表現は stackoverflow から取りました。うまく動作しない場合は置き換えられます。

これでフィールドのデータを入力できます。あとはデータを出力するための Field Formatter を追加するだけです。

modules/custom/drupalbook_youtube/src/Plugin/FieldFieldFormatter/DrupalbookYoutubeThumbnailFormatter.php

2 つのフォーマッターを用意します。シンプルなものから始めます。これは、Youtube 動画ページへのリンク付きの画像を表示します。

<?php

namespace Drupal\drupalbook_youtube\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;

/**
 * Plugin implementation of the 'drupalbook_youtube_thumbnail' formatter.
 *
 * @FieldFormatter(
 *   id = "drupalbook_youtube_thumbnail",
 *   module = "drupalbook_youtube",
 *   label = @Translation("Displays video thumbnail"),
 *   field_types = {
 *     "drupalbook_youtube"
 *   }
 * )
 */
class DrupalbookYoutubeThumbnailFormatter extends FormatterBase {

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = array();

    foreach ($items as $delta => $item) {
      preg_match("#(?<=v=)[a-zA-Z0-9-]+(?=&)|(?<=v\/)[^&\n]+(?=\?)|(?<=v=)[^&\n]+|(?<=youtu.be/)[^&\n]+#", $item->value, $matches);

      if (!empty($matches)) {
        $content = '<a href="' . $item->value . '" target="_blank"><img src="http://img.youtube.com/vi/' . $matches[0] . '/0.jpg"></a>';
        $elements[$delta] = array(
          '#type' => 'html_tag',
          '#tag' => 'p',
          '#value' => $content,
        );
      }

    }

    return $elements;
  }

}

クラスのアノテーションでフィールドタイプを指定します:

/**
 * Plugin implementation of the 'drupalbook_youtube_thumbnail' formatter.
 *
 * @FieldFormatter(
 *   id = "drupalbook_youtube_thumbnail",
 *   module = "drupalbook_youtube",
 *   label = @Translation("Displays video thumbnail"),
 *   field_types = {
 *     "drupalbook_youtube"
 *   }
 * )
 */

#type html_tag を使い、各フィールド項目を出力します。#value キーの値として、ページに表示される既成の HTML を配列に渡します:

$content = '<a href="' . $item->value . '" target="_blank"><img src="http://img.youtube.com/vi/' . $matches[0] . '/0.jpg"></a>';
$elements[$delta] = array(
  '#type' => 'html_tag',
  '#tag' => 'p',
  '#value' => $content,
);

ビュー画像はすでに Youtube によって生成されており、動画の ID を知っていれば直接アクセスできます。

複数のフィールド値をサポートするために $delta を使い、1 つのフィールドを通じて 1 ページに複数の動画を出力できるようにします。

次に、テンプレートと設定を持つ、より複雑な形式を見てみましょう:

modules/custom/drupalbook_youtube/src/Plugin/FieldFieldFormatter/DrupalbookYoutubeVideoFormatter.php

<?php

namespace Drupal\drupalbook_youtube\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;

/**
 * Plugin implementation of the 'drupalbook_youtube_video' formatter.
 *
 * @FieldFormatter(
 *   id = "drupalbook_youtube_video",
 *   module = "drupalbook_youtube",
 *   label = @Translation("Displays Youtube video"),
 *   field_types = {
 *     "drupalbook_youtube"
 *   }
 * )
 */
class DrupalbookYoutubeVideoFormatter extends FormatterBase {

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return array(
      'width' => '600',
      'height' => '450',
    ) + parent::defaultSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $elements['width'] = array(
      '#type' => 'textfield',
      '#title' => t('Youtube video width'),
      '#default_value' => $this->getSetting('width'),
    );
    $elements['height'] = array(
      '#type' => 'textfield',
      '#title' => t('Youtube video height'),
      '#default_value' => $this->getSetting('height'),
    );

    return $elements;
  }

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = array();
    $width = $this->getSetting('width');
    $height = $this->getSetting('height');

    foreach ($items as $delta => $item) {
      preg_match("#(?<=v=)[a-zA-Z0-9-]+(?=&)|(?<=v\/)[^&\n]+(?=\?)|(?<=v=)[^&\n]+|(?<=youtu.be/)[^&\n]+#", $item->value, $matches);

      if (!empty($matches)) {
        $elements[$delta] = array(
          '#theme' => 'drupalbook_youtube_video_formatter',
          '#width' => $width,
          '#height' => $height,
          '#video_id' => $matches[0],
        );
      }

    }

    return $elements;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $summary = [];

    $settings = $this->getSettings();

    if (!empty($settings['width']) && !empty($settings['height'])) {
      $summary[] = t('Video size: @width x @height', ['@width' => $settings['width'], '@height' => $settings['height']]);
    }
    else {
      $summary[] = t('Define video size');
    }

    return $summary;
  }

}

ファイルの先頭には、標準の namespace の接続とアノテーションがあります:

<?php

namespace Drupal\drupalbook_youtube\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;

/**
 * Plugin implementation of the 'drupalbook_youtube_video' formatter.
 *
 * @FieldFormatter(
 *   id = "drupalbook_youtube_video",
 *   module = "drupalbook_youtube",
 *   label = @Translation("Displays Youtube video"),
 *   field_types = {
 *     "drupalbook_youtube"
 *   }
 * )
 */

クラス名は、それが Field Formatter であることを示すために Formatter で終わります:

class DrupalbookYoutubeVideoFormatter extends FormatterBase {

画像サイズのデフォルト設定を定義します:

/**
 * {@inheritdoc}
 */
public static function defaultSettings() {
  return array(
    'width' => '600',
    'height' => '450',
  ) + parent::defaultSettings();
}
Next, we define the form of settings for the field formatter:

/**
 * {@inheritdoc}
 */
public function settingsForm(array $form, FormStateInterface $form_state) {
  $elements['width'] = array(
    '#type' => 'textfield',
    '#title' => t('Youtube video width'),
    '#default_value' => $this->getSetting('width'),
  );
  $elements['height'] = array(
    '#type' => 'textfield',
    '#title' => t('Youtube video height'),
    '#default_value' => $this->getSetting('height'),
  );

  return $elements;
}

これは Manage display ページで次のように見えます:

above

settingsSummary() メソッドでは、Manage display ページのフィールドの説明に表示されるものを返します:

public function settingsSummary() {
  $summary = [];

  $settings = $this->getSettings();

  if (!empty($settings['width']) && !empty($settings['height'])) {
    $summary[] = t('Video size: @width x @height', ['@width' => $settings['width'], '@height' => $settings['height']]);
  }
  else {
    $summary[] = t('Define video size');
  }

  return $summary;
}

video

それでは、フォーマッターの最も重要なメソッド viewElements() を見てみましょう。

$width = $this->getSetting('width');
$height = $this→getSetting('height');

設定を読み込みます。これは設定フォームを通じて定義されるか、フォールバックとしてクラスのプロパティで定義されます。

foreach ($items as $delta => $item) {

複数のフィールドをサポートするので、フィールドのすべての要素をたどります。

$elements[$delta] = array(
  '#theme' => 'drupalbook_youtube_video_formatter',
  '#width' => $width,
  '#height' => $height,
  '#video_id' => $matches[0],
);

前の形式とは異なり、この形式では #theme キーを指定し、フィールド要素を処理するためにどのテンプレートを使うかをその中で指定します。私たちのフォーマッター用に、新しいテンプレート drupalbook_youtube_video_formatter を作成します。このテンプレートは drupalbook_youtube.module ファイルで定義します:

modules/custom/drupalbook_youtube/drupalbook_youtube.module

/**
 * Implements hook_theme().
 */
function drupalbook_youtube_theme() {
  return array(
    'drupalbook_youtube_video_formatter' => array(
      'variables' => array('width' => 600, 'height' => 450, 'video_id' => NULL),
    ),
  );
}

ここでもデフォルトの width と height の値を取り、video_id は $elements 配列の '#video_id' => $matches[0] から取得します。

そして今、テンプレートファイル自体を追加する必要があります:

modules/custom/drupalbook_youtube/templates/drupalbook-youtube-video-formatter.html.twig

{#
/**
 * @file
 * Default theme implementation of a simple Youtube video.
 *
 * Available variables:
 * - width: Youtube video width.
 * - height: Youtube video height.
 * - video_id: Youtube video ID.
 *
 * @see template_preprocess()
 * @see template_drupalbook_youtube_video_formatter()
 *
 * @ingroup themeable
 */
#}
{% spaceless %}
  <iframe width="{{ width }}" height="{{ height }}" src="https://www.youtube.com/embed/{{ video_id }}" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen>iframe>
{% endspaceless %}

これで、Youtube 動画フィールドを作成できます:

link

ここでカスタムフィールドの作成を終え、Entity API に進みます。そこでカスタムタイプのエンティティを作成します。