Урок 8. Работа с базой данных
Опубликовано вт, 27/06/2017 - 15:58
Работа с базой данных — неотъемлемая часть разработки. Сегодня поговорим о том, что изменилось по этому поводу в восьмерке.
Содержание
- Устаревшие функции
- Статические запросы
- Работа с сущностями
- Динамические запросы
- Транзакции
- Внешние ключи (foreign keys)
- Хуки установки, деинсталляции, обновления
- Домашнее задание
Устаревшие функции
Нововведения коснулись функций для работы с базой данных (БД). Они все еще могут использоваться, но уже являются устаревшими и будут удалены в 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() класса Node1 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
function MYMODULE_update_8001() { // ... }
Хуки hook_enable() и hook_disable() больше не существуют.
Домашнее задание
Ответ по прошлому заданию
Будет дополнено.
Задание по текущему уроку
Будет дополнено.
Дополнительная информация по статье
- https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Database!database.api.php/group/schemaapi/8.3.x - Schema API из официальной документации.
- https://api.drupal.org/api/drupal/core!includes!database.inc/function/db_ignore_replica/8.3.x - описание db_ignore_replica() из документации.
- https://api.drupal.org/api/drupal/core!includes!database.inc/function/db_query/8.3.x - описание db_query().
- https://api.drupal.org/api/drupal/core%21includes%21database.inc/function/db_query_range/8.3.x - описание db_query_range().
- https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Database!Transaction.php/class/Transaction/8.3.x - класс Transaction.
- Версии программных продуктов, используемых в статье: Drupal 8.3.4