DrupalのViewsにUIから変更可能なカスタムのオプションを追加する方法

## やりたいこと
- DrupalのViewsにこんなオプションを追加したい
    - 単純な真偽値を保存できる
    - UIから変更可

## やり方
ディスプレイエクステンダーというプラグインを作成します。

まずは`my_module/src/Plugin/views/display_extender/MyFlagDisplayExtender.php`を作成します。必要なファイルはこれだけです。

ファイルの中身は以下のように編集していきます。

まず通常のプラグインと同様にアノテーションにメタ情報を書き込みます。クラスは`DisplayExtenderPluginBase`を継承します。

```php

<?php

namespace Drupal\my_module\Plugin\views\display_extender;

use Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Myフラグのオプションを追加する.
 *
 * @ingroup views_display_extender_plugins
 *
 * @ViewsDisplayExtender(
 *   id = "my_flag",
 *   title = @Translation("Myフラグ"),
 *   help = @Translation("Myフラグの設定を行います。"),
 * )
 */
class MyFlagDisplayExtender extends DisplayExtenderPluginBase {
```

この時点で`/admin/structure/views/settings/advanced`の画面下部「ディスプレイエクステンダー」の項目に自分のディスプレイエクステンダーが追加されているはずなので、チェックして保存します。

![スクリーンショット 2021-01-13 21.30.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/ab63a87b-581b-6538-6e83-4dddcff74e92.png)


オプションのデフォルト値を定義します。今回はオプション名は`value`とします。$optionsはこのプラグイン内だけの値なので汎用的な名前で大丈夫です。ちなみに、このオプションは強制的に全てのディスプレイに追加されるので、デフォルト値は全てのディスプレイに適用しても問題ない値にしましょう。

```php

  /**
   * {@inheritdoc}
   */
  public function defineOptions() {
    $options['value'] = ['default' => TRUE];
    return $options;
  }
```

ビューのUIにカスタムオプションの項目を追加します。

```php

 /**
   * {@inheritdoc}
   */
  public function optionsSummary(&$categories, &$options) {
    // 新しいカテゴリーを作る.
    $categories['my_category'] = [
      'title' => 'Myカテゴリー ',
      'column' => 'second',
    ];

    // オプションを追加する.
    $options['my_flag'] = [
      'category' => 'my_category',
      'title' => 'Myフラグ',
      'des' => 'Myフラグの設定を行います。',
      // Myフラグのオン・オフを設定画面へのリンクテキストに使用する.
      'value' => $this->options['value'] ? 'オン' : 'オフ',
    ];
  }

```
これによりビューの設定画面ではこのように表示されます。
![extender_summary.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/edbc35d6-7436-ce8d-c373-d4f3bca8f04e.png)


さきほど追加したオプションの設定フォームを定義します。ここは通常のForm APIの書き方と変わりません。`$form_state->get('section') === 'my_flag'`の部分を抜くと全部のオプションでこのフォームが表示されるので注意してください。ちなみにこの`my_flag`は`optionsSummary`で設定したオプションのキーと同一にする必要があります。（ややこしい）

```php

  /**
   * {@inheritdoc}
   */
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    if ($form_state->get('section') === 'my_flag') {
      $form['value'] = [
        '#type' => 'checkbox',
        '#title' => 'Myフラグ',
        '#default_value' => $this->options['value'],
        '#description' => 'Myフラグをオンにする場合はチェックを入れてください。',
      ];
    }
  }
```
これによりさきほどの設定画面の「オン」の文字クリックでこのようなフォームが開きます。
![スクリーンショット 2021-01-26 21.43.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/6a555cc3-a164-dd84-7653-8ea72a4b8179.png)


設定フォーム保存時の処理を書きます。

```php

  /**
   * {@inheritdoc}
   */
  public function submitOptionsForm(&$form, FormStateInterface $form_state) {
    if ($form_state->get('section') === 'my_flag') {
      $this->options['value'] = $form_state->getValue(['value']);
    }
  }

}

```

全部つなげるとこのようになります。

```php

<?php

namespace Drupal\my_module\Plugin\views\display_extender;

use Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Myフラグのオプションを追加する.
 *
 * @ingroup views_display_extender_plugins
 *
 * @ViewsDisplayExtender(
 *   id = "my_flag",
 *   title = @Translation("Myフラグ"),
 *   help = @Translation("Myフラグの設定を行います。"),
 * )
 */
class MyFlagDisplayExtender extends DisplayExtenderPluginBase {

  /**
   * {@inheritdoc}
   */
  public function defineOptions() {
    $options['value'] = ['default' => TRUE];
    return $options;
  }

  /**
   * {@inheritdoc}
   */
  public function optionsSummary(&$categories, &$options) {
    // 新しいカテゴリーを作る.
    $categories['my_category'] = [
      'title' => 'Myカテゴリー ',
      'column' => 'second',
    ];

    // オプションを追加する.
    $options['my_flag'] = [
      'category' => 'my_category',
      'title' => 'Myフラグ',
      'des' => 'Myフラグの設定を行います。',
      // Myフラグのオン・オフを設定画面へのリンクテキストに使用する.
      'value' => $this->options['value'] ? 'オン' : 'オフ',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    if ($form_state->get('section') === 'my_flag') {
      $form['value'] = [
        '#type' => 'checkbox',
        '#title' => 'Myフラグ',
        '#default_value' => $this->options['value'],
        '#description' => 'Myフラグをオンにする場合はチェックを入れてください。',
      ];
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitOptionsForm(&$form, FormStateInterface $form_state) {
    if ($form_state->get('section') === 'my_flag') {
      $this->options['value'] = $form_state->getValue(['value']);
    }
  }

}
```

## 使い方
オプションの値は`DisplayPluginBase`オブジェクトの`display`というパラメーターに格納されています。`hook_views_query_alter`で使うならこんな感じです。

```php
/**
 * Implements hook_views_query_alter().
 */
function my_module_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
  // ディスプレイを取得.
  $display = $view->getDisplay()->display;

  // Myフラグの値を取得.
  if (isset($display['display_options']['display_extenders']['my_flag'])) {
    $my_flag = $display['display_options']['display_extenders']['my_flag']['value'];
    // Do something.
  }
}
```
`$display['display_options']['display_extenders']['my_flag']`の`my_flag`はプラグインIDです。

Viewsのカスタマイズ方は本当にいろいろあるので、また何か面白いネタがあれば共有したいと思います。