
この記事は 「Drupal Advent Calendar 2017」の12月15日分の記事です。
ANNAIの青山です。
Drupalは非常に柔軟で拡張しやすいデータ構造を持っている事で知られています。例えば、コンテンツタイプというデータ型を管理UIから定義し、その中にテキストや画像、URLなどの任意のフィールドを自由に追加できます。
また、Viewsを使ってそれらをコレクション(一覧、グリッドなど)として抽出したり、REST APIとして公開することさえ、コードの開発を一切せずに実現できます。
これらの機能がフレームワークとして提供されることにより、開発者やサイト制作者は毎回繰り返し行われるような定型のタスクから解放され、ビジネスロジックやUXなど、本質的な価値を作り出すことに集中できるようになります。
ですが、「実際にデータベースの中がどのような構造になっているか」という解説は(特にDrupal 8では) あまりされていないようです。
そこで、Drupalの基本のコンテンツである「ノード」がどのようなデータ構造になっているか解説していこうと思います。
(この記事を作成時点の最新バージョンである 8.4.2 を元に調査しています)
インストール直後のテーブル一覧を見てみる
まず、Drupalのインストール直後にデータベースにどのようなテーブルがあるか見てみましょう。
MariaDB [drupal]> show tables; +------------------------------+ | Tables_in_drupal | +------------------------------+ | block_content | | block_content__body | | block_content_field_data | | block_content_field_revision | | block_content_revision | | block_content_revision__body | | cache_bootstrap | | cache_config | | cache_container | | cache_data | | cache_default | | cache_discovery | | cache_dynamic_page_cache | | cache_entity | | cache_menu | | cache_page | | cache_render | | cachetags | | comment | | comment__comment_body | | comment_entity_statistics | | comment_field_data | | config | | file_managed | | file_usage | | history | | key_value | | key_value_expire | | locale_file | | locales_location | | locales_source | | locales_target | | menu_link_content | | menu_link_content_data | | menu_tree | | node | | node__body | | node__comment | | node__field_image | | node__field_tags | | node_access | | node_field_data | | node_field_revision | | node_revision | | node_revision__body | | node_revision__comment | | node_revision__field_image | | node_revision__field_tags | | queue | | router | | search_dataset | | search_index | | search_total | | semaphore | | sequences | | sessions | | shortcut | | shortcut_field_data | | shortcut_set_users | | taxonomy_index | | taxonomy_term_data | | taxonomy_term_field_data | | taxonomy_term_hierarchy | | url_alias | | user__roles | | user__user_picture | | users | | users_data | | users_field_data | | watchdog | +------------------------------+ 70 rows in set (0.01 sec)
たくさんありますね。。 今回のテーマはノードのデータ構造ということで、ノードの周辺だけ見ていきます。
ノード全体を管理する "node" テーブル
それでは、まずは "node" テーブルの定義を見てみましょう。
MariaDB [drupal]> describe node; +----------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+------------------+------+-----+---------+----------------+ | nid | int(10) unsigned | NO | PRI | NULL | auto_increment | | vid | int(10) unsigned | YES | UNI | NULL | | | type | varchar(32) | NO | MUL | NULL | | | uuid | varchar(128) | NO | UNI | NULL | | | langcode | varchar(12) | NO | | NULL | | +----------+------------------+------+-----+---------+----------------+ 5 rows in set (0.00 sec)
このテーブルのカラムにはそれぞれ以下のデータが格納されます。
カラム | 格納されるデータ |
---|---|
nid |
ノードのID |
vid | ノードのリビジョンID (後述) |
type |
コンテンツタイプ。「page」や「article」など。 |
uuid | UUID。Drupal 7ではuuidモジュールを入れる必要がありましたが、Drupal 8ではコアでUUIDを生成してくれます(祝)。 |
langcode |
フィールドの言語コード。多言語サイトでは言語毎にデータが管理されるため、en, jaなどの言語コードが入る。 |
Drupalのノードには、ノードを一意に特定するための "ノードID" の他に、保存する度に "リビジョン" と呼ばれるバージョンが付けられます。このようなデータ構造にすることで、過去の任意の時点のデータを表示したり、復元することが可能になります。
では、"/node/add" から最初から定義されている「基本ページ」のコンテンツを1件登録してみましょう。「基本ページ」のフィールドは「タイトル」と「本文」のみのシンプルな構成になっています。以下のように入力してみましょう。

無事に登録できるとこのような画面になります。

それでは、 "node" テーブルのデータを見てみましょう。
MariaDB [drupal]> select * from node; +-----+------+------+--------------------------------------+----------+ | nid | vid | type | uuid | langcode | +-----+------+------+--------------------------------------+----------+ | 1 | 1 | page | e56aa693-67ba-4b09-bcdc-00eeb9a1f581 | ja | +-----+------+------+--------------------------------------+----------+ 1 row in set (0.00 sec)
先ほど作成した「基本ページ」のものらしきデータが入っています。 しかし、編集フォームでは "タイトル" や "本文" がありましたが、このテーブルには含まれていないことがわかります。
ノードのメタデータを管理する "node_field_data" テーブル
では、次に "node_field_data" テーブルの定義を見てみましょう。
MariaDB [drupal]> describe node_field_data; +-------------------------------+------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------------------------+------------------+------+-----+---------+-------+ | nid | int(10) unsigned | NO | PRI | NULL | | | vid | int(10) unsigned | NO | MUL | NULL | | | type | varchar(32) | NO | MUL | NULL | | | langcode | varchar(12) | NO | PRI | NULL | | | status | tinyint(4) | NO | MUL | NULL | | | title | varchar(255) | NO | MUL | NULL | | | uid | int(10) unsigned | NO | MUL | NULL | | | created | int(11) | NO | MUL | NULL | | | changed | int(11) | NO | MUL | NULL | | | promote | tinyint(4) | NO | MUL | NULL | | | sticky | tinyint(4) | NO | | NULL | | | default_langcode | tinyint(4) | NO | | NULL | | | revision_translation_affected | tinyint(4) | YES | | NULL | | +-------------------------------+------------------+------+-----+---------+-------+ 13 rows in set (0.01 sec)
タイトル (title) がありましたね。こちらのテーブルにもnidとvidがあり、先程のnodeテーブルと結合できそうな感じがします。このテーブルのカラムにはそれぞれ以下のデータが格納されます。
カラム | 格納されるデータ |
---|---|
nid |
ノードのID |
vid | ノードのリビジョンID (後述) |
type |
コンテンツタイプ。「page」や「article」など。 |
langcode | フィールドの言語コード。多言語サイトでは言語毎にデータが管理されるため、en, jaなどの言語コードが入る。 |
stats |
ノードの掲載状態 (0が非掲載、1が掲載) |
title | ノードのタイトル |
uid |
ノードの作成者のユーザーID |
created | ノードの作成時刻 |
changed | ノードの更新時刻 |
promote | サイトのフロントページ(デフォルトのトップページ)に表示するかどうか |
sticky | 一覧表示した時にトップに表示するかどうか |
default_language | ノードのデフォルト言語 |
Drupal 7のデータ構造に詳しい方は、この時点でだいぶ構造が変わっていることに気がついたかもしれません。 Drupal 7ではstatusやtitleなどはnodeテーブル自体で管理されていました。 つまり、これらのデータはリビジョンを持っていませんでした。
Drupal 8では、これらの値もリビジョン毎に保存されるため、データのトレーサビリティがより向上しています。 では、こちらのデータも見ていきましょう。カラムが多いので必要なところだけに絞り込みます。
MariaDB [drupal]> select nid, vid, status, title from node_field_data; +-----+-----+--------+------------------------------+ | nid | vid | status | title | +-----+-----+--------+------------------------------+ | 1 | 1 | 1 | Hello Drupal Data Structure! | +-----+-----+--------+------------------------------+ 1 row in set (0.00 sec)
基本ページに入力したタイトルが入っていることが確認できました。
ノードのメタデータの履歴を管理する"node_field_revision" テーブル
さて、テーブル一覧に "node_field_revision" という似たような名前のテーブルがあります。 こちらのテーブルの定義も見てみましょう。
MariaDB [drupal]> describe node_field_revision; +-------------------------------+------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------------------------+------------------+------+-----+---------+-------+ | nid | int(10) unsigned | NO | MUL | NULL | | | vid | int(10) unsigned | NO | PRI | NULL | | | langcode | varchar(12) | NO | PRI | NULL | | | status | tinyint(4) | NO | | NULL | | | title | varchar(255) | YES | | NULL | | | uid | int(10) unsigned | NO | MUL | NULL | | | created | int(11) | YES | | NULL | | | changed | int(11) | YES | | NULL | | | promote | tinyint(4) | YES | | NULL | | | sticky | tinyint(4) | YES | | NULL | | | default_langcode | tinyint(4) | NO | | NULL | | | revision_translation_affected | tinyint(4) | YES | | NULL | | +-------------------------------+------------------+------+-----+---------+-------+ 12 rows in set (0.00 sec)
typeカラムがないだけで、先ほどの "node_field_data" とほぼ同じ構造ですね。typeカラムがない理由は明確で、リビジョン毎に変わることがあり得ないからです。データも見てみましょう。
MariaDB [drupal]> select nid, vid, status, title from node_field_revision; +-----+-----+--------+------------------------------+ | nid | vid | status | title | +-----+-----+--------+------------------------------+ | 1 | 1 | 1 | Hello Drupal Data Structure! | +-----+-----+--------+------------------------------+ 1 row in set (0.01 sec)
先ほどと全く同じですね。無駄に思えますが、これにはどういう意味があるのでしょうか?試しに、作成したページのタイトルを「Hello Drupal Data Structure!」から「こんにちは、Drupalのデータ構造!」に書き換えてみましょう。

ここでもう一度、node, node_field_data, node_field_revision をそれぞれ見てみます。
MariaDB [drupal]> select * from node; +-----+------+------+--------------------------------------+----------+ | nid | vid | type | uuid | langcode | +-----+------+------+--------------------------------------+----------+ | 1 | 2 | page | e56aa693-67ba-4b09-bcdc-00eeb9a1f581 | ja | +-----+------+------+--------------------------------------+----------+ 1 row in set (0.00 sec) MariaDB [drupal]> select nid, vid, status, title from node_field_data; +-----+-----+--------+---------------------------------------------+ | nid | vid | status | title | +-----+-----+--------+---------------------------------------------+ | 1 | 2 | 1 | こんにちは、Drupalのデータ構造! | +-----+-----+--------+---------------------------------------------+ 1 row in set (0.00 sec) MariaDB [drupal]> select nid, vid, status, title from node_field_revision; +-----+-----+--------+---------------------------------------------+ | nid | vid | status | title | +-----+-----+--------+---------------------------------------------+ | 1 | 1 | 1 | Hello Drupal Data Structure! | | 1 | 2 | 1 | こんにちは、Drupalのデータ構造! | +-----+-----+--------+---------------------------------------------+ 2 rows in set (0.00 sec)
はい、だいぶ雰囲気が掴めてきましたね。
まず、 nodeテーブルのvidが1から2に変わっていることが分かります。node_field_data テーブルも同様にvidが1から2に変わっていて、タイトルも後で更新したときのデータになっていることが分かります。それに対して、node_field_revision テーブルには vidが1と2のデータが両方存在します。
つまり、
- node_field_revision テーブルには過去の全てのリビジョンのデータが格納される
- node_field_data テーブルには最新のリビジョンのデータのみが格納される
- node テーブルのvidで最新のリビジョンを判断する
のようにデータが管理されていることになります。
「本文」フィールドのデータを管理する "node__body" テーブル
さて、少し分かってきたところで、次は「本文」のデータ構造も見ていきましょう。
MariaDB [drupal]> describe node__body; +--------------+------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------+------------------+------+-----+---------+-------+ | bundle | varchar(128) | NO | MUL | | | | deleted | tinyint(4) | NO | PRI | 0 | | | entity_id | int(10) unsigned | NO | PRI | NULL | | | revision_id | int(10) unsigned | NO | MUL | NULL | | | langcode | varchar(32) | NO | PRI | | | | delta | int(10) unsigned | NO | PRI | NULL | | | body_value | longtext | NO | | NULL | | | body_summary | longtext | YES | | NULL | | | body_format | varchar(255) | YES | MUL | NULL | | +--------------+------------------+------+-----+---------+-------+ 9 rows in set (0.00 sec)
body_value などはなんとなく意味がわかりますが、他はちょっとイメージがつかないですね。 データの方も見てみましょう。
MariaDB [drupal]> select bundle, entity_id, revision_id, body_value, body_format from node__body; +--------+-----------+-------------+--------------------------------------------------------+-------------+ | bundle | entity_id | revision_id | body_value | body_format | +--------+-----------+-------------+--------------------------------------------------------+-------------+ | page | 1 | 2 | <p>Drupalのデータ構造を見てみるよ!</p> | basic_html | +--------+-----------+-------------+--------------------------------------------------------+-------------+ 1 row in set (0.00 sec)
コンテンツに入力した本文が入っているのが確認できました。 このテーブルのカラムにはそれぞれ以下のデータが格納されます。
カラム | 格納されるデータ |
---|---|
bundle |
エンティティの種別。ノードの場合は"page", "artcile" などのコンテンツタイプが格納される。 |
deleted | コンテンツタイプ(正確にはbundle)からフィールドが削除された場合、1が格納される (通常は0)。 |
entity_id |
エンティティのID。ノードの場合はノードIDが格納される。 |
revision_id | エンティティのリビジョンID |
langcode |
フィールドの言語コード。多言語サイトでは言語毎にデータが管理されるため、en, jaなどの言語コードが入る。 |
delta |
フィールドに値が複数入力可能な場合のインデックス番号。 「本文」フィールドのように1つしか入力できない場合は0が格納される。 |
body_value |
「本文フィールド」の入力値 |
body_summary | 「本文フィールド」(概要)の入力値 |
body_format |
「本文フィールド」のテキストフォーマット。「basic_html」、「plaintext」、「markdown」など。 |
脱線: bundleとは
「bundle」という見慣れないキーワードが出てきました。ノードに限定するとそれほど重要な要素ではないのですが、Drupal全体のデータ構造を理解する上では非常に重要なので、少し脱線して説明します。
「bundle」を簡単に表現すると、「ある共通の定義や型を持ったデータの入れ物」です。Drupalのドキュメントでは、「In Drupal 8, bundles are a type of container for information that holds the field or setting definitions.」と表現されています。
ちなみに、これはDrupalの独自の用語ではありません。開発者であれば、フレームワークのソースコードやドキュメントなどで目にすることは多いでしょう。「bundle」が具体的に何を意味しているかはコンテキストによって若干変わりますが、Drupalでは、
- ノードの場合: 「基本ページ」や「記事」など個々のコンテンツタイプがbundle
- タクソノミーの場合: 個々の「ボキャブラリー」がbundle
- ブロックの場合: 個々の「カスタムブロックタイプ」がbundle
のように使われます。
なぜ「bundle」という概念がデータ構造に取り入れられているかは、次回にまた解説します。
「本文」フィールドの履歴を管理する "node_revision__body" テーブル
さて、本文も例によってリビジョン毎にデータが管理されます。 "node_revision__body" のデータ構造を見てみましょう。
MariaDB [drupal]> describe node_revision__body; +--------------+------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------+------------------+------+-----+---------+-------+ | bundle | varchar(128) | NO | MUL | | | | deleted | tinyint(4) | NO | PRI | 0 | | | entity_id | int(10) unsigned | NO | PRI | NULL | | | revision_id | int(10) unsigned | NO | PRI | NULL | | | langcode | varchar(32) | NO | PRI | | | | delta | int(10) unsigned | NO | PRI | NULL | | | body_value | longtext | NO | | NULL | | | body_summary | longtext | YES | | NULL | | | body_format | varchar(255) | YES | MUL | NULL | | +--------------+------------------+------+-----+---------+-------+
node__body と全く同じですね。 データも見てみましょう。
MariaDB [drupal]> select bundle, entity_id, revision_id, body_value, body_format from node_revision__body; +--------+-----------+-------------+--------------------------------------------------------+-------------+ | bundle | entity_id | revision_id | body_value | body_format | +--------+-----------+-------------+--------------------------------------------------------+-------------+ | page | 1 | 1 | <p>Drupalのデータ構造を見てみるよ!</p> | basic_html | | page | 1 | 2 | <p>Drupalのデータ構造を見てみるよ!</p> | basic_html | +--------+-----------+-------------+--------------------------------------------------------+-------------+
タイトルと同様に、こちらもリビジョン毎にデータが独立して保存されているのが分かります。
まとめ
今回は、デフォルトで定義されている「基本ページ」のコンテンツを登録し、ノードと本文フィールドがどのようにデータとして管理されているかを見てみました。単にコンテンツをHTMLとして保存しているのではなく、バンドルやリビジョン、テキストフォーマットなど、データを抽象化し、構造化して管理するのがDrupalの大きな特徴です。
ちなみに、細かな違いはありますが、Drupal 7でも大枠のデータ構造はほぼ同様です。
次回はカスタムフィールドを追加し、そのデータ構造がどのようになっているかを解説する予定です。
(Photo by Thomas Kvistholt on Unsplash)
関連コンテンツ
- Drupal8スタートブックの読者様向けに修正箇所をまとめました
- 既存のDrupalフィールドを再利用すべきか?
- サーバーサイドエンジニアが初めてDrupalを触ってみた
- Drupal 8のデモ用インストールプロファイルUmami を日本語化してみた
- Drupal のレイアウトビルダが持つ強力な機能と独自の特徴
- Developers Festa Sapporo 2018で Webシステム開発基盤としてのDrupal を紹介してきました
- Drupal 8インストール方法:XREA編
- Drupal 8インストール方法:ロリポップ編
- あなたのサイトをDrupalに移行すべき理由
- 第16回 Drupalをもっと知りたい方に向けた各種情報
Drupal 8初心者講座バックナンバー
Drupal初心者講座について
第1回 歴史に見るDrupal のDNA
第2回 Drupalはフレームワークか?CMSか?
第3回 Drupalの特徴
第4回 Drupal 8のインストール(1)
第5回 Drupal 8のインストール(2)
第6回 コンテンツを投稿してみる
第7回 ボキャブラリとタクソノミーを使う
第8回 コンテンツ管理におけるDrupalと他のCMSとの比較
第9回 Drupal 8のブロックシステム
第10回 Drupalの標準クエリービルダ Views
第11回 Drupalと他のCMSのクエリビルダー機能を比較
第12回 Drupal 8の多言語機能と他のCMSやサービスとの比較
第13回 Drupalの権限設定とWordPressやMovable Typeとの比較
第14回 Drupalのテーマシステムについて
第15回 Drupalの拡張モジュールの選定と利用方法
第16回 Drupalをもっと知りたい方に向けた各種情報