В одной из прошлых статей я рассматривал особенности построения запросов к базе данных на основе 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() позволяют управлять идентификаторами ревизий в результирующем массиве. При использовании первого метода ключами массива будут идентификаторы текущих ревизий, а при использовании второго метода - идентификаторы самых поздних. Наконец, при использовании третьего метода ключами массива будут идентификаторы всех ревизий.
Полезные материалы
Комментарии
А можно каким-то образом в результате entityQuery сразу получить готовую загруженную сущность без промежуточного массива?
Т.е. вместо
$query = \Drupal::entityQuery('node', 'OR');
$query->condition('uid', 1);
$query->condition('uid.entity.name', 'superduperuser');
$query->range(0, 1);
$nids = $query->execute();
$node = Node::load(current($nids));
иметь что-то вида:
$node = $query->getFirstAndLoad();
Я долго изучал, как работает правильный метод вызова запросов для сущностей. В итоге нашел статью, что он уже устарел. https://www.drupal.org/node/2849874
Да, сервис entity.query
объявлен устаревшим и в Drupal 9 его уже нет. Теперь объект запроса нужно получать через хранилище конкретного типа сущностей. При этом метод \Drupal::entityQuery()
всё ещё доступен для не объектно-ориентированного кода.
Ключи, указываемые в "condition", у разных типов сущностей отличаются. Но можно унифицировать свой запрос, предварительно получая список ключей, примерно так:
$keys = \Drupal::entityTypeManager()->getDefinition('node')->getKeys();