先日とあるプロジェクトで、「あるカスタムモジュールの処理が最後に実行された時間を記録する機能を実装してほしい」という要望がありました。
とりあえずやってみますとは言ってみたものの、どうやって保存するのが最適なのか、少し悩みました。直近の実行時間だけわかればいいので(つまりデータの数は常に1個なので)、わざわざDBにテーブルを作る必要はありません。かといってファイルに書き出すと、処理が行われるたびにgitに差分として出てしまいます。
いろいろ調べた結果、「モジュールの最終実行時間」はDrupalの「状態(State)」という種類のデータにあたるということがわかりました。この場合、State APIを使えば1〜2行のコードでDBのあらかじめ用意されたテーブルにデータを保存したり、取り出したりできます。今回はそれを利用して、簡単に最終実行時間を記録する機能を実装することができました。めでたしめでたし。

このように、Drupalではウェブ開発をする際に特によく必要になるデータの保存形式を以下の4種類に分け、それぞれにAPIなどを用意して効率的にデータのやりとりを行えるようにしています。

  • コンテンツ(Content)
  • 構成(Configuration)
  • セッション(Session)
  • 状態(State)

これらを使い分けることができるようになると、カスタムモジュール開発が楽になります。今回はそれぞれの用途や使い方について解説していきたいと思います。

コンテンツ(Content)

概要

コンテンツ」は見て字のごとく、サイトの「内容」そのものを保存するための形式です。ノードのようにサイトで展示することを目的として保存されるものだけではなく、ユーザーのようにサイト管理上必要な内部情報として保存されるものがあります。一般的には「編集者」のような権限の少ない役割でも編集できるものが多いです。
一度型(コンテンツタイプなど)を決めたらどんどん数を増やすことができ、人為的に削除しない限りデータベースから消えることはありません。データ(フィールド値)一つに対してテーブルのセル一つがあてがわれるので、Database APIなどで簡単に取り出せるのも特徴です。
Drupal 8では「エンティティ」の形式でコンテンツを保存することが推奨されています。エンティティの場合、個々のデータはフィールドに保存され、DB上では[エンティティタイプ名]__field_[フィールド名]というテーブルで確認することができます。

  • ノード
  • タクソノミー
  • 画像

使い方

エンティティの場合はEntity APIを使ってデータの取得や保存を行います。以下はそのサンプルコードです。

  • 既存のタイプのエンティティのフィールド値を取得する
// ノードIDからノードをロードする.
$node = Node::load($id);
// ノードのタイトルを取得する.
$node->get('title')->value;
  • 既存タイプのエンティティのフィールド値を上書き保存する
// ノードのタイトルを上書きする.
$node->set('title', 'new_title')->save();

Working with the Entity API Drupal 8 Entity API cheat sheet

新しいエンティティタイプを作成する方法はこちらのドキュメントが参考になります。 Creating a custom content entity

構成(Configuration)

概要

構成」はコンテンツと同じく比較的永続的で、なおかつコンテンツタイプなどサイトを作成する上で必要な設定を保存するために使われます。
YAML形式で書かれ、DBのconfigテーブルに保存されます。「構成のエクスポート」(drush cex)でファイルに書き出すことができるため、複数の環境で共有することができます。

  • サイト名やサイトのメールアドレス
  • コンテンツタイプの名前やフィールドの種類

使い方

  • 既存の構成を取得する
// mymodule.settingsという名前の構成からmessageという項目の値を取得する
$config = \Drupal::config('mymodule.settings');
$config->get('message');
  • 既存の構成を上書きする
// messageを上書きする.
$config = \Drupal::service('config.factory')->getEditable('mymodule.settings');
$config->set('message', 'New Message')->save();

Simple Configuration API

新しい構成を定義する方法はこちらのドキュメントが参考になります。 Defining and using your own configuration in Drupal 8

セッション(Session)

概要

セッション」は個々のセッションと紐づく情報を保存するための保存形式です。「テンポラリーストレージ(Temporary storage)」、「テンプストア(Tempstore)」とも呼ばれます。代表的なものがセッションIDです。
セッションには「プライベート(private)」タイプと「共有(shared)」タイプがあります。どちらも個々のユーザーに紐づく情報ですが、「プライベート」には当該ユーザーしかアクセスしてはいけない情報を格納するのに対し、「共有」には他のユーザーにもアクセスできる情報を格納します。
「他のユーザーにもアクセスできるセッション情報」というとピンと来ない方も多いのではないかと思いますが、例えばユーザーAがとあるビューを編集している時にユーザーBが同じ編集画面にアクセスすると、「このビューはユーザーAによって編集中です」というメッセージが表示されます。このようにサイト運営上共有する必要があり、他人に見られても害にならない情報は共有セッション情報に振り分けることができます。
プライベートのセッション情報はセッションが切れると削除されるため、比較的寿命の短い保存形式だと言えます。

  • プライベート
    • ログインしているユーザーのセッションID
    • 多段階フォームでユーザーが入力した値
  • 共有
    • ビューの未保存の編集内容

使い方

  • 既存のセッション情報を取得する
// mymoduleという名前のプライベートセッション情報からmyvariableという変数の値を取得する.
$tempstore = \Drupal::service('tempstore.private')->get('mymodule');
$some_data = $tempstore->get('myvariable');
  • セッション情報を記録する
// myvariableを上書きする.
$tempstore = \Drupal::service('tempstore.private')->get('mymodule');
$tempstore->set('myvariable', $some_data);

Storing Session Data with Drupal 8 Drupal 8: Tempstore

状態(State)

概要

状態」とは、セッションとは関係なく一つの環境で一時的に保持しておきたい情報を保存するための形式です。一般的には「cronの最終実行時間」のように機械的にセットされる値に使われます。
保存された値はDBのkey_valueテーブルに保存されますが、構成のようにファイルに書き出すことはできません。つまりDBをインポートしない限り他の環境に移行することはできないので注意しましょう。

  • cronの最終実行時間
  • アップデートの最終チェック時間

使い方

  • 既存の状態を取得する.
// cronの最終実行時間を取得する.
\Drupal::state()->get('system.corn_last');
  • 状態を記録する.
// カスタムモジュールの処理実行時間を記録する.
$request_time = \Drupal::time()->getCurrentTime();
\Drupal::state()->set('mymodule.last_execute', $request_time);

State API overview

どの形式で保存する?

ここまで説明したように、それぞれの保存形式でできることやできないことがあるので、要件を吟味して、時と場合に応じて保存形式を選ぶことが重要です。「どの形式で保存したらいいのかな?」と悩んだら、以下のフローチャートでざっくり確認してましょう。(あくまで「ざっくり」なので、もちろん例外もあります。)

Overview of Configuration (vs. other types of information) Configuration or state?

おわりに

というわけで今回はコンテンツ、構成、セッション、状態の違いを紹介しました。実はsettings.phpの情報やキャッシュも入れてデータの形式は6種類とする考え方もあるみたいなんですが、とりあえずモジュール開発時に迷うのはこの4種だと思ったので紹介してみました。以前の私と同じような問題にぶち当たってる方々の参考になれば幸いです。