Bitrix ORM D7 (Object-Relational Mapping)

История развития

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();
}

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *