В одной из прошлых статей я рассматривал особенности построения запросов к базе данных на основе Drupal 8 Database API. Безусловно, с помощью данного API можно получать любые данные, содержащиеся в базе, однако, такой подход не всегда оправдан. В случае работы с сущностями правильным решением будет использование Entity API, а если точнее, то сервиса entity.query.

 

Сервис entity.query

Сервис entity.query представляет собой развитие EntityFieldQuery из Drupal 7 и, аналогично своему прародителю, служит для удобного получения сущностей, соответствующих определённому паттерну. Получать таким образом можно любые сущности - и контентные и конфигурационные.

Сервис предоставляет объект запроса (реализует интерфейс \Drupal\Core\Entity\Query\QueryInterface) для заданного типа сущности. Существует два способа получить данный объект: правильный и н̶е̶п̶р̶а̶в̶и̶л̶ь̶н̶ы̶й̶ простой.

Правильный способ заключается в получении объекта с помощью контейнера сервисов. Это наиболее рекомендуемый подход. Он удобен при наличии доступа к контейнеру (например, в методе create() у класса плагина).

/** @var \Symfony\Component\DependencyInjection\ContainerInterface $container */
$service = $container->get('entity.query');
$query = $service->get($entity_type);

Простой способ предполагает использование статического метода entityQuery() класса \Drupal. Данный подход не рекомендуется к применению, поэтому использовать его стоит только в общем случае при отсутствии доступа к контейнеру сервисов.

$query = \Drupal::entityQuery($entity_type);

В обоих случаях в переменную $entity_type нужно передать идентификатор типа сущностей, с которыми требуется работать (например, node, user, comment и т.д.).

 

Загружаем сущности правильно

Перейдём к конкретным примерам. Для простоты будем создавать объект запроса с помощью статического метода класса \Drupal.

Пример: Получить идентификаторы всех опубликованных нод типа article.

$query = \Drupal::entityQuery('node');
$query->condition('type', 'article');
$query->condition('status', 1);
$nids = $query->execute();

Здесь используются методы condition() и execute(). Первый используется для применения условий к запросу, а второй непосредственно для выполнения запроса и получения идентификаторов загружаемых сущностей. В метод condition() можно передавать четыре параметра:

  • field - машинное имя поля сущности, для которого определяется условие (является обязательным параметром);
  • value - значение поля, может быть скалярным значением или массивом. Если параметр опущен, то будет использоваться NULL;
  • operator - оператор условия. Если параметр опущен, то ему будет присвоено значение «=»;
  • langcode - код языка. Если параметр опущен, любой перевод будет удовлетворять условию. В противном случае выборка будет ограничена заданным переводом.

В entity.query не существует различий между свойствами и полями сущности, как это было при использовании EntityFieldQuery в Drupal 7. Теперь определение условий для свойств и полей делается в рамках метода condition().

Параметр field должен содержать имя поля сущности, за которым может следовать имя столбца поля. В свою очередь, столбец может быть ссылочным свойством (в случае ссылочного поля), за которым может следовать имя поля целевой сущности и т. д. Кроме того, тип целевой сущности можно указать, добавив «:target_entity_type_id» после ссылочного свойства.

В качестве оператора допустимы следующие значения:

  • =, <>, >, >=, <, <=, STARTS_WITH, CONTAINS, ENDS_WITH: Эти операторы предполагают, что тип value соответствует типу значений поля;
  • IN, NOT IN: Эти операторы предполагают, что value является массивом и тип каждого элемента соответствует типу значений поля;
  • BETWEEN: Этот оператор предполагает, что value является массивом из двух элементов, тип которых соответствует типу значений поля;

Пример: Получить идентификаторы всех опубликованных нод типа poem или fairy_tale, которые в тизере поля body содержат фразу «У лукоморья дуб зеленый», помечены трёмя и более тегами, один из которых «Сказки Пушкина», и опубликованы пользователем с идентификатором 1799 в течение последней недели.

$query = \Drupal::entityQuery('node');
$query->condition('type', ['poem', 'fairy_tale'], 'IN');
$query->condition('body.summary', 'У лукоморья дуб зеленый', 'CONTAINS');
$query->condition('field_tags.%delta', 2);
$query->condition('field_tags.entity:taxonomy_term.name', 'Сказки Пушкина');
$query->condition('uid', 1799);
$query->condition('created', time() - 604800, '>');
$query->condition('status', 1);
$nids = $query->execute();

Стоит отметить, что такие значения параметра field, как body и body.value будут идентичны, так как value является столбцом по умолчанию. В случае multiple-поля можно определять условие относительно конкретного значения с помощью указания дельты. Например, параметр field_tags.7 будет указывать на восьмое значение поля field_tags. Кроме того, можно задать условие для самой дельты, используя «%delta», как это было реализовано в примере выше.

Тип целевой сущности указывать необязательно, если он точно известен. Например, если известно, что поле field_tags хранит ссылки на термины таксономии, то параметры field_tags.entity.name и field_tags.entity:taxonomy_term.name будут тождественны.

Пример: Получить идентификаторы всех нод типа article, которые имеют тизер поля body и не отмечены тегами.

$query = \Drupal::entityQuery('node');
$query->condition('type', 'article');
$query->exists('body.summary');
$query->notExists('field_tags');
$nids = $query->execute();

За проверку поля на пустоту отвечают методы exists() и notExists(). Важно обратить внимание, что эти методы не выполняют проверку на наличие поля у сущности. Это значит, что при проверке несуществующего поля будет получена ошибка. При этом вызов exists($field) идентичен вызову condition($field, NULL, 'IS NOT NULL'), а вызов notExists($field) идентичен condition($field, NULL, 'IS NULL').

Пример: Получить идентификаторы всех нод, упорядоченные по дате создания.

$query = \Drupal::entityQuery('node')->sort('created', 'DESC');
$nids = $query->execute();

Пример: Получить количество всех нод.

$query = \Drupal::entityQuery('node')->count();
$count = $query->execute();

Пример: Получить идентификаторы последних 10 нод.

$query = \Drupal::entityQuery('node');
$query->sort('created', 'DESC');
$query->range(0, 10);
$nids = $query->execute();

С помощью метода pager() также есть возможность управления результатами выборки на основе пагинации.

Пример: Получить идентификаторы всех нод, опубликованных пользователем с идентификатором 1 или с именем «superduperuser».

$query = \Drupal::entityQuery('node', 'OR');
$query->condition('uid', 1);
$query->condition('uid.entity.name', 'superduperuser');
$nids = $query->execute();

Посредством значения параметра conjunction в методе создания объекта запроса можно управлять объединением условий. Допустимы значения AND и OR.  В первом случае условия будут соединены конъюнкцией, а во втором - дизъюнкцией. По умолчанию условия объединяются с помощью конъюнкции.

Пример: Получить идентификаторы всех нод типа article, опубликованных пользователем с идентификатором 1 или с именем «superduperuser».

$query = \Drupal::entityQuery('node');
$query->condition('type', 'article');
 
$group = $query->orConditionGroup();
$group->condition('uid', 1);
$group->condition('uid.entity.name', 'superduperuser');
 
$query->condition($group);
$nids = $query->execute();

Методы andConditionGroup() и orConditionGroup() позволяют добавлять условия, связанные конъюнкцией и дизъюнкцией, независимо от параметра conjunction. Эти методы позволяют реализовывать действительно гибкие условия.

Пример: Получить объекты нод, идентификаторы которых были получены с помощью entity.query.

$nids = \Drupal::entityQuery('node')->execute();
$nodes = \Drupal\node\Entity\Node::loadMultiple($nids);

Метод execute() возвращает массив (при использовании метода count() будет возвращено число), значения которого всегда будут содержать идентификаторы сущностей. Ключи массива могут быть как идентификаторами ревизий сущностей, так и идентификаторами самих сущностей в зависимости от наличия поддержки ревизий у заданного типа сущностей.

Методы currentRevision()latestRevision() и allRevisions() позволяют управлять идентификаторами ревизий в результирующем массиве. При использовании первого метода ключами массива будут идентификаторы текущих ревизий, а при использовании второго метода - идентификаторы самых поздних. Наконец, при использовании третьего метода ключами массива будут идентификаторы всех ревизий.

 

Полезные материалы

Андрей Тымчук

Комментарии

Ключи, указываемые в "condition", у разных типов сущностей отличаются. Но можно унифицировать свой запрос, предварительно получая список ключей, примерно так:
 

$keys = \Drupal::entityTypeManager()->getDefinition('node')->getKeys();

Добавить комментарий