Одним из нововведений 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() объекта подключения. Метод в качестве входных параметров может принимать строку с запросом к базе данных.
Материалы для изучения
Комментарии
>> Направление сортировки можно задать посредством второго параметра метода groupBy(), который по умолчанию равен "ASC"
orderBy() же. Поправьте, пожалуйста.
Благодарю! Интересно так !