История развития
ORM — Object-Relational Mapping, (объектно-реляционное отображение).
С версии ядра 12.0.0 (2012-13 год) появился абстрактный базовый класс для работы с объектами данных DataManager (исходя из офф. документации по D7).
С версии ядра 18.0.3 (2018-06-07) добавляются объекты ORM, в это же время меняется структура классов ORM (исходя из описания истории версий).
С версии ядра 19.0.0 (2020 год) у модуля iblock добавлена поддержка
Понятие сущности
По определению Bitrix Сущность — это совокупность коллекции объектов с присущей им базовой (низкоуровневой) бизнес-логикой.
Все сущности являются потомками класса
\Bitrix\Main\ORM\Data\DataManager
Например, сущность Пользователь — множество пользователей с присущим набором полей: ID, LOGIN, EMAIL, PASSWORD и т.д.
В парадигме Bitrix под сущностью подразумевается User, но класс с её описанием должен иметь постфикс Table, то есть UserTable
Основные имена были зарезервированыНо сегодня в работе с ORM мы получим объект класса с префиксом EO_, то есть EO_User
Стандартная сущность
Обращение через физически описанный класс
Наследуется от \Bitrix\Main\ORM\Data\DataManager
и обязательно определяет методы getTableName и getMap
Первый возвращает название таблицы в БД. Второй — массив полей сущности
Стандартная сущность. Highload-блоков
Наследуется от \Bitrix\Highloadblock\DataManager
который расширяет \Bitrix\Main\ORM\Data\DataManager
Стоит определить метод getHighloadBlock который возвращает массив данных о highload-блоке
Вручную описывать сущности Highload-блоков нет необходимости, поскольку для них существует компиляция. Делать это нужно в случаях если:
- Нужна строгая типизация
При генерации аннотаций модуля highloadblock создается описание только для таблиц модуля, а не для таблиц пользовательских highload-блоков - Нужно расширить поля сущности или описать новые связи, которые не строятся автоматически
Базовый синтаксис. Полезные методы сущности
# Получение массива полей сущности
ElementTable::getMap();
# Восстановление объекта
ElementTable::wakeUpObject(mixed: $row);
# Восстановление коллекции
ElementTable::wakeUpCollection(mixed: $rows);
# Удаление элемента
ElementTable::delete(mixed: $primary);
# Если есть доступ к объекту элемента, то нужно вызвать его метод $object->delete();
# Обновление элемента
ElementTable::update(mixed: $primary, array: $data);
Если есть доступ к объекту элемента, то нужно обновлять его поля с сохранением $object->setName('BlaBla')->save();
Для добавления стоит использовать конструкцию вида:
ElementTable::createObject()->setName('BlaBla')->save(); // Простой пример, сохранили элемент с именем
//----------------------------------------
$item1 = ElementTable::createObject()
->setName('BlaBla') //Установили имя
->setPropTest('BlaBla'); //Установили значение для строкового свойства PROP_TEST
$item2 = ElementTable::createObject()->setName('BlaBla');
$collection = ElementTable::createCollection(); // формируем коллекцию сохраняем, убодно использовать в цикле
$collection->add($item1);
$collection->add($item2);
$collection->save();
Добавление полей в выбору
ElementTable::query()
->setSelect(['NAME', 'CITY.NAME']) // Плохо
->addSelect('NAME')
->addSelect('CITY.NAME');
Добавление новых полей в процессе выборки
ElementTable::query()
->registerRuntimeField(new Reference(
'REF_CITY',
CityTable::class,
Join::on('this.CITY.VALUE', 'ref.ID')
))
->addSelect(new Reference(
'REF_CITY',
CityTable::class,
Join::on('this.CITY.VALUE', 'ref.ID')
));
Where синтаксис
where(string: field, string: operator, mixed: value)
where(string: field, mixed: value)
whereNull(string: field)
whereIn(string: field, array: values)
whereLike(string: field, string: value)
whereMatch(string: field, string: value)
whereBetween(string: field, mixed: valueMin, mixed: valueMax)
whereExists(Query|SqlExpression: query)
whereColumn(string: field, string: field)
Помимо where
есть заготовленные методы для разных операторов фильтра. Также для каждого из типов фильтра есть отрицательный вариант whereNot
или where{Operator}Not
Мало кто знает, но whereColumn('FIELD_1', 'FIELD_2')
является оберткой надwhere('FIELD_1', new \Query\Filter\Expression\Column('FIELD_2'))
Зная это можно быстро составлять выражения равенства между колонками
whereIn('LOGIN', [
new Column('NAME'),
new Column('LAST_NAME')
])
Сложные правила
use \Bitrix\Iblock\ORM\Query;
$filter = Query::filter()
->logic(Query::filter()::LOGIC_OR)
->where('CODE', 'test')
->where('CODE', 'test_test')
->whereNull('CODE');
// Сделать все условия фильтра негативными
// $filter->negative();
ElementTable::query()
->whereNotNull('CITY')
->whereBetween('ID', 1, 100)
->whereIn('USER.LOGIN', [
new Column('USER.NAME'),
new Column('USER.LAST_NAME')
])
->where($filter);
Если понадобится список операторов фильтра можно посмотреть тут:
\Bitrix\Main\ORM\Query\Filter\Operator::get()
CAllSQLWhere::$operations
CAllIBlock::MkOperationFilter()
На крайний случая можно заглянуть в этот метод (он парсит строки старого фильтра в getList
)
Остальное
# Сортировка
addOrder(string: field, string: order = 'ASC')
# Лимит
setLimit(int: limit)
# Шаг
setOffset(int: offset)
# Кеширование
setCacheTtl(int: ttl)
cacheJoins(bool: mode)
countTotal(bool: count)
Получение результата
Если для рабочей сущности были заранее сгенерированы аннотации, то exec
можно опустить. В противном случае его обязательно нужно указывать, для корректной работы подсказок IDE
$query->fetchCollection();
$query->exec()->fetchCollection();
$query->fetchObject();
$query->exec()->fetchObject();
// Bitrix\Main\ORM\Query\Query
/**
* Short alias for $result->fetchCollection()
*
* @return null Actual type should be annotated by orm:annotate
* @throws Main\ObjectPropertyException
* @throws Main\SystemException
*/
public function fetchCollection()
{
return $this->exec()->fetchCollection();
}