Дата последнего изменения: 10.10.2024
При создании интернет-проектов на платформе Bitrix Framework доступен обширный функционал "из коробки", использовать который можно посредством вызовов API соответствующих модулей. При этом каждый модуль в концепции фреймворка является самостоятельной рабочей единицей, которая обеспечивает решение определенного круга задач.
Как правило, API каждого модуля разрабатывается исходя из специфики задач, и нередко формат вызовов отличается от модуля к модулю. Чтобы свести эти различия к минимуму базовый функционал, который присутствует практически в каждом модуле, стандартизирован. Это CRUD-операции: Create, Read, Update, Delete (создание, чтение, обновление и удаление данных).
Например, сущность Пользователь - это множество пользователей с набором полей:
При этом ID автоматически выдается базой данных, Имя и Фамилия ограничены длиной 50 символов, Логин должен состоять только из латинских букв, цифр и знака подчеркивания и так далее.
Вместо программирования каждой такой сущности, мы бы хотели описывать ее в определенном формате. Такое описание обрабатывалось бы ядром системы, являлось для него своего рода конфигурацией:
Book ID int [autoincrement, primary] ISBN str [match: /[0-9X-]+/] TITLE str [max_length: 50] PUBLISH_DATE date
Например, похожим образом можно описать каталог книг, в котором система сама будет следить за корректностью и целостностью данных: проверять формат и вхождение в диапазон допустимых значений.
Для конфигурации сущностей не используются средства разметки (xml, yml и т.п.), вместо этого используется php. Такой вариант дает максимум возможностей развития и гибкости.
Так выглядит определение типов данных из приведенного выше примера:
namespace SomePartner\MyBooksCatalog; use Bitrix\Main\Entity; class BookTable extends Entity\DataManager { public static function getTableName() { return 'my_book'; } public static function getMap() { return array( new Entity\IntegerField('ID'), new Entity\StringField('ISBN'), new Entity\StringField('TITLE'), new Entity\DateField('PUBLISH_DATE') ); } }
За описание структуры сущности отвечает метод getMap(), который возвращает массив экземплярами полей.
Каждый тип поля представлен в виде класса-наследника Entity\ScalarField - эти поля работают с простыми скалярными значениями, которые сохраняются в базу данных "как есть". По умолчанию доступно 8 таких типов:
В рамках соблюдения стандартов кодирования рекомендуется называть поля в верхнем регистре. Имена должны быть уникальными в рамках одной сущности.
Как правило, в конструкторе поля первым параметром передается имя поля, а вторым параметром - дополнительные настройки. Общие настройки будут рассмотрены далее в этой главе, но есть и специфическая настройка для BooleanField и EnumField:
new Entity\BooleanField('NAME', array( 'values' => array('N', 'Y') )) new Entity\EnumField('NAME', array( 'values' => array('VALUE1', 'VALUE2', 'VALUE3') ))
Для BooleanField, поскольку true и false не могут храниться в таком виде в БД, задается маппинг значений в виде массива, где первый элемент заменяет при хранении false, а второй true.
В большинстве случаев у сущности есть первичный ключ по одному полю. Он же, как правило, является автоинкрементным. Чтобы рассказать об этом сущности, необходимо воспользоваться параметрами в конструкторе поля:
new Entity\IntegerField('ID', array( 'primary' => true ))
Так заявляется о принадлежности поля к первичному ключу. Благодаря этой опции, сущность будет контролировать вставку данных и не даст добавить запись без указания значения для первичного ключа. При обновлении и удалении записей их можно будет идентифицировать только по первичному ключу.
Часто не указывается явно значение ID, а оно получается из базы данных уже после успешного добавления записи. В таком случае нужно сообщить об этом сущности:
new Entity\IntegerField('ID', array( 'primary' => true, 'autocomplete' => true ))
Флаг 'autocomplete', для сущности означает, что при добавлении новой записи не нужно требовать от разработчика установки значения для данного поля. По умолчанию, такое требование применяется только к полям из первичного ключа, но можно попросить систему требовать установку и любого другого поля:
new Entity\StringField('ISBN', array( 'required' => true ))
Теперь нельзя будет добавить новую книгу, не указав ее ISBN код.
При описании сущности для уже имеющейся таблицы может возникнуть желание по-другому назвать колонку. Например, изначально в таблице `my_book` поле ISBN называлось как ISBNCODE, и старый код использует это название колонки в SQL запросах. Если в новом API необходимо оптимизировать название до более читаемого ISBN, то в этом поможет параметр 'column_name':
new Entity\StringField('ISBN', array( 'required' => true, 'column_name' => 'ISBNCODE' ))
Бывают и другие случаи, когда в одной физической колонке в таблице хранятся разные по смыслу значения. В таком случае можно создать несколько полей сущности, у которых будет одинаковый 'column_name'.
Предусмотрено не только хранение данных как есть, но и их преобразование при выборке. Допустим, возникла потребность наравне с датой издания сразу же получать возраст книги в днях. Хранить это число в БД накладно: придется каждый день пересчитывать обновлять данные. Можно просто считать возраст на стороне базы данных:
SELECT DATEDIFF(NOW(), PUBLISH_DATE) AS AGE_DAYS FROM my_book
Для этого нужно описать в сущности виртуальное поле, значение которого базируется на SQL-выражении с другим полем или полями:
new Entity\ExpressionField('AGE_DAYS', 'DATEDIFF(NOW(), %s)', array('PUBLISH_DATE') )
Первым параметром, как и у остальных полей, задается имя. Вторым параметром нужно передать текст SQL выражения, но при этом другие поля сущности нужно заменить на плейсхолдеры согласно формату sprintf. Третьим параметром нужно передать массив с именами полей сущности в определенном порядке, который был задан в выражении.
(FIELD_X + FIELD_Y) * FIELD_X
, то выражение можно описать так: '(%s + %s) * %s', [FIELD_X, FIELD_Y, FIELD_X];
или так: '(%1$s + %2$s) * %1$s', [FIELD_X, FIELD_Y]
.Очень часто выражения могут применяться для агрегации данных (например, COUNT(*) или SUM(FIELD)), такие примеры будут рассмотрены в главе Выборка данных.
Помимо полей ScalarField и ExpressionField, сущность может содержать Пользовательские поля. Они конфигурируются через Административный интерфейс и не требуют дополнительного описания на стороне сущности. Все, что требуется указать в сущности, это выбранный Объект пользовательского поля:
class BookTable extends Entity\DataManager { ... public static function getUfId() { return 'MY_BOOK'; } ... }
В дальнейшем именно этот идентификатор нужно указывать при прикреплении пользовательских полей к сущности:
Таким образом, можно выбирать и обновлять значения пользовательских полей наравне со значениями штатных полей сущности.
С версии 24.100.0 главного модуля появилась возможность запрещать кеширование в таблетах ORM.
Чтобы запретить кеширование добавьте в описание таблицы:
public static function isCacheable(): bool { return false; }
По результатам данной главы получена следующая сущность:
namespace SomePartner\MyBooksCatalog; use Bitrix\Main\Entity; class BookTable extends Entity\DataManager { public static function getTableName() { return 'my_book'; } public static function getUfId() { return 'MY_BOOK'; } public static function getMap() { return array( new Entity\IntegerField('ID', array( 'primary' => true, 'autocomplete' => true )), new Entity\StringField('ISBN', array( 'required' => true, 'column_name' => 'ISBNCODE' )), new Entity\StringField('TITLE'), new Entity\DateField('PUBLISH_DATE') ); } } // код для создания таблицы в MySQL // (получен путем вызова BookTable::getEntity()->compileDbTableStructureDump()) CREATE TABLE `my_book` ( `ID` int NOT NULL AUTO_INCREMENT, `ISBNCODE` varchar(255) NOT NULL, `TITLE` varchar(255) NOT NULL, `PUBLISH_DATE` date NOT NULL, PRIMARY KEY(`ID`) );
Таким образом можно описать в сущности обычные скалярные поля, выделить из них первичный ключ, указать автоинкрементные поля и какие поля должны быть обязательно заполнены. При расхождении имени колонки в таблице и желаемого имени в сущности будет возможность уладить этот момент.
Осталось только зафиксировать код сущности в проекте. Согласно общим правилам именования файлов в D7, код сущности нужно сохранить в файле: local/modules/somepartner.mybookscatalog/lib/book.php
После чего система автоматически будет подключать файл при нахождении вызовов класса BookTable.
'ID' => array( 'data_type' => 'integer', 'primary' => true, 'autocomplete' => true, ),оставлена для совместимости. При инициализации все равно создаются объекты классов
\Bitrix\Main\Entity\*
. Использовать можно оба варианта, правильней - через объекты.