Drupal 8 の Display Suite にカスタムのフィールドを追加する

Drupal でノードなどの表示形式を変更する場合、[Display Suite](https://www.drupal.org/project/ds) を使用してカスタマイズすることがあります。この際、ノードなどで定義されているフィールドは表示・非表示を切り替えることはできます。しかし、固定の文言を表示するためのフィールドや複数フィールドの値を組み合わせといったフィールドの表示を行うことは、Display Suite が標準で提供する機能だけでは難しいです。そこで、Display Suite にカスタムフィールドを追加するための方法を記載します。

## Display Suite のカスタムフィールド追加の基本形

Display Suite にカスタムフィールドを実装する際の基本形は下記になります。

```php
<?php

namespace Drupal\some_module\Plugin\DsField;

use Drupal\ds\Plugin\DsField\DsFieldBase;

/**
 * Generated field.
 *
 * @DsField(
 *   id = "some_module_DSFieldTest",
 *   title = @Translation("DSFieldTest"),
 *   entity_type = "user"
 * )
 */
class DSFieldTest extends DsFieldBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    return [
      '#markup' => 'Something field value.',
    ];
  }

}
```

コメント文のようになっている場所はアノテーションで、Display Suite フィールドの定義を行う場所です。
`build()` は、Display Suite で追加されたフィールドの出力を行う場所です。メソッドの返り値に [Render arrays](https://www.drupal.org/docs/8/api/render-api/render-arrays) を与えることで、表示内容を決定することができます。
なお、Display Suite の drush コマンドでフィールドを作成した場合、テンプレートは下記のようになります。`build()` 以外に関しては後述する Display Suite 作者のスライドがあるので、そちらで確認してください。

```php
<?php

namespace Drupal\some_module\Plugin\DsField;

use Drupal\Core\Form\FormStateInterface;
use Drupal\ds\Plugin\DsField\DsFieldBase;

/**
 * Generated field.
 *
 * @DsField(
 *   id = "some_module_DSFieldTest",
 *   title = @Translation("DSFieldTest"),
 *   entity_type = "user"
 * )
 */
class DSFieldTest extends DsFieldBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm($form, FormStateInterface $form_state) {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function formatters() {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function isAllowed() {
    return TRUE;
  }

}
```

## フィールドを呼び出す Entity の制約をかける　

アノテーションに `ui_limit` を追加し、`{}`内に`"エンティティID|ディスプレイモード"` という値を設定することで、特定のエンティティや特定のディスプレイモードにのみフィールドを追加するように制約をかけることができます。例えば、下記のように `"article|*"` という値を設定すれば、`article` コンテンツタイプのみにフィールドを作成することができ、その他のコンテンツタイプでは出てこなくなります。逆に特定のディスプレイモードにのみ表示するように制約をかけるには、`"*|some_display_mode"` という値を設定します。

```php
<?php

namespace Drupal\some_module\Plugin\DsField;

use Drupal\ds\Plugin\DsField\DsFieldBase;

/**
 * Generated limited field.
 *
 * @DsField(
 *   id = "some_module_DSFieldLimitTest",
 *   title = @Translation("DSFieldLimitTest"),
 *   entity_type = "node",
 *   ui_limit = {"article|*"}
 * )
 */
class DSFieldLimitTest extends DsFieldBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    return [];
  }

}
```

複数の制約条件を書く際は、下記のようにカンマ区切りで値を設定していきます。

```php
/**
 * @DsField(
 *   id = "some_module_DSFieldMultiLimitTest",
 *   title = @Translation("DSFieldMultiLimitTest"),
 *   entity_type = "node",
 *   ui_limit = {
 *     "page|hogehoge",
 *     "article|foobar"
 *   }
 * )
 */
```

## フィールドを使用している Entity を呼び出す

Display Suite で作成したフィールドは、呼び出し元の Entity を取得可能です。
Display Suite にある [`DsFieldBase` 抽象クラスのソースコード](http://cgit.drupalcode.org/ds/tree/src/Plugin/DsField/DsFieldBase.php?h=8.x-3.x#n111)を見ると、下記のように `$this->counfiguration['entity']` に定義されていることが分かります。

```php:/ds/src/Plugin/DsField/DsFieldBase.php
  public function entity() {
    return $this->configuration['entity'];
  }
```

作成したフィールドでは、`$this->entity()` を呼び出せるのでそれを使って呼び出し元の Entity の他のフィールドの値を使って処理を書くことができます。

## DI の設定

Display Suite では、ブロックなどとは違い `DsFieldBase` に DI するためのサービスコンテナを呼び出す処理が書かれているため、`DsFieldBase` を呼び出すだけで DI を設定することができます。例えば、ログイン中のユーザを取得して何かしらの処理をする場合は下記のようになります。

```php
<?php

namespace Drupal\some_module\Plugin\DsField;

use Drupal\ds\Plugin\DsField\DsFieldBase;

/**
 * Generated field.
 *
 * @DsField(
 *   id = "some_module_DSFieldTest",
 *   title = @Translation("DSFieldTest"),
 *   entity_type = "user"
 * )
 */
class DSFieldTest extends DsFieldBase {

  /**
   * Current User AccountProxy definition.
   *
   * @var \Drupal\Core\Session\AccountProxy
   */
  protected $currentUser;


  /**
   * DSFieldTest constructor.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Session\AccountProxy $current_user
   *   The current user account proxy.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, AccountProxy $current_user) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->currentUser = $current_user;
  }


  /**
   * {@inheritdoc}
   */
  public function create() {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('current_user')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function build() {
    $uid = $this->currentUser->id();
    return [
      '#markup' => $uid . ':',
    ];
  }

}
```


## まとめ

この記事では、Display Suite で追加可能になるフィールドを作成する方法を記載しました。
Display Suite のフィールドの設定フォームやフォーマッタの作成などといった、さらに詳しいことは、[Display Suite の作者のスライド](http://bramg.github.io/reveal.js/#/)に書いてあるのでそちらを参照してください。