Перейти к содержимому

Своя таблица на ORM - сущность, запросы, изменение структуры

Заводим собственную таблицу под данные проекта: описываем сущность, создаём таблицу при установке модуля и разбираемся, как менять её потом.

Решение

Описываем сущность классом:

namespace Vendor\Shop;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\{IntegerField, StringField, DatetimeField};
class SyncLogTable extends DataManager
{
// имя таблицы задают один раз: переименование ломает весь код проекта
public static function getTableName(): string { return 'vendor_sync_log'; }
public static function getMap(): array
{
return [
new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
new IntegerField('ORDER_ID', ['required' => true]),
new StringField('STATUS', ['size' => 32]),
new DatetimeField('CREATED_AT'),
];
}
}

Класс сущности остаётся единственным источником правды о структуре. Имя таблицы, состав полей и их типы живут в коде, поэтому новый стенд получает ту же структуру, что и боевой сайт, без ручного вмешательства.

Создаём таблицу при установке модуля:

public function DoInstall() {
\Bitrix\Main\Loader::includeModule('main');
\Vendor\Shop\SyncLogTable::getEntity()->createDbTable();
// индексы добавляют отдельным запросом: сущность их не создаёт
$conn = \Bitrix\Main\Application::getConnection();
$conn->query('CREATE INDEX ix_sync_order ON vendor_sync_log (ORDER_ID)');
}

Таблицу создаёт установка модуля, а вовсе не человек своими руками. Индексы при этом добавляют отдельно: описание сущности их не создаёт, а без указателя по полю отбора таблица тормозит уже на сотне тысяч строк.

Пишем и читаем данные:

SyncLogTable::add(['ORDER_ID' => $orderId, 'STATUS' => 'sent',
'CREATED_AT' => new \Bitrix\Main\Type\DateTime()]);
$rows = SyncLogTable::getList([
'select' => ['ID', 'STATUS', 'CREATED_AT'],
'filter' => ['=ORDER_ID' => $orderId, '>CREATED_AT' => $since],
'order' => ['ID' => 'DESC'], 'limit' => 50,
])->fetchAll();

Запросы идут через методы сущности, а не строками с прямым обращением к базе. Выигрыш здесь не в красоте: связи, типы и экранирование значений платформа берёт на себя, а прямой запрос оставляет это автору.

Меняем структуру через обновление модуля:

// скрипт обновления: сначала база, потом описание сущности в коде
$conn->query('ALTER TABLE vendor_sync_log ADD COLUMN ATTEMPT INT DEFAULT 0');
// правка только getMap оставляет базу прежней и ломает запросы

Структуру меняют в двух местах сразу: в базе и в описании сущности. Правка одного описания даёт ошибку неизвестного поля, а правка одной базы - поле, о котором код не знает.

Старые записи из журналов и очередей чистят по расписанию. Таблица, в которую только пишут, растёт линейно и однажды становится самой большой в базе, а пользы от записей годичной давности обычно нет.

Имя таблицы стоит начинать с имени своего собственного решения. Тогда в базе видно, чьи это данные, а случайное совпадение имён с чужим модулем не приводит к разбору посреди рабочего дня.

Своя таблица не даёт интерфейса правки и прав доступа. Если данные нужно показывать и править людям, инфоблок или highload-блок обойдутся дешевле, чем собственная страница в административной части.

Типичные проблемы

На новом стенде таблицы нет.

Её создавали руками, а не классом установки своего модуля. Установка на чистой площадке о такой таблице попросту ничего не знает.

Запросы к таблице стали медленными.

Нет индекса по полю, которое участвует в отборе. Описание сущности индексы не создаёт, их добавляют отдельным запросом.

Ошибка о неизвестном поле после обновления.

Поле добавили в описание сущности, но не добавили в саму таблицу базы. Менять структуру нужно сразу в обоих местах.

Таблица разрослась до миллионов строк.

В неё только пишут и никогда ничего не чистят. Журналам и очередям задают разумный срок хранения записей.

Данные некому править.

У своей таблицы нет ни интерфейса, ни прав доступа. Для данных, которые ведут люди, берут инфоблок или highload-блок.

Частые вопросы

Чем это лучше highload-блока?

Своя таблица даёт полный контроль над структурой и индексами. Highload-блок даёт интерфейс и права из коробки.

Можно ли связать свою таблицу с элементами инфоблока?

Да, полем со ссылкой и описанием связи в сущности. Тогда выборка соберёт данные одним запросом.

Где хранить класс сущности?

В своём модуле, в каталоге классов. Тогда автозагрузка находит его на любом запросе.

Нужны ли миграции как отдельный механизм?

На проекте с несколькими стендами - да, иначе структура расходится. Скрипты обновления модуля закрывают ту же задачу.

Смежное

Первоисточник