[Drupal] エンティティクエリ（EntityQuery）の使い方と注意点まとめ

今回はエンティティのIDのリストを取得するのによく使われる「エンティティクエリ」について解説したいと思います。

[public static function Drupal::entityQuery](https://api.drupal.org/api/drupal/core!lib!Drupal.php/function/Drupal%3A%3AentityQuery/8.2.x)

## 基本的な使い方
公開の記事タイプノードで絞り込む場合はこのようになります。

```php
$nids = \Drupal::entityQuery('node')
  ->condition('type', 'page')
  ->condition('status', TRUE)
  ->execute();
```
ノードのようにリビジョンがあるエンティティの場合、結果は以下のように`リビジョンID => エンティティID`の配列になります。ユーザーのようにリビジョンが無いエンティティの場合は`エンティティID => エンティティID`です。データベースAPIと違い、それ以外のフィールド値を取得できない点に注意です。

```
 [
     2 => "1", // リビジョンIDが2、ノードIDが1
     6 => "2",
   ]
```

### 便利な使い方
- 件数を調べる

```php
$nids = \Drupal::entityQuery('node')
  ->latestRevision()
  ->condition('type', 'page')
  ->count()
  ->execute();
```
- フィールド値が存在するかどうかで絞り込む

```php
$nids = \Drupal::entityQuery('node')
  ->latestRevision()
  ->condition('type', 'page')
  ->exists('field_page_editor')
  ->execute();
```

- 参照先エンティティのフィールド値で絞り込む

```php
$nids = \Drupal::entityQuery('node')
 ->condition('type', 'page')
 ->condition('field_page_editor.entity:user.mail', 'editor@example.com')
 ->execute();
```

- パラグラフの親エンティティの値で絞り込む(`parent_id`, `parent_type`, `parent_field_name`が使える)
```php
\Drupal::entityQuery('paragraph')
  ->condition('type', 'my_paragraph')
  ->condition('parent_field_name', ['field_a', 'field_c'], 'IN')
```

## 気をつけたい仕様

個人的にハマったポイントです。

### 通常だとデータを検索できるのはデフォルトリビジョンだけ
デフォルトリビジョンと最新リビジョンが違う場合、上記の結果に上がってくるのはデフォルトリビジョンだけになります。

- 最新リビジョンを対象に検索する

```php
$nid = \Drupal::entityQuery('node')
  ->latestRevision()
  ->condition('type', 'page')
  ->condition('status', TRUE)
  ->execute();
```

- 全てのリビジョンを対象に検索する

```php
$nid = \Drupal::entityQuery('node')
  ->allRevisions()
  ->condition('type', 'page')
  ->condition('status', TRUE)
  ->execute();
```
全てのリビジョンを対象にした場合の結果

```
 [
     1 => "1",
     2 => "1",
     3 => "2",
     4 => "2",
     5 => "2",
     6 => "2",
   ]
```

### Content Moderationのモデレーション状態では検索できない

Content Moderationでモデレーション状態を付与すると、エンティティにmoderation_stateというフィールドが追加されるのでentityQueryで検索できそうな感じがしますが、現時点ではできません。モデレーション状態で絞り込みたい場合はデータベースAPIを使うしかないです。

☓できない

```php
$nid = \Drupal::entityQuery->('node')
  ->latestRevision()
  ->condition('type', 'page')
  ->condition('moderation_state', 'pending')
  ->execute();
```
○できる

```php
$database = \Drupal::database();
$query = $database->select('node_field_data', 'n')
  ->leftJoin('content_moderation', 'c', 'n.nid = c.entity_id')
  ->addField('n.nid')
  ->condition('c.moderation_state', 'pending');
```

### エンティティクエリで検索できるデータは現在のユーザーがアクセスできるデータだけ
エンティティクエリでデータを検索すると、エンティティに対してアクセスチェックが行われ、現在のユーザーがアクセスできないエンティティは結果に入らない仕組みになっています。cronやスクリプトで実行しているときはユーザーが匿名ユーザーになっているので、非公開のデータは返ってきません。現在のユーザーにアクセスできないエンティティも検索対象にするには、`accessCheck`を`FALSE`にする必要があります。

```php
$nid = \Drupal::entityQuery->('node')
  ->latestRevision()
  ->condition('type', 'page')
  ->condition('status', TRUE)
  ->accessCheck(FALSE)
  ->execute();
```

### Database APIに比べると遅い

Drupalのバックエンドでデータを検索する方法というと次の3つがります。

- データベースAPIの静的クエリ
- データベースAPIの動的クエリ
- エンティティクエリ

速さを比べると、データベースAPIの静的クエリ > データベースAPIの動的クエリ > エンティティクエリの順になります。ちなみにViewsはさらに遅いです。

## エンティティクエリのメリットは可読性の高さ
エンティティクエリはデータベースの実際のテーブル名やカラム名といった予備知識が不要なので、それが必要なデータベースAPIに比べるととても読みやすいです。基本的にはエンティティクエリ、速さが必要な部分でデータベースAPIを使うのがいいのではないかと思います。