База данных
С материалами и пользователями работают через сущности. Но для собственных простых данных — подписчиков, оценок, журнала — удобнее своя таблица и прямые запросы. Drupal даёт для этого построитель запросов: он сам экранирует значения и одинаково работает с MySQL, PostgreSQL и SQLite.
Своя таблица
Структуру таблиц модуля описывает функция blog_schema() в файле blog.install в корне папки модуля. Создайте его вручную:
// web/modules/custom/blog/blog.install
/**
* Implements hook_schema().
*/
function blog_schema(): array
{
return [
'blog_subscribers' => [
'description' => 'Подписчики блога.',
'fields' => [
'id' => ['type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE],
'email' => ['type' => 'varchar', 'length' => 255, 'not null' => TRUE],
'created' => ['type' => 'int', 'not null' => TRUE, 'default' => 0],
],
'primary key' => ['id'],
'unique keys' => ['email' => ['email']],
],
];
}В настоящем файле первой строкой идёт <?php. serial — число с автоматическим увеличением, varchar — строка, int — целое. Ключ unique keys запрещает два одинаковых адреса.
Таблицы создаются в момент включения модуля. Модуль уже включён, поэтому включите его заново:
vendor/bin/drush pmu blog -y
vendor/bin/drush en blog -yВ базе появится таблица blog_subscribers. Как менять структуру без переустановки, рассказано в уроке об обновлениях.
Вставка
Подключение к базе — сервис database. Передайте его в форму подписки и сохраните адрес:
// web/modules/custom/blog/src/Form/SubscribeForm.php
use Drupal\Core\Database\Connection;
use Symfony\Component\DependencyInjection\ContainerInterface;
public function __construct(private Connection $database) {}
public static function create(ContainerInterface $container): static
{
return new static($container->get('database'));
}
public function submitForm(array &$form, FormStateInterface $form_state): void
{
$this->database->insert('blog_subscribers')
->fields([
'email' => $form_state->getValue('email'),
'created' => time(),
])
->execute();
$this->messenger()->addStatus($this->t('Вы подписаны.'));
}После drush cr отправьте форму /blog/subscribe — в таблице появится строка. fields() принимает массив «столбец — значение», execute() выполняет запрос.
Выборка
// web/modules/custom/blog/src/Controller/BlogController.php
$rows = \Drupal::database()->select('blog_subscribers', 's')
->fields('s', ['email', 'created'])
->orderBy('created', 'DESC')
->range(0, 20)
->execute()
->fetchAll();
foreach ($rows as $row) {
$emails[] = $row->email;
}Второй аргумент select() — короткое имя таблицы внутри запроса. fetchAll() возвращает массив объектов, столбцы — их свойства. В классах подключение передают через конструктор, как в форме выше; \Drupal::database() здесь записан для краткости.
| Метод результата | Что возвращает |
|---|---|
fetchAll() | Все строки объектами |
fetchAssoc() | Одну строку массивом |
fetchField() | Одно значение из первой строки |
fetchCol() | Первый столбец всех строк |
fetchAllKeyed() | Массив «первый столбец — второй столбец» |
Условия
// web/modules/custom/blog/src/Form/SubscribeForm.php
$exists = $this->database->select('blog_subscribers', 's')
->condition('s.email', $email)
->countQuery()
->execute()
->fetchField();condition() работает как в запросах к сущностям: столбец, значение, необязательный оператор. countQuery() превращает запрос в подсчёт строк. Такую проверку помещают в validateForm(), чтобы сообщить, что адрес уже подписан.
Изменение и удаление
// в любом классе с подключением к базе
$this->database->update('blog_subscribers')
->fields(['email' => 'anna@example.org'])
->condition('id', 5)
->execute();
$this->database->delete('blog_subscribers')
->condition('created', strtotime('-2 years'), '<')
->execute();Первый запрос меняет адрес в строке с номером 5, второй удаляет подписки старше двух лет. Оба возвращают число затронутых строк.
Частая ошибка
Не читайте и не меняйте прямыми запросами таблицы материалов и пользователей — node_field_data, users_field_data и подобные. Запись в обход Entity API не вызывает хуки, не сбрасывает кеш и не учитывает права и переводы: на сайте останутся старые данные. Прямые запросы — только для собственных таблиц модуля.