アイキャッチ画像: デスクの上でパソコンを使用している

こんにちはスタジオ・ウミの大野です。今年は滋賀県に来て11年目になるのですが、引っ越してきて以来初めて交通機関が止まるほど雪が積もるところを見なかった年になりました。暖冬ですね。通勤が楽なのは確かなんですが、何だかちょっと寂しい気分です。

さて、今回はエンティティの検索に便利な EntityFieldQuery API について解説したいと思います。EntityFieldQuery API は簡単に言うと SQL クエリーを用いないでエンティティのプロパティやフィールドからエンティティを抽出する ことができる仕組みです。Entity Field Query は Drupal のデータベース API とよく似た書き方をするため、この記事ではデータベース API を使った経験があるかたが対象です。

以下の内容は Drupal.org に投稿されている How to use EntityFieldQuery を元に加筆修正・翻訳した記事です。正確な意味を知りたい方は原文をご覧ください。

EntityFieldQuery は、特定の条件に基づいてエンティティの検索を可能にするクラス です。エンティティのプロパティやフィールドの値、その他にエンティティのメタデータからエンティティを検索することができます。構文はとてもコンパクトで簡単に使用でき、そして素晴らしいことに、この機能は Drupal のコアに含まれているので追加のモジュールが必要ありません。

使用例

まず最初に EntityFieldQuery を使うと何が便利になるか見てみましょう。以下のクエリーは特定の教員でタグ付けされていて、年内に掲載された全ての写真付きの記事を抽出するものです。

$query = new EntityFieldQuery();

$query->entityCondition('entity_type', 'node')
  ->entityCondition('bundle', 'article')
  ->propertyCondition('status', NODE_PUBLISHED)
  ->fieldCondition('field_news_types', 'value', 'spotlight', '=')
  ->fieldCondition('field_photo', 'fid', 'NULL', '!=')
  ->fieldCondition('field_faculty_tag', 'tid', $value)
  ->fieldCondition('field_news_publishdate', 'value', $year . '%', 'like')
  ->fieldOrderBy('field_photo', 'fid', 'DESC')
  ->range(0, 10)
  ->addMetaData('account', user_load(1)); // Run the query as user 1.

$result = $query->execute();

if (isset($result['node'])) {
  $news_items_nids = array_keys($result['node']);
  $news_items = entity_load('node', $news_items_nids);
}

このコードをみてどう感じられたでしょうか。db_query()db_select() を使うといくつものテーブルをジョインする必要がありますが、EntityFieldQuery その辺りの処理を自動的にこなしてくれます。ただし、EntityFieldQuery は db_query() などのデータベース API と比べるとオーバーヘッドが大きいようですので、パフォーマンスも求めるような処理の場合は低レイヤーの API を使った方が良いそうです。

コードの最後から5行目にある $result 変数は、一次元目のキーにエンティティのタイプ、二次元目のキーにエンティティの ID が入った連想配列となります。

$result['node'][12322] = array(一部のノードのデータ);

結果が空のときに $resultnode 配列にキーが無いことに注意しましょう。チェックする際は isset() の関数を使います。詳しくはこの説明をご覧ください。

オペレーター

以下に登場する entityCondition(), propertyCondition()fieldCondition() メソッドで使える引数の $operator$value は同じ振る舞いなので先に解説します。使用できる $value の値はデータベース API と同様に $operator によって使用できる型が変わります。public function EntityFieldQuery::propertyCondition のページに詳しく解説されていますが、以下に一部抜粋して解説します。

オペレーターに使用できる値は以下の通りです。

  • =, <>, >, >=, <, <=, STARTS_WITH, CONTAINS: $value にはデータベースのカラムと同じタイプの値を使用します。
  • IN, NOT IN: $value には1つ以上の値が入った配列を使用します。
  • BETWEEN: $value には2つの値が入った配列を使用します。

オペレーターは省略することができますが、$value の型によってデフォルト値が変わります。$value が配列の場合は IN に、それ以外は = となります。

各メソッドを使ったクエリーの作成

entityCondition($name, $value, $operator = NULL)

entityConditions() メソッドは殆どのエンティティクラスでサポートされており、エンティティのプロパティで絞り込むためのメソッドです。$name のプロパティを $value の条件で絞り込みます。

$name $value 説明
entity_type node, taxonomy_term などのエンティティのシステム名称 エンティティタイプを指定します。オペレーターを指定することはできません。
bundle article, page など エンティティのバンドルを指定します。エンティティがノードの場合はコンテンツタイプになります。コメントエンティティの様なバンドルの概念が無い一部のエンティティではサポートされていません。
revision_id 整数 リビジョンの ID を指定します。
entity_id 整数 エンティティ ID を指定します。エンティティがノードの場合はノード ID になります。

エンティティタイプが「ノード」かつ、コンテンツタイプが article のものを抽出する際の例は以下の通りです。

$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node')
  ->entityCondition('bundle', 'article');
$result = $query->execute();

propertyCondition($name, $value, $operator = NULL)

propertyCondition() はエンティティに固有に設定されたプロパティを絞り込むためのメッソです。

通常、プロパティの実体はデータベース上のエンティティのベーステーブルにカラムとして格納されています。例えばノードエンティであれば node、ファイルエンティティであれば file_managed に在ります。grep コマンドで hook_entity_info() を検索すれば、エンティティのベーステーブルを示すコードを探すことができます。

状態が「公開」のノードだけ抽出する場合は以下の様に使います。

$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node')
  ->propertyCondition('status', NODE_PUBLISHED);
$result = $query->execute();

fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL)

エンティティに設定されたフィールドの値で絞り込むためのメソッドです。

  • $field: システムで使用されている名称を使用します。例えば Body (本文)フィールドの場合、データベースでは field_data_body と言う名前のテーブルですが、実際には field_data_の接頭辞を取り除いた body と言う名前になります。field_config_instance テーブルでその相関を見ることができます。
  • $column: カラムで使用する名前はデータベース上のカラム名から接頭辞を抜いた形で一致する必要があります。通常は前述のシステム内部で使用されるフィールド名が接頭辞として使用されているので、それを取り除いた形です。Body フィールドであれば body_value, body_summary, body_format, language というカラムがありますが、これらは value, summary, format, language と言う名前になります。

fieldOrderBy($field, $column, $direction = 'ASC')

フィールドの値によって結果をソートするメソッドです。

  • $field: 前述の fieldCondition() と同様に field_data_ の接頭辞を取り除いた値が使用できます。
  • $column: こちらも前述の fieldCondition() と同様に、フィールドのシステム名称を抜いたデータベースカラム名を指定します。

propertyOrderBy($column, $direction = 'ASC')

プロパティで結果をソートするメソッドです。

原文の記事では "does not work on all properties." と書かれているのですが、手元の環境ではちゃんと動作しました。

range($start = NULL, $length = NULL)

クエリーの結果をここで設定された範囲に限定します。

count()

クエリーの結果数のカウントだけ取得します。

$count = $query->count()->execute();

// クエリーの結果の数が出力されます。
echo $count;

addMetaData($key, $object)

メタデータを追加します。

EntityFieldQuery はデフォルトではカレントユーザーの権限で実行されますが、その振る舞いが望まれないケースがあります。addMetaData() を使うことで他のユーザーとして実行し結果を取得することができます。以下の例はユーザー ID が 1 のスーパーユーザーで実行する場合の例です。

$query->addMetaData('account', user_load(1));

ランダムに並び替え

結果をランダムに並べ替える最も簡単な方法は、addTag() メソッドを使って random タグを追加する方法です。

<?php
function mymodule_superblock() {
  $query = new EntityFieldQuery();
  $result = $query
    ->entityCondition('entity_type', 'node')
    ->fieldCondition('field_categories', 'tid', array('12', '13'), 'IN')
    ->propertyCondition('status', NODE_PUBLISHED)
    ->addTag('random')
    ->range(0, 5)
    ->execute();
}
?>

タクソノミータームの ID を抽出する例

以下は Tags ボキャブラリからタームの ID を配列で取得する例です。

<?php

function connect_country_fetch_countries() {
  $query = new EntityFieldQuery();
  $query->entityCondition('entity_type', 'taxonomy_term')
  ->entityCondition('bundle', array('tags'))
  ->propertyOrderBy('weight');
  $result = $query->execute();
  $tids = array_keys($result['taxonomy_term']);
  return $tids;
}
?>

EntityFieldQuery で何が使えるか調べるには

以下の関数を使うことで、どんなエンティティやフィールドが定義されているか調べることができます。

<?php
// 定義されている全てのエンティティリストを取得します。
dpm(entity_get_info());

// 定義されている全てのフィールドリストを取得します。
dpm(field_info_fields());
?>

デバッグ方法

Devel モジュールの dpm() 関数と hook_query_alter() を使うことで EntityFieldQuery をデバッグすることができます。以下の例はstackexchange で解説されている方法です。

<?php
function CUSTOMMODULE_query_alter($query) {
  if ($query->hasTag('efq_debug') && module_exists('devel')) {
    dpm((string) $query);
    dpm($query->arguments());
  }
}
?>

上記の hook_query_alter() を作成し、以下の様に addTag() メソッドを使い efq_debug タグを追加することでクエリーの内容を見ることができます。

<?php
$q = new EntityFieldQuery;
$q->entityCondition('entity_type', 'node');
  ->addTag('efq_debug');
  ->execute();
?>

参考リンク

コードの例

  • modules\simpletest\tests\entity_query.test に EntityFieldQuery のテストがあります。
  • Drupal ディレクトリを grep コマンドを使ってnew EntityFieldQuery()を検索することで、コアやコントリビュートモジュールの使用例を検索できます。

記事

以下の記事は全て英語です。

その他のリソース