Урок 8. Работа с базой данных

Работа с базой данных в Drupal 8

Работа с базой данных — неотъемлемая часть разработки. Сегодня поговорим о том, что изменилось по этому поводу в восьмерке.

Содержание

Устаревшие функции

Нововведения коснулись функций для работы с базой данных (БД). Они все еще могут использоваться, но уже являются устаревшими и будут удалены в 9 версии. Если установлен и правильно настроен Code Sniffer, то эти функции будут зачеркнутые как deprecated. Ниже приведен список этих функций[1]:

  • db_query — выполняет запрос для текущей активной базы данных.
  • db_query_range — аналогично db_query, только с ограничением по диапазону выборки.
  • db_query_temporary — выполняет SELECT строковый запрос и сохраняет результат во временную таблицу.
  • db_insert — создает новую запись в конкретной таблице.
  • db_merge — мержит результаты новой записи и старой, которая уже существовала в БД.
  • db_update — обновляет запись в таблице.
  • db_delete — удаляет запись из таблицы.
  • db_truncate — очищает таблицу целиком от всех записей.
  • db_select — выполняет SELECT запрос.
  • db_transaction — создает транзакцию для текущей БД.
  • db_set_active — устанавливает новую активную БД.
  • db_escape_table — ограничивает имя динамической таблицы безопасными символами.
  • db_escape_field — ограничивает имя динамического поля безопасными символами.
  • db_like — подставляет символы, которые необходимы в шаблоне LIKE.
  • db_driver — получает имя текущего активного драйвера БД.
  • db_close — закрывает соединение с активной базой.
  • db_next_id — получает следующий уникальный ID.
  • db_or — устанавливает OR для всех переданных условий.
  • db_and — устанавливает AND для всех переданных условий.
  • db_xor — устанавливает XOR для всех переданных условий.
  • db_condition — возвращает новое DatabaseCondition.
  • db_create_table — создает таблицу в БД.
  • db_field_names — возвращает массив имен филдов в виде ключ/индекс.
  • db_index_exists — проверяет существование индекса в конкретной таблице.
  • db_table_exists — проверяет существование таблицы в БД.
  • db_field_exists — проверяет существование колонки в определенной таблице.
  • db_find_tables — находит все таблицы, которые удовлетворяют поиску.
  • db_rename_table — переименовывает таблицу.
  • db_drop_table — удаляет таблицу.
  • db_add_field — добавляет новые поле в таблицу.
  • db_drop_field — удаляет поле из таблицы.
  • db_field_set_default — устанавливает дефолтное значение для поля.
  • db_field_set_no_default — устанавливает поле, которое не будет иметь дефолтного значения.
  • db_add_primary_key — добавляет primary ключ.
  • db_drop_primary_key — удаляет primary ключ.
  • db_add_unique_key — добавляет уникальный ключ.
  • db_drop_unique_key — удаляет уникальный ключ.
  • db_add_index — добавляет индекс.
  • db_drop_index — удаляет индекс.
  • db_change_field — изменяет поле в таблице БД.

Как видно из этого списка, практически все функции из файла /core/includes/database.inc являются устаревшими, кроме db_ignore_replica()[2].

Статические запросы

Статические запросы представлены двумя deprecated функциями: db_query()[3], db_query_range()[4]. Первая выполняет простые выборки, вторая — если необходимо указать диапазон выборки. С помощью данных функций рекомендуется выполнять простые запросы SELECT, которые не используют поиск по сущностям, их полям. Кроме того, есть существенное ограничение — синтаксис запроса может отличаться в различных типах баз данных, что в свою очередь негативно влияет на универсальность запроса.
Есть несколько важных замечаний, которым необходимо следовать:

  • помещать имена таблиц в фигурные скобки {}. Это необходимо для корректного распознавания таблицы друпалом, т.к. если используются префиксы в базе данных, то конечное имя таблицы может не совпадать с ожидаемой.
  • использовать плейсхолдеры вместо прямой подстановки переменных в запрос. Это позволит избежать SQL инъекций.

Примеры запроса с использованием deprecated функции

1
$items = db_query('SELECT * FROM {watchdog} WHERE type=:type', [':type' => 'cron'])->fetchAll();

Тот же запрос с использованием класса Database

1
$items = Database::getConnection()->query('SELECT * FROM {watchdog} WHERE type=:type', [':type' => 'cron'])->fetchAll();

Второй вариант, если необходимо выполнить запрос с параметром limit, например из предыдущего примера выберем первые 5 записей

1
$items = db_query_range('SELECT * FROM {watchdog} WHERE type=:type', 0, 5, [':type' => 'cron'])->fetchAll();

С использованием класса Database

1
$items = Database::getConnection()->queryRange('SELECT * FROM {watchdog} WHERE type=:type', 0, 5, [':type' => 'cron'])->fetchAll();

Работа с сущностями

Для работы с сущностями в БД есть специальный API — Entity Query API. API представлен следующими методами, с помощью которых следует выполнять запросы:

  • \Drupal::entityQuery() — статический метод, который возвращает entity query объект.
    В качестве примера, получим все ноды с типом article.

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

    После выполнения запроса в переменной $results будет массив с vid в качестве ключей и nid в качестве значений.
    Загрузка объектов нод можно выполнить с помощью статического метода loadMultiple() класса Node

    1
    2
    
    use Drupal\node\Entity\Node;
    $nodes = Node::loadMultiple($results);
  • \Drupal::entityQueryAggregate() — возвращает агрегированный объект для данного типа сущности. Пример агрегации комментариев по последней дате обновления

    1
    2
    3
    
    $query = \Drupal::entityQueryAggregate('comment');
    $query->aggregate('changed', 'MAX');
    $results = $query->execute();

    В результате получим массив
    результирующий массив агрегации

Динамические запросы

Динамические запросы позволяют абстрагироваться от конкретной базы данных. Т.е. запрос, написанный с помощью abstraction layer для работы с MySQL, прекрасно отработает и на PostgreSQL, и с другой БД, если Друпал имеет соответствующий драйвер. Даже если его нет, его всегда можно написать:)
Упрощенные функции для работы с БД (db_select, db_update, db_delete и т. д.) являются своего рода обертками и, как мы уже выяснили, будут удалены в Drupal 9.
Рассмотрим несколько примеров динамических запросов.
Выборка всех сообщений из таблицы watchdog с типом крон

1
2
3
4
$query = Database::getConnection()->select('watchdog', 'w');
$query->fields('w', ['message']);
$query->condition('w.type', 'cron');
$messages = $query->execute()->fetchAll();

Пример выборки данных из поискового индекса search_index модуля ядра Search с джойном на таблицу search_total

1
2
3
4
5
6
$query = Database::getConnection()->select('search_index', 'si');
$query->fields('si');
$query->join('search_total', 'st', 'st.word = si.word');
$query->fields('st', ['count']);
$query->range(0, 5);
$result = $query->execute()->fetchAll();

Транзакции

Для того, чтобы создать транзакцию, нужно использовать простую запись[5]

1
$transaction = Database::getConnection()->startTransaction();

Если по каким-либо причинам что-то пошло не так — вызываем ролбэк

1
$transaction->rollBack();

Пример записи в кастомную в таблицу example с транзакцией и конструкцией try catch.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use \Drupal\Core\Database\Database;
 
$connection = Database::getConnection();
$transaction = $connection->startTransaction();
try {
 $query = $connection->insert('example');
 $query->fields([
   'text',
   'uid',
 ]);
 $query->values([
   'text' => 'example item',
   'uid' => 1,
 ]);
 $query->execute();
}
catch (\Exception $e) {
 $transaction->rollBack();
 drupal_set_message($e->getMessage(), 'error');
}

Внешние ключи (foreign keys)

Внешние ключи по-прежнему не работают в Drupal 8. Использование их в схеме носит исключительно информативный характер[6].

1
2
3
4
5
6
7
8
9
// ...
// For documentation purposes only; foreign keys are not created in the
// database.
'foreign keys' => [
 'node_revision' => [
   'table' => 'node_field_revision',
   'columns' => ['vid' => 'vid'],
 ],
// ...

Если все-таки необходимо реализовать такую возможность, то рекомендую ознакомиться со статьей Разбираемся с FOREIGN KEYS в Друпале.

Хуки установки, деинсталляции, обновления

Привычные для нас хуки с седьмой версии доступны и в 8-ке:

  • hook_schema() — необходим для создания таблиц в БД, если таковые необходимы модулю.
  • hook_schema_alter() — альтер существующей схемы.
  • hook_install() — будет вызван при установке модуля.
  • hook_uninstall() — будет вызван при удалении модуля.
  • hook_update_N() — хук, используемый для обновления модуля, внесения изменений в таблицы БД, выполнения логики когда модуль уже работает. В соответствии с версией Друпала нумерация имеет префикс 8xxx
    1
    2
    3
    4
    
      MYMODULE_update_8001() {
        // ...
      }
      

Хуки hook_enable() и hook_disable() больше не существуют.

Домашнее задание

Ответ по прошлому заданию

Будет дополнено.

Задание по текущему уроку

Будет дополнено.

Дополнительная информация по статье

  1. https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Database!database.api.php/group/schemaapi/8.3.x - Schema API из официальной документации.
  2. https://api.drupal.org/api/drupal/core!includes!database.inc/function/db_ignore_replica/8.3.x - описание db_ignore_replica() из документации.
  3. https://api.drupal.org/api/drupal/core!includes!database.inc/function/db_query/8.3.x - описание db_query().
  4. https://api.drupal.org/api/drupal/core%21includes%21database.inc/function/db_query_range/8.3.x - описание db_query_range().
  5. https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Database!Transaction.php/class/Transaction/8.3.x - класс Transaction.
  6. Версии программных продуктов, используемых в статье: Drupal 8.3.4