Одним из нововведений Drupal 7 был объектно-ориентированный подход в системе построения запросов к базе данных. Работа системы строилась на таких функциях, как db_select, db_update, db_insert и т.д. Поддерживалось также и прямое написание запросов на основе функций db_query и db_query_range. Основные плюсы данного похода заключаются в отсутствии привязки к определённой СУБД и удобстве поддержки кода. В Drupal 8 все эти функции помечены, как deprecated (с выходом Drupal 9 будут удалены из ядра), и выполнение запросов к базе данных рекомендуется выполнять на основе системы соответствующих классов.

Общие принципы взаимодействия с базой данных в Drupal 8 строятся на получении объекта подключения к текущей базе данных и вызове методов данного объекта для построения конечного запроса и получения результата его выполнения. Получить объект подключения можно с помощью контейнера сервисов (класс Drupal), используя его статический метод database(). После получения объекта подключения нужно получить объект, соответствующий требуемому типу запроса. Как и в седьмой версии, в Drupal 8 построение запроса выполняется на основе таких методов, как fields(), join(), condition() и т. д. Выполнить запрос можно обращением к методу execute().

 

Выборка

Самый часто используемый способ "общения" с базой данных - это выборка. При разработке модулей не раз приходится сталкиваться с необходимостью получения данных из таблиц контрибных модулей или ядра Drupal. Получить объект SELECT-запроса можно с помощью метода select() из объекта подключения. Рассмотрим примеры основных SELECT-запросов:

1) Простейшая выборка с условием

$query = \Drupal::database()->select('node_field_data', 'nfd');
$query->fields('nfd', ['uid', 'title']);
$query->condition('nfd.nid', 1);
$result = $query->execute()->fetchAll();

В данном примере выполняется выбор значений полей uid и title таблицы node_field_data при условии, что значение в поле nid равно единице. Результат выполнения запроса формируется в виде массива, каждое значение которого будет объектом класса stdClass и будет содержать значения выбранных записей. Добавлять  поля в запросе можно также с помощью метода addField(). По умолчанию в методе condition() используется оператор равенства, но можно использовать и другие операторы, передав оператор в качестве третьего параметра.

2) Выборка одного значения

$query = \Drupal::database()->select('node_field_data', 'nfd');
$query->addField('nfd', 'title');
$query->condition('nfd.nid', 1);
$result = $query->execute()->fetchField();

В переменной $result будет содержаться прямое значение поля title. В случае наличия нескольких полей в результирующей записи, будет использовано первое из них. В случае наличия нескольких записей, будет использовано первое поле первой записи.

3) Выборка первой записи

$query = \Drupal::database()->select('node_field_data', 'nfd');
$query->fields('nfd', ['nid', 'title']);
$query->condition('nfd.type', 'article');
$result = $query->execute()->fetchAssoc();

В переменной $result будет содержаться первая запись результата выборки в виде ассоциативного массива. Для получения записи в виде объекта нужно использовать метод fetchObject().

4) Выборка первой колонки в виде простого массива

$query = \Drupal::database()->select('node_field_data', 'nfd');
$query->addField('nfd', 'title');
$query->condition('nfd.type', 'article');
$result = $query->execute()->fetchCol();

В переменной $result будет содержаться одномерный массив, содержащий значения поля title всех выбранных записей.

5) Объединение таблиц в выборке

$query = \Drupal::database()->select('node_field_data', 'nfd');
$query->addField('nfd', 'title');
$query->addField('ufd', 'name');
$query->join('users_field_data', 'ufd', 'ufd.uid = nfd.uid');
$query->condition('nfd.type', 'article');
$result = $query->execute()->fetchAll();

Объединять таблицы в запросе также можно посредством методов innerJoin() (к слову метод join() - это синоним данного метода) и leftJoin().

6) Выбор определённого диапазона записей

$query = \Drupal::database()->select('node_field_data', 'nfd');
$query->fields('nfd', ['nid', 'title']);
$query->condition('nfd.type', 'article');
$query->range(0, 10);
$result = $query->execute()->fetchAll();

С помощью метода range() можно управлять диапазоном выбираемых записей. Метод имеет два параметра: первый - позиция начала диапазона, второй - количество выбираемых записей с начала диапазона.

7) Использование условий ИЛИ в выборке

$condition_or = new \Drupal\Core\Database\Query\Condition('OR');
$condition_or->condition('nfd.nid', 5);
$condition_or->condition('nfd.nid', 7);
 
$query = \Drupal::database()->select('node_field_data', 'nfd');
$query->fields('nfd', ['nid', 'title']);
$query->condition($condition_or);
$result = $query->execute()->fetchAll();

В переменной $result будут две записи - для ноды с nid 5 и для ноды с nid 7. 

8) Подсчёт числа записей в выборке

$query = \Drupal::database()->select('node_field_data', 'nfd');
$query->fields('nfd', ['nid', 'title']);
$query->condition('nfd.type', 'article');
$result = $query->countQuery()->execute()->fetchField();

9) Проверка значений на NULL

$query = \Drupal::database()->select('example', 'e');
$query->fields('e');
$query->isNull('e.field_null');
$query->isNotNull('e.field_not_null');
$result = $query->execute()->fetchAll();

10) Применение сложных выражений в выборке

$query = \Drupal::database()->select('node_field_data', 'nfd');
$query->fields('nfd', ['nid', 'title']);
$query->addExpression("DATE_FORMAT(FROM_UNIXTIME(nfd.created), '%e %b %Y')", 'node_created');
$result = $query->execute()->fetchAll();

11) Группировка записей выборки

$query = \Drupal::database()->select('node_field_data', 'nfd');
$query->addField('nfd', 'type');
$query->addExpression('COUNT(*)', 'count');
$query->groupBy('nfd.type');
$result = $query->execute()->fetchAll();

12) Применение сложных условий в запросе

$query = \Drupal::database()->select('node_field_data', 'nfd');
$query->fields('nfd', ['nid', 'title', 'type']);
$query->where('DAY(FROM_UNIXTIME(nfd.created)) = :day', [':day' => 7]);
$result = $query->execute()->fetchAll();

Отмечу, что метод where() можно использовать не только в контексте SELECT-запросов. Например, его можно применить в запросе UPDATE или DELETE.

13) Сортировка выбранных записей

$query = \Drupal::database()->select('node_field_data', 'nfd');
$query->fields('nfd', ['nid', 'title', 'type']);
$query->orderBy('nfd.title');
$result = $query->execute()->fetchAll();

Направление сортировки можно задать посредством второго параметра метода orderBy(), который по умолчанию равен "ASC". Сделать рандомную сортировку можно на основе метода orderRandom().

 

Вставка и изменение

В случае, когда требуется добавление или изменение данных в определённой таблице, лучше обратиться к Drupal API (если таблица относится к ядру) или API контрибного модуля (если таблица относится к контрибному модулю). Однако, если вы создаёте собственный модуль, который оперирует своими таблицами, то операции добавления и изменения придётся описывать вручную.

Объект UPDATE-запроса получить можно с помощью метода update() из объекта подключения, а объект INSERT-запроса - с помощью метода insert(). Аналогично предыдущему разделу, рассмотрим основные примеры запросов:

1) Обновление записей

$query = \Drupal::database()->update('example');
$query->fields([
  'field_1' => $value_1,
  'field_2' => $value_2,
  'field_3' => $value_3,
]);
$query->condition('field_4', $value_4);
$query->execute();

В результате выполнения этого кода в таблице example будут обновлены поля field_1, field_2 и field_3 (они получат значения $value_1, $value_2 и $value_3 соответственно) для записей, в которых поле field_4 равно $value_4.

2) Применение сложных выражений при обновлении

$query = \Drupal::database()->update('example');
$query->expression('field_1', 'field_1 + :amount', [':amount' => 100]);
$query->expression('field_3', 'field_2');
$query->execute();

Если нужно применить сложное выражение для обновления данных, то следует использовать метод expression(). Он содержит три параметра - обновляемое поле, выражение, аргументы для выражения. В данном примере для всех записей численное поле field_1, будет увеличено на 100, а поле field_3 получит значения из field_2.

3) Добавление одной записи

$query = \Drupal::database()->insert('example');
$query->fields([
  'field_1' => $value_1,
  'field_2' => $value_2,
  'field_3' => $value_3,
]);
$query->execute();

Стоит отметить, что метод fields() может принимать один или два параметра. Если первый параметр - ассоциативный массив, то ключи массива должны соответствовать полям таблицы, а значения - добавляемым значениям. В этом случае второй параметр опускается. Если первый параметр обычный массив, то он соответствует полям таблицы и нужно передать второй параметр, который должен содержать добавляемые значения в порядке, соответствующем порядку полей в первом параметре. Если второй параметр опущен, добавляемые значения нужно передать на основе метода values().

4) Добавление нескольких записей

$values = [
  [$value_1, $value_2, $value_3],
  [$value_4, $value_5, $value_6],
  [$value_7, $value_8, $value_9],
];
$query = \Drupal::database()->insert('example');
$query->fields(['field_1', 'field_2', 'field_3']);
foreach ($values as $record) {
  $query->values($record);
}
$query->execute();

5) Добавление или обновление в зависимости от наличия записи

$query = \Drupal::database()->upsert('example');
$query->fields(['field_id', 'field_1']);
$query->values([$id, $value_1]);
$query->key('field_id');
$query->execute();

Часть бывают случаи, что в зависимости от контекста нужно либо обновить запись, либо добавить новую. Реализация предварительного SELECT-запроса проверки наличия записи с дальнейшей условной конструкцией - это плохой тон. В Drupal 8 для этого имеется объект UPSERT-запроса (в Drupal 7 кстати такого типа запросов нет). Получить этот объект можно с помощью метода upsert(). Объект UPSERT-запроса обязательно должен содержать ключевое поле, по которому будет выполняться проверка существования записи. Поле должно быть уникальным в рамках таблицы. Добавить ключевое поле можно вызовом метода key().

Относительно обновления и добавления записей стоит ещё сказать, что метод execute() в случае операции обновления возвращает количество обновлённых записей, а в случае операции добавления - идентификатор добавленной записи, при условии, что добавлялась одна запись. Если добавлялось множество записей, возвращаемый идентификатор не определён.

 

Удаление

Иногда записи нужно и удалять. В случае таблиц модулей или ядра также следует использовать только соответствующее API. Ну, а для удаления данных из своих таблиц смело используйте объект DELETE-запроса, который, как вы, наверное, догадались, можно получить с помощью метода delete().

$query = \Drupal::database()->delete('example');
$query->condition('field', $value);
$query->execute();

Построение условий для запроса удаления идентично построению для запросов выборки и изменения (можно использовать проверку на NULL, сложные выражения и т.д.), а метод execute() возвращает количество удалённых записей.

 

В виде заключения

В общем и целом структура построения запросов к базе данных в Drupal 8 не сильно изменилась относительно Drupal 7. Однако появились некоторые приятные и удобные вещи (например, запрос UPSERT). К слову, когда у вас не получается составить нужный запрос на основе объектной модели, вы всегда можете на свой страх и риск (если вы плохой SQL-щик) выполнить прямой запрос к базе данных с помощью метода query() объекта подключения. Метод в качестве входных параметров может принимать строку с запросом к базе данных.

Материалы для изучения

  1. Основные отличия в построении запросов к БД между Drupal 8 и Drupal 7
  2. Обзор Drupal 8 Database API
  3. Drupal 8 - запросы к базе данных

Комментарии

Ольга Тимцова

Благодарю! Интересно так !

Сергей

>> Направление сортировки можно задать посредством второго параметра метода groupBy(), который по умолчанию равен "ASC"

orderBy() же. Поправьте, пожалуйста.

Исправили, спасибо!
Олег

Спасибо, Андрей!

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