Модуль стоит, а логи заказов все ещё пишутся сырым SQL или тащатся в инфоблок. MyModule\LogTable::getList() падает с "Class ...Table not found", в phpMyAdmin таблицы нет - хотя install.php прошёл чисто. Обычно виноваты: класс без суффикса Table, createDbTable без isTableExists или забытый Loader::includeModule. Ниже - полный цикл своей ORM-таблицы в 1С-Битрикс: Управление сайтом через DataManager, getMap и createDbTable.
Своя *Table в lib/ модуля - это когда данные служебные: логи, очередь, счётчики. Класс наследует DataManager, имя заканчивается на Table, getMap описывает поля, createDbTable() вызывается в InstallDB с проверкой isTableExists. Перед CRUD - Loader::includeModule. Результат: таблица в БД, add/getList работают без сырого SQL, uninstall удаляет таблицу через dropTable.
Речь про коробочную CMS, не про облачный Bitrix24. ORM (Object-Relational Mapping) - слой D7, который связывает PHP-класс с таблицей MySQL: поля в getMap(), чтение и запись через getList, add, update, delete. Каркас модуля ещё не собран - начните с гайда по созданию модуля, здесь шаг после include.php.
Выберите, когда нужна своя ORM-таблица, а когда хватит инфоблока или Highload
DataManager в lib/ - не замена всему подряд. Это узкий инструмент для служебных данных модуля, которые не должны жить в админке как контент.
| Задача | Инфоблок D7 (B60) | Highload (B26) | Своя *Table в модуле |
|---|---|---|---|
| Новости, каталог, SEO-страницы | Да - ElementTable, админка | Нет | Нет - лишний вес |
| Справочник 50 000 SKU | Медленно | Да - отдельная таблица HL | Редко - HL проще |
| Лог API-запросов модуля | Избыточно | Избыточно | Да - LogTable в lib/ |
| Очередь фоновых задач | Нет | Иногда | Да - полный контроль схемы |
Выборка инфоблока - в сравнении GetList и D7 ORM, справочники - в гайде по Highload. Своя таблица - когда данные только вашего модуля, без редактора в админке.
Делайте: храните служебные записи в *Table. Не делайте: не создавайте инфоблок ради десятка строк лога - админка и кеш усложнят поддержку.
Создайте класс LogTable в lib/ с getTableName и getMap
Наследник DataManager живёт в lib/. Имя класса заканчивается на Table - правило ядра. getTableName() - имя таблицы в MySQL, getMap() - поля (IntegerField, StringField и др.).
- Создайте файл /local/modules/mycompany.custommodule/lib/LogTable.php.
- Задайте namespace Mycompany\Custommodule и extends \Bitrix\Main\Entity\DataManager.
- Реализуйте getTableName() - например, return 'mycompany_custommodule_log';
- Опишите getMap(): ID (IntegerField, primary, autoincrement), TIMESTAMP_X (DatetimeField), LEVEL (StringField), MESSAGE (TextField).
- Зарегистрируйте класс в include.php через Loader::registerAutoLoadClasses с полным именем Mycompany\Custommodule\LogTable.
- Проверьте, что имя класса в автозагрузке совпадает с файлом - не Log вместо LogTable.
<?php
namespace Mycompany\Custommodule;
use Bitrix\Main\Entity\DataManager;
use Bitrix\Main\Entity\IntegerField;
use Bitrix\Main\Entity\StringField;
use Bitrix\Main\Entity\TextField;
use Bitrix\Main\Entity\DatetimeField;
class LogTable extends DataManager
{
public static function getTableName()
{
return 'mycompany_custommodule_log';
}
public static function getMap()
{
return [
new IntegerField('ID', [
'primary' => true,
'autocomplete' => true,
]),
new DatetimeField('TIMESTAMP_X', [
'default_value' => function () {
return new \Bitrix\Main\Type\DateTime();
},
]),
new StringField('LEVEL', ['required' => true]),
new TextField('MESSAGE'),
];
}
}
Схема: lib/LogTable.php → getTableName + getMap → registerAutoLoadClasses в include.php → класс доступен после includeModule.
Делайте: держите поля в getMap, а не в ручном CREATE TABLE. Не делайте: не называйте класс Log без суффикса Table - ядро не распознает сущность.
Настройте createDbTable в InstallDB с проверкой isTableExists
createDbTable() создаёт таблицу по getMap() при установке. Не меняет схему существующей таблицы и не добавляет индексы. Без isTableExists при повторной установке - "table already exists".
public function InstallDB()
{
if (!\Bitrix\Main\Loader::includeModule('mycompany.custommodule')) {
return false;
}
$connection = \Bitrix\Main\Application::getConnection();
$tableName = \Mycompany\Custommodule\LogTable::getTableName();
if (!$connection->isTableExists($tableName)) {
\Mycompany\Custommodule\LogTable::getEntity()->createDbTable();
}
return true;
}
public function UnInstallDB()
{
$connection = \Bitrix\Main\Application::getConnection();
$tableName = \Mycompany\Custommodule\LogTable::getTableName();
if ($connection->isTableExists($tableName)) {
$connection->dropTable($tableName);
}
return true;
}
Движок берётся из default-storage-engine MySQL - часто MyISAM. Нужен InnoDB - ALTER после создания или настройка сервера по рекомендациям 1С-Битрикс. Изменили getMap - колонки через Sprint Migration или ALTER, не createDbTable.
Делайте: вызывайте createDbTable только при !isTableExists. Не делайте: не ждите, что правка getMap автоматически обновит БД.
Выполните CRUD: add, getList, update и delete после includeModule
На практике любой вызов LogTable::getList() из компонента или агента требует предварительного Loader::includeModule('mycompany.custommodule'). Без этого - "Class Mycompany\Custommodule\LogTable not found", даже если таблица в БД уже есть.
\Bitrix\Main\Loader::includeModule('mycompany.custommodule');
$result = \Mycompany\Custommodule\LogTable::add([
'LEVEL' => 'error',
'MESSAGE' => 'Payment callback timeout',
]);
if (!$result->isSuccess()) {
// $result->getErrorMessages()
}
$rows = \Mycompany\Custommodule\LogTable::getList([
'filter' => ['=LEVEL' => 'error'],
'order' => ['TIMESTAMP_X' => 'DESC'],
'limit' => 50,
'select' => ['ID', 'TIMESTAMP_X', 'LEVEL', 'MESSAGE'],
])->fetchAll();
Workflow: includeModule → add с isSuccess() → getList с filter/order/limit → update/delete по ID. Отладка SQL - getLastQuery(), см. справочник DataManager.
Делайте: проверяйте isSuccess() после add и update. Не делайте: не вызывайте *Table до includeModule - типичная ловушка после успешной установки.
Проверьте чек-лист и типичные ошибки перед выкладкой на prod
После установки модуля на staging пройдите короткий smoke-test - он сэкономит часы на проде.
- Установите модуль в админке - таблица появилась в phpMyAdmin с полями из getMap.
- Добавьте тестовую запись через LogTable::add() - ID вернулся, isSuccess() true.
- Выберите строки через getList с фильтром - данные совпадают с add.
- Удалите модуль - таблица исчезла, в b_module записи нет.
- Переустановите модуль - нет ошибки "table exists", CRUD снова работает.
- Откройте getLastQuery() после getList - SQL совпадает с ожидаемым filter/order.
Типичная ошибка на практике — Log вместо LogTable в автозагрузке; вторая ловушка: авто-миграция getMap не сработает; третья — MyISAM вместо InnoDB. Генератор perfmon (?orm=y) ускоряет старт - урок по автогенерации ORM.
Делайте: фиксируйте схему в git и прогоняйте uninstall на копии БД. Не делайте: не правьте таблицу только в phpMyAdmin на проде без миграции.
Что дальше
Дальше: ReferenceField, сервис в lib/Service/, агент очистки логов, миграции схемы между окружениями. Помощь с архитектурой модуля - обсудим задачу, кейсы - в портфолио.
Автор: Максим Мольков, Senior-разработчик 1С-Битрикс.
Источники: концепция ORM Bitrix Framework, справочник DataManager D7, Base::createDbTable.
Частые вопросы
Чем своя ORM-таблица отличается от инфоблока и Highload?
Инфоблок - для контента с админкой и SEO. Highload - для больших справочников через UI. Своя *Table в lib/ - для служебных данных модуля: логи, очереди, счётчики. Выбор: контент - инфоблок, миллионы строк справочника - HL, внутренние данные модуля - DataManager.
Где положить класс Table в структуре модуля?
В /local/modules/vendor.module/lib/, например lib/LogTable.php. Namespace совпадает с вендором модуля. Зарегистрируйте путь в include.php через Loader::registerAutoLoadClasses - полное имя класса с суффиксом Table.
Как создать таблицу при установке модуля без ручного SQL?
В InstallDB вызовите Loader::includeModule, проверьте isTableExists по getTableName(), затем LogTable::getEntity()->createDbTable(). В UnInstallDB - connection->dropTable с той же проверкой.
Зачем getMap, если можно писать SQL напрямую?
getMap даёт типизацию полей, единый CRUD через add/getList и защиту от расхождения PHP и БД. Сырой SQL быстрее на бумаге, но без карты полей легко ошибиться в именах колонок и типах при рефакторинге.
Почему createDbTable создаёт MyISAM, а не InnoDB?
ORM берёт default-storage-engine MySQL сервера. Проверьте SHOW VARIABLES LIKE 'default_storage_engine'. После createDbTable выполните ALTER TABLE ... ENGINE=InnoDB или настройте сервер до установки модуля.
Как изменить схему таблицы после первого релиза?
createDbTable не ALTER-ит существующую таблицу. Добавьте колонку через Connection::queryExecute с ALTER или оформите миграцию в Sprint Migration - так схема доедет на staging и prod без ручных правок.
Class LogTable not found - что проверить первым?
Три пункта: Loader::includeModule('vendor.module') перед вызовом; в registerAutoLoadClasses полное имя с Table; файл лежит в lib/ и namespace совпадает. Таблица в БД есть, а класс не найден - почти всегда автозагрузка, не install.