Понятие сущность и работа с сущностью в ORM Битрикс
Сущность — это таблица в базе данных. Каждая сущность состоит из полей, которые соответствуют колонкам таблицы. Например, сущность «Пользователь» может включать поля: ID, Имя, Фамилия, Пароль, Логин.
Вместо программирования каждой сущности их описывают в формате, который ядро системы воспринимает как конфигурацию.
Пример для каталога книг:
Book
// ID уникальный идентификатор книги, который генерируется автоматически
ID int [autoincrement, primary]
// ISBN строка, которая должна соответствовать определенному формату
ISBN str [match: /[0-9X-]+/]
// TITLE название книги, ограниченное 50 символами
TITLE str [max_length: 50]
// PUBLISH_DATE дата публикации
PUBLISH_DATE date
Система автоматически проверяет корректность и целостность данных на основе описанных правил.
Типизация полей
Для описания сущностей используется PHP. Это обеспечивает гибкость и позволяет использовать возможности языка для настройки сущностей.
Пример определения типов данных:
namespace SomePartner\MyBooksCatalog;
use Bitrix\Main\Entity;
class BookTable extends Entity\DataManager
{
public static function getTableName(): string
{
return 'my_book';
}
public static function getMap(): array
{
return [
new Entity\IntegerField('ID'),
new Entity\StringField('ISBN'),
new Entity\StringField('TITLE'),
new Entity\DateField('PUBLISH_DATE')
];
}
}
- Класс
BookTableописывает сущность «Книга» - Имя класса сущности должно заканчиваться на
Table. Это требование фреймворка для корректной работы ORM. Основное имя Book в этом пространстве имен зарезервировано для будущего использования, чтобы представлять элементы сущности как объекты, а не массивы - Имя таблицы можно задать в методе
getTableName, например,my_book. Если метод не определен, имя таблицы формируется автоматически из неймспейса и названия класса, например,b_somepartner_mybookscatalog_book - Каждое поле, это объект класса, который наследуется от
Entity\ScalarField.IntegerFieldдля целых чисел,StringFieldдля строк,DateFieldдля дат - Метод
getMap()описывает структуру сущности, возвращая массив полей. Каждое поле, это класс-наследникEntity\ScalarField, работающий с простыми значениями, которые сохраняются в базу данных без изменений
Типы полей
Поля рекомендуется называть в верхнем регистре. Они должны быть уникальными в рамках сущности. В конструкторе поля первым параметром идет имя, вторым — настройки.
Типы данных, которые встречаются в битрикс:
- Целое число
IntegerField, подробнее - Строка с фиксированной длиной
StringField, подробнее - Текстовое поле переменной длины
TextField, подробнее - Число с плавающей запятой
FloatField, подробнее - Логическое значение (true/false)
BooleanField, подробнее - Перечисление, представляющееся как список значений
EnumField, подробнее - Дата и время
DatetimeField, подробнее - Только дата
DateField, подробнее - Только время
TimeField - Ссылка на файл или изображение
FileField - Ссылка на другую таблицу
ReferenceField
Атрибуты при создании кастомных таблиц:
- Определяет поле как первичный ключ
primary, подробнее - Автоматическое заполнение значения поля при добавлении записи
autocomplete, подробнее - Требует обязательного заполнения поля
required, подробнее - Задает значение по умолчанию для поля
default_value, подробнее
IntegerField поле для хранения целых чисел
Конструктор:
new Entity\IntegerField('NAME', [
// размер поля в битах (необязательный параметр)
'size' => 32,
]);
Методы настройки:
configureSize($size)задает размер поля в битах.
FloatField поле для хранения чисел с плавающей точкой
Конструктор:
new Entity\FloatField('NAME', [
// общая точность числа (необязательный параметр)
'precision' => 10,
// количество знаков после запятой (необязательный параметр)
'scale' => 2,
]);
Методы настройки:
configurePrecision($precision)задает общую точность числаconfigureScale($scale)задает количество знаков после запятой
DecimalField поле для хранения чисел с фиксированной точностью
Конструктор:
new Entity\DecimalField('NAME', [
// Общая точность числа (необязательный параметр)
'precision' => 10,
// Количество знаков после запятой (необязательный параметр)
'scale' => 2,
]);
Методы настройки:
configurePrecision($precision)задает общую точность числаconfigureScale($scale)адает количество знаков после запятой
StringField поле для хранения строк
Конструктор:
new Entity\StringField('NAME', [
// Регулярное выражение для валидации (необязательный параметр)
'format' => '/^[a-zA-Z]+$/',
// Максимальная длина строки (необязательный параметр)
'size' => 255,
]);
Методы настройки:
configureFormat($format)задает регулярное выражение для валидацииconfigureSize($size)задает максимальную длину строки
TextField поле для хранения текста большой длины
Конструктор:
new Entity\TextField('NAME', [
// Указывает, что это поле является длинным текстом (по умолчанию false)
'long' => true,
]);
Методы настройки:
configureLong($long)указывает, что поле предназначено для хранения длинного текста
DateField поле для хранения даты
Конструктор:
new Entity\DateField('NAME', [
// Формат даты (необязательный параметр)
'format' => 'Y-m-d',
]);
Методы настройки:
configureFormat($format)задает формат даты
DateTimeField поле для хранения даты и времени
Конструктор:
new Entity\DateTimeField('NAME', [
// Использовать временные зоны (по умолчанию true)
'useTimezone' => true,
]);
Методы настройки:
configureUseTimezone($use)включает или отключает использование временных зонconfigureDefaultValueNow()устанавливает значение по умолчанию как текущую дату и время
BooleanField поле для хранения логических значений
Конструктор:
new Entity\BooleanField('NAME', [
// Маппинг значений (N — false, Y — true)
'values' => ['N', 'Y'],
]);
Методы настройки:
configureStorageValues($falseValue, $trueValue)задает маппинг значений для хранения в БДbooleanizeValue($value)преобразует любое значение в строгое логическое
EnumField поле для хранения значения из предопределенного списка
Конструктор:
new Entity\EnumField('NAME', [
// Список допустимых значений
'values' => ['VALUE1', 'VALUE2', 'VALUE3'],
]);
Методы настройки:
configureValues($values)задает список допустимых значений
ArrayField поле для хранения массивов данных
Конструктор:
new Entity\ArrayField('NAME', [
// Тип сериализации (json или php)
'serializationType' => 'json',
]);
Методы настройки:
configureSerializationJson()использует JSON для сериализацииconfigureSerializationPhp()использует PHP для сериализацииconfigureSerializeCallback($callback)задает пользовательский метод сериализацииconfigureUnserializeCallback($callback)задает пользовательский метод десериализации
CryptoField— поле для хранения зашифрованных данных, которые есть необходимость расшифровывать, например, токены
Конструктор:
new Entity\CryptoField('NAME', [
// Включить шифрование (по умолчанию true)
'crypto_enabled' => true,
// Ключ шифрования
'crypto_key' => 'mysecretkey',
]);
Методы настройки:
encrypt($data)шифрует данные перед сохранениемdecrypt($data)расшифровывает данные при чтенииgetDefaultKey()получает ключ шифрования из настроек системы
SecretField поле для хранения зашифрованные данные, которые не должны быть доступны в открытом виде, например пароль
Конструктор:
new Entity\SecretField('NAME', [
// Длина секрета
'secret_length' => 16,
]);
Методы настройки:
configureSecretLength($length)задает длину секретаgetRandomBytes()генерирует случайные байты для секрета
Первичный ключ Primary Key
Первичный ключ (Primary Key) — это уникальный идентификатор записи в таблице. Обычно это поле с автоинкрементом, которое автоматически увеличивается при добавлении новой записи
В большинстве случаев у сущности есть первичный ключ по одному полю. Указав поле как первичный ключ primary, сущность контролирует вставку данных, не позволяя добавить запись без значения ключа. При обновлении и удалении записи идентифицируются по первичному ключу.
Если значение поля не задается явно, а генерируется базой данных после добавления записи, укажите параметр autocomplete.
new Entity\IntegerField('ID', [
// Поле является первичным ключом
'primary' => true,
// Значение генерируется автоматически
'autocomplete' => true
]);
Обязательное поле
Любое поле можно сделать обязательным. Например, чтобы нельзя было добавить книгу без указания ISBN, укажите параметр required.
new Entity\StringField('ISBN', [
'required' => true
]);
Разрешить значение NULL
Для полей можно установить разрешать или запрещать NULL значения. За это отвечает параметр nullable.
new Entity\StringField('TITLE', [
'nullable' => true
]);
Значение по умолчанию
Для полей сущностей можно задать значения по умолчанию. Они применяются, если значение для этих полей не указано при добавлении новой записи. Для этого используется параметр default_value.
Параметр default_value может принимать любое значение, которое можно вызвать: имя функции, массив с классом и методом, или анонимную функцию. Это позволяет гибко задавать значения по умолчанию, включая те, которые вычисляются динамически.
Рассмотрим пример с каталогом книг, где в поле Дата публикации по умолчанию устанавливается текущий день.
new Entity\DateField('PUBLISH_DATE', [
// Устанавливаем для поля значение по умолчанию — текущая дата
'default_value' => new Type\Date()
]);
Теперь при вставке в таблицу записи, если не указать дату издания, она будет равна текущему дню.
$result = BookTable::add([
'ISBN' => '978-0321127426', // Указываем ISBN книги
'TITLE' => 'Some new book' // Указываем название книги
]);
Маппинг имени колонки
Если вы хотите изменить название колонки в таблице, используйте параметр column_name. Например, в таблице my_book поле ISBN изначально называлось ISBNCODE и старый код использует это название в SQL-запросах. Чтобы в новом API использовать название ISBN, примените column_name.
new Entity\StringField('ISBN', [
'required' => true,
'column_name' => 'ISBNCODE'
]);
Выражения ExpressionField
Данные можно не только хранить, но и преобразовывать при выборке. Например, чтобы вместе с датой издания получать возраст книги в днях, не нужно хранить это число в БД и обновлять его ежедневно. Можно вычислить возраст на стороне базы данных.
SELECT DATEDIFF(NOW(), PUBLISH_DATE) AS AGE_DAYS FROM my_book
Здесь DATEDIFF(NOW(), PUBLISH_DATE) SQL-выражение, которое вычисляет разницу между текущей датой и датой публикации. В сущности создается виртуальное поле, основанное на SQL-выражении.
new Entity\ExpressionField('AGE_DAYS',
'DATEDIFF(NOW(), %s)', ['PUBLISH_DATE']
);
Первый параметр, это имя поля, второй параметр, это SQL-выражение с плейсхолдерами вместо полей, третий параметр, это массив имен полей в порядке, указанном в выражении. Плейсхолдер %s заменяется на значение поля PUBLISH_DATE.
Используйте плейсхолдеры %s или %1$s, %2$s и так далее, подставляя значения в определенные позиции:
%sпоследовательно подставляет значения в том порядке, в котором они переданы%1$sи%2$sявно указывают порядок подстановки значений
Например, когда в выражении EXPR участвует несколько полей:
(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]
Выражения ExpressionField используются только при выборке данных для фильтрации, группировки и сортировки. Поскольку физически таких колонок в таблице нет, записать значение поля невозможно, и система выдаст исключение.
Пользовательские поля
Пользовательские поля настраиваются через административный интерфейс и не требуют описания в сущности. Для их использования нужно указать идентификатор сущности.
class BookTable extends Entity\DataManager
{
public static function getUfId()
{
return 'MY_BOOK';
}
}
Метод getUfId() возвращает уникальный идентификатор 'MY_BOOK', который используется для прикрепления пользовательских полей к сущности.
С версии 20.5.200 главного модуля, добавлена поддержка SqlExpression для операций с сущностями пользовательских полей в ORM. Таким образом, можно выбирать и обновлять значения пользовательских полей так же, как и значения стандартных полей сущности.
Кеширование
Кеширование ускоряет работу с данными, но в некоторых случаях его нужно отключать. Например, если данные часто изменяются.
С версии 24.100.0 Главного модуля появилась возможность отключать кеширование в ORM-таблицах.
Чтобы отключить кеширование, добавьте в описание таблицы код:
public static function isCacheable(): bool
{
return false;
}
Валидаторы
Валидаторы используются для проверки данных перед их записью в базу данных. Они помогают убедиться, что данные соответствуют правилам и форматам.
Валидация задается параметром validation в конструкторе поля и представляет собой функцию callback, которая возвращает массив валидаторов. Валидаторы запускаются только тогда, когда действительно необходимо проверить данные, например, перед их сохранением в базу данных. При выборке данных из базы данных валидация не требуется, так как данные уже считаются корректными.
В качестве валидатора можно использовать наследника класса Entity\Validator\Base или любой callable. Объект callable должен вернуть true, текст ошибки или объект Entity\FieldError для собственного кода ошибки.
Валидаторы работают при добавлении и обновлении записей. Для проверки данных только при добавлении или обновлении используйте механизм событий.
Стандартные валидаторы
DateValidato проверяет, является ли значение корректной датой:
(new DateField('CREATED_AT', [
'required' => true,
]))->addValidator(new DateValidator());
BooleanValidator проверяет, является ли значение допустимым для поля типа boolean:
(new BooleanField('IS_ACTIVE', [
'required' => true,
]))->addValidator(new BooleanValidator());
DateValidator проверяет, является ли значение корректной датой:
(new DateField('CREATED_AT', [
'required' => true,
]))->addValidator(new DateValidator());
EnumValidator проверяет, находится ли значение в списке допустимых значений для перечисления:
(new EnumField('STATUS', [
'required' => true,
'values' => ['NEW', 'IN_PROGRESS', 'COMPLETED'],
]))->addValidator(new EnumValidator());
ForeignValidator проверяет, существует ли значение в связанной таблице (внешний ключ):
(new IntegerField('GROUP_ID', [
'required' => true,
]))->addValidator(new Foreign(GroupTable::getEntity()->getField('ID')));
LengthValidator проверяет длину строкового значения:
(new StringField('NAME', [
'required' => true,
]))->addValidator(new Length(null, 255));
RangeValidator проверяет, находится ли числовое значение в заданном диапазоне:
(new IntegerField('AGE', [
'required' => true,
]))->addValidator(new Range(18, 100));
RegExpValidator проверяет, соответствует ли значение регулярному выражению:
(new StringField('EMAIL', [
'required' => true,
]))->addValidator(new RegExp('/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/'));
UniqueValidator проверяет, является ли значение уникальным:
(new StringField('UNIQUE_CODE', [
'required' => true,
]))->addValidator(new Unique());
Эти валидаторы нельзя применять к пользовательским полям — проверка значений пользовательских полей настраивается через административный интерфейс.
Пример использования валидаторов
Рассмотрим пример использования валидаторов для проверки поля ISBN в каталоге книг. ISBN — это уникальный идентификатор книги. Сделаем поле ISBN обязательным и зададим шаблон проверки: код поля должен состоять минимум из 13 символов и содержать только цифры и дефисы.
new Entity\StringField('ISBN', [
// Поле обязательно для заполнения
'required' => true,
// Имя столбца в базе данных
'column_name' => 'ISBNCODE',
'validation' => function() {
return [
// Валидатор для проверки формата ISBN
new Entity\Validator\RegExp('/[\d-]{13,}/')
];
}
])
Чтобы проверить, что в ISBN ровно 13 цифр, создайте свой валидатор.
new Entity\StringField('ISBN', [
// Поле обязательно для заполнения
'required' => true,
// Имя столбца в базе данных
'column_name' => 'ISBNCODE',
'validation' => function() {
return [
function ($value) {
// Удаляем дефисы из значения
$clean = str_replace('-', '', $value);
// Проверяем, что значение состоит из 13 цифр
if (preg_match('/^\d{13}$/', $clean)) {
// Если условие выполняется, валидация успешна
return true;
} else {
return 'Код ISBN должен содержать 13 цифр.'; // Сообщение об ошибке, если условие не выполняется
}
}
];
}
])
Валидатор принимает значение поля $value, но также можно использовать дополнительную информацию.
new Entity\StringField('ISBN', [
// Поле обязательно для заполнения
'required' => true,
// Имя столбца в базе данных
'column_name' => 'ISBNCODE',
'validation' => function() {
return [
function ($value, $primary, $row, $field) {
// value — значение поля
// primary — массив с первичным ключом, в данном случае [ID => 1]
// row — весь массив данных, переданный в ::add или ::update
// field — объект валидируемого поля, Entity\StringField('ISBN', ...)
}
];
}
])
Коды ошибок
Если у поля несколько валидаторов и нужно узнать, какой из них сработал, можно использовать код ошибки. По умолчанию есть два стандартных кода ошибок:
BX_INVALID_VALUEесли сработал валидаторBX_EMPTY_REQUIREDесли не указано обязательное поле
Например, у поля ISBN последняя цифра, контрольная. Добавим валидатор для ее проверки и обработаем результат.
Описываем валидатор в поле сущности:
new Entity\StringField('ISBN', [
// Поле обязательно для заполнения
'required' => true,
// Имя столбца в базе данных
'column_name' => 'ISBNCODE',
'validation' => function() {
return [
function ($value) {
// Удаляем дефисы из значения
$clean = str_replace('-', '', $value);
// Проверяем, что значение состоит из 13 цифр
if (preg_match('/^\d{13}$/', $clean)) {
// Если условие выполняется, валидация успешна
return true;
} else {
return 'Код ISBN должен содержать 13 цифр.'; // Сообщение об ошибке, если условие не выполняется
}
},
function ($value, $primary, $row, $field) {
// Здесь можно добавить логику проверки контрольной цифры ISBN
// Если контрольная цифра неправильная, возвращаем ошибку
return new Entity\FieldError(
$field, 'Контрольная цифра ISBN не сошлась', 'MY_ISBN_CHECKSUM'
);
}
];
}
])
Выполняем операцию:
$result = BookTable::update(...);
if (!$result->isSuccess()) {
// Получаем список ошибок
$errors = $result->getErrors();
foreach ($errors as $error) {
if ($error->getCode() == 'MY_ISBN_CHECKSUM') {
// Обработка ошибки, связанной с нашим валидатором контрольной цифры ISBN
}
}
}
Пример создания сущности
// Определяем пространство имен для организации кода
namespace SomePartner\MyBooksCatalog;
// Подключить класс Entity из Bitrix для работы с сущностями
use Bitrix\Main\Entity;
// Определить класс BookTable, наследующий DataManager
class BookTable extends Entity\DataManager
{
public static function getTableName(): string
{
// Возвращает имя таблицы 'my_book'
return 'my_book';
}
public static function getUfId(): string
{
// Возвращает идентификатор 'MY_BOOK'
return 'MY_BOOK';
}
public static function getMap(): array
{
return [
// Определить поле 'ID' как целочисленного
new Entity\IntegerField('ID', [
// Указать, что это первичный ключ
'primary' => true,
// Автоматически увеличивать значения (автоинкремент)
'autocomplete' => true
]),
// Определить поле 'ISBN' как строковое
new Entity\StringField('ISBN', [
// Поле обязательно для заполнения
'required' => true,
// Имя колонки в базе данных 'ISBNCODE'
'column_name' => 'ISBNCODE'
]),
// Определить поле 'TITLE' как строковое
new Entity\StringField('TITLE'),
// Определить поле 'PUBLISH_DATE' как дату
new Entity\DateField('PUBLISH_DATE')
];
}
}
