テーマにカスタム設定を定義する

メンテナンスしやすいテーマ開発、デザインコンポーネント設計など、悩んでませんか？
モジュール上で実装した機能とデザイン領域の連携をどうすればいいのか、どのレイヤーで実装すると見通しが良くなるのか、考えることが度々あります。

![drupal8_themes.png](https://qiita-image-store.s3.amazonaws.com/0/39468/0b04cb57-7d3c-526b-cd30-eb78cf741eb0.png)



今回は、テーマの中に書くべき実装・設定に関して書いていこうと思います。



##モジュールとテーマの領域

CMSでサイト制作する場合に、テーマを切り替えた時にサイト設定や機能がなくなったり、動作しなくなることがないようにモジュールはテーマに依存しないように実装するのが一般的で、Drupalも例外ではありません。
その後の運用コストを下げるためにも依存しないようにしておくことが大事です。

MVCモデルの観点で見ると、

* モデル（M）： [Entity API](https://www.drupal.org/docs/8/api/entity-api/introduction-to-entity-api-in-drupal-8)
* ビュー（V）： [Theme + Twig](https://www.drupal.org/docs/8/theming)
* コントローラ（C）： [ルーティングとコントローラ](https://www.drupal.org/docs/8/api/routing-system/introductory-drupal-8-routes-and-controllers-example)

のように分離されており、表示部分はテーマ上に実装すれば良いことが分かります。

バックエンド開発者とフロントエンド開発者がいる状況だと、モジュールはMC、テーマはVとしっかり線引きして作業分担したいところですが、モジュール上にテーマレイヤーの処理を書いたり、その逆もできてしまうので作業領域の境界が分からなくなってきます。



##テーマ側に持たせるべき設定・実装

テーマは、基本的にサイト全体のCSS／JS／Twigテンプレートなど見た目の調整をする領域であり、ブログシステムのようにカジュアルにテーマを切り替えることは無いと思いますが、なるべくモジュール機能に依存しない実装をします。

* 標準的なデザインコンポーネントの表示出し分け<br>
（ロゴ、グローバルヘッダの検索フォーム、SNSアイコン、カルーセルスライダーなど）
* モジュール上で定義されているデザインコンポーネントを`template_preprocess()`フックで前処理する （レンダリング配列の操作）
* `theme_suggestions_HOOK_alter()`によるテンプレートサジェスト登録（HTML要素名やHTML属性に依存するもの）
* サイト共通のCSS／JSの定義
* モジュールが提供するTwigテンプレートをオーバーライドする

とは言いつつも、Twigテンプレート上でカスタムモジュールが提供するフィルターや関数を使ったりなど、完全に依存性をクリアにすることは難しいですが、意識するようにしておきましょう。


####逆にモジュール側に持たせたいもの

* 特定のモジュールに依存する処理（現状、テーマがモジュールへの依存関係を宣言する仕組みがないため）
* パラグラフモジュールを使ってイチからデザインコンポーネント実装を行う場合は、専用のカスタムモジュールを用意し、それに関するTwigテンプレート設置や`template_preprocess()`フックによる前処理はそのカスタムモジュール内で完結させる。
* `theme_suggestions_HOOK_alter()`によるテンプレートサジェスト登録（ページの状態やコンテンツタイプに依存するもの）
* Twigは基本的にビューの役割のみを持たせ、フィルターや関数によるDBからのデータ読み込み、ロジックの介入はなるべく避ける。（計算や加工などのデータ処理はコントローラやモデル側で行う）


その他、モジュールとテーマ間でサイトの状態やデータ共有をしたい場合は、[再利用可能なサービス](https://www.drupal.org/docs/8/api/services-and-dependency-injection/services-and-dependency-injection-in-drupal-8)を用意するのが良いと思います。




##テーマ設定画面に項目を追加する

テーマ側に持たせるべき設定・実装をいくつか挙げましたが、その中の一つ、デザインコンポーネントの表示出し分けをする場合の実装方法を説明していきます。

Drupal管理画面には、テーマごとに独自の設定画面 `/admin/appearance/settings/MYTHEME` が存在します。
この画面には、デフォルトで「ロゴ画像設定」や「ショートカットアイコン設定」などの標準設定項目が用意されています。

![Screen Shot 2018-12-24 at 14.38.36.png](https://qiita-image-store.s3.amazonaws.com/0/39468/8d00e6b6-8d5f-dec1-b410-0b56c18964f2.png)



Drupal 8では、テーマディレクトリ直下に `MYTHEME.theme` または `theme-settings.php` のいづれかのファイルを作成して、以下のフックを追加すればカスタム項目を追加可能です。

```php
/**
 * Implements hook_form_system_theme_settings_alter() for settings form.
 */
function MYTHEME_form_system_theme_settings_alter(&$form, FormStateInterface $form_state) {
  $form['custom_theme_settings'] = [
    '#type' => 'fieldset',
    '#title' => t('Custom theme settings'),
    '#open' => TRUE,
    '#tree' => TRUE,
  ];
  $form['custom_theme_settings']['use_logo'] = [
    '#type' => 'checkbox',
    '#title' => t('Use the header logo.'),
    '#wrapped_label' => TRUE,
    '#options' => [
      0 => t('No'),
      1 => t('Yes'),
    ],
    '#required' => FALSE,
    '#disabled' => FALSE,
    '#default_value' => theme_get_setting('custom_theme_settings.use_logo'),
    '#return_value' => 1,
  ];
}
```

テーマ設定画面に項目を追加することのメリットとしてよく挙げられるのは、サイトビルダーに対してテーマの外観設定を提供することができるというものがあります。もちろん素晴らしいことですが利点はそれだけではありません。

例えば、デザイン要素の表示/非表示をする場合は、まずカスタムブロック化してからブロックレイアウト画面上で設定することはよくある手段ですが、単純にデザイン要素の表示/非表示を行いたいだけであればテーマ設定にチェックボックス項目を持たせた方が解りやすい場合があります。



###テーマにデフォルト設定値を含める

テーマ設定画面に追加した項目のデフォルト値を含めておくことができます。

Drupal 8は、YAMLファイルによるデフォルト設定を提供するための仕組みを持っています。
テーマディレクトリ直下に `config` という名前でディレクトリを作成し、その中に新規ディレクトリ `install` という名前を付けます。

そして、`config/install` 直下に `MYTHEME.settings.yml` という名前を付けてYAMLファイルを設置します。

**例）MYTHEME.settings.yml**

```yaml
features:
  comment_user_picture: true
  comment_user_verification: true
  favicon: 1
  node_user_picture: true
logo:
  path: ''
  url: ''
  use_default: 1
table: ''
custom_theme_settings:
  use_logo: 1
```

config/installに含めたYAMLファイルは、テーマのインストール時にインポートされます。

```bash
$ drush en mytheme -y
$ drush config-set system.theme default mytheme
```

以上により、デフォルト設定が読み込まれます。

もし、テーマがインストール済みの場合にもう一度YAMLファイルをインポートしたければ config-importコマンドを実行しましょう。

```bash
$ cd /path-to-docroot
$ drush config-import --partial --source=themes/custom/mytheme/config/install
Collection  Config                     Operation                
             mytheme.settings  update
Import the listed configuration changes? (y/n): y
Synchronized configuration: update mytheme.settings.             [ok]
Finalizing configuration synchronization.                        [ok]
The configuration was imported successfully.                     [success]
```

※ config-importコマンド実行時、`--partial`オプションを付けておくと新しい差分はインポートされますが削除された差分は反映されません。
意図せずアクティブな設定が消えないようにするため、`--partial`オプションは常に付けるようにしておくと安心です。


その後、設定内容を確認する場合は、キャッシュクリアしましょう。

```bash
$ drush cr
```


##まとめ

Drupal 8にはテーマレイヤーを操作するための多くのAPIがあり、レンダリング配列の前処理をしたり変数を書き換えることはいつでもできます。
ただし、テーマの役割を決めておかないとシステム規模が大きくなるにつれて依存関係が複雑になり、一つの変更のために広範囲の修正が必要になることがあります。

HTML要素の書き換え処理などの実装がモジュールの奥深くに入り込まないようにするためにも、プロジェクトを開始する前に実装方針を明確化しておくことが大事ですね。