Drupal 7 の EntityFieldQuery でオリジナルの条件指定メソッドを利用する方法

今回は Drupal 7 の開発のマニアックなお話です。

$pq = new PackageQuery();
$result = $pq->random()->range(0, 1)->execute();
$random_package_id = key($result['node']);

Drupal 7 で開発を行っていると EntityFieldQuery でオリジナルの条件指定メソッドを作成したくなることがあります。 オリジナルの条件を指定するには基本的には addTag() メソッドを利用すれば OK です。 ただこれだとコードがいかにも手続き的な感じになりわかりづらくなるので、ドメインモデルに忠実なサブクラスを作成してそこでメソッドを作成すると吉です。

以下サンプルコードです。 package というノードタイプがサイトに存在し、ランダム、 URL フィールドがセットされているもの、いないものなど、さまざまな条件でノード一覧を抽出したいとします。

まずは EntityFieldQuery を継承したオリジナルのクエリクラスを定義します( Composer などのパッケージを表す「 Package 」エンティティがあるような想定です)。

/**
 * Package entity query class
 */
class PackageQuery extends EntityFieldQuery {

  public function random() {
    $this->addTag('random');
    return $this;
  }

  public function isUrlFilled() {
    $this->addTag('is_url_filled');
    return $this;
  }

  public function isUrlEmpty() {
    $this->addTag('is_url_empty');
    return $this;
  }

}

ポイントは addTag() を利用することとチェイニングのために return $this しておくことです。

このクラスの使い方は以下のとおり。

// ランダムに 1 件抽出する
// (本当は結果が 0 件の場合の分岐処理が必要ですがここでは省略しています)
$pq = new PackageQuery();
$result = $pq->random()->range(0, 1)->execute();
$random_id = key($result['node']);

// URL フィールドがセットされたものを全件取得する
$pq = new PackageQuery();
$result = $pq->isUrlFilled()->execute();
$ids_with_url = $result['node'];

// URL フィールドがセットされていないものを全件取得する
$pq = new PackageQuery();
$result = $pq->isUrlEmpty()->execute();
$ids_without_url = $result['node'];

addTag('is_url_filled')addTag('is_url_empty') は以下の形で hook_query_TAG_alter() を実装して実現します。

/**
 * Implementation of hook_query_TAG_alter
 */
function mymodule_query_is_url_filled_alter($query) {
  $query->leftJoin('field_data_field_my_url', 'pu', 'node.nid = pu.entity_id');
  $query->condition('pu.entity_type', 'node')
    ->isNotNull('pu.field_package_url_value')
  ;
}

/**
 * Implementation of hook_query_TAG_alter
 */
function mymodule_query_is_url_empty_alter($query) {
  $query->leftJoin('field_data_field_my_url', 'pu', 'node.nid = pu.entity_id');
  $is_node_cond = db_or()->condition('pu.entity_type', 'node')
    ->isNull('pu.entity_type')
  ;
  $query
    ->condition($is_node_cond)
    ->isNull('pu.field_package_url_value')
    ;
}

便利です。