Потребовалось для одного проекта скрестить инфоблоки с собственными таблицами, всяческими сквозными фильтрами и сортировками. Да и ещё и работало чтоб быстро. Первое, что приходит на ум - ORM. Но она в инфоблоках пока отсутствует (точнее, отсутствует самая нужная её часть - работа с элементами), поэтому пришлось реализовывать свою прослойку, которой и хочу поделиться - https://github.com/unnamed777/IblockOrm
Краткие характеристики
Доступна вся мощь getlist'а orm и sql-билдера - связывание и подтягивание данных откуда угодно и как угодно (раз, два).
Возможна работа как с заранее описанными сущностями (наследниками обёртки), так и с динамически создаваемыми (аналогично hlblock'ам).
Поддержка стандартной постраничной навигации. Пока в ORM её нет, поэтому пришлось немного костыльнуть, зато поддерживается стандартный system.pagenavigation.
Хелпер для получения всех свойств элементов в выборке (описано ниже).
Только для ИБ 2.0. При особом желании можно и под первые адаптировать, но не было необходимости.
Read only. Создания и модификация элементов через эту обёртку невозможны, для этого есть замечательное классическое апи.
Доступ к элементам по правам - на совести конечного разработчика, обёртка не предлагает никакого функционала, но предоставляет все возможности для его реализации.
Обёртка состоит из трёх классов:
ElementTable - главный класс, наследуется от orm'ного DataManager. Через него происходит вся работа.
MultiplePropertyElementTable - служебная сущность, описывающая множественные свойства инфоблока.
SinglePropertyElementTable - аналогично предыдущему, только для единичных.
Все они находятся под рекомендуемым namespace Vendor\Module, в моём случае - \nav\IblockOrm. Кстати говоря, нагромождение папок в обёртке может показаться странным, но предполагается, что она будет находиться в /local примерно вот так:
Как использовать Для начала зарегистрируем классы в автолоадере (можно и просто заинклудить):
Как видно, синтаксис запросов всё тот же. Основные моменты:
Свойства доступны по коду PROPERTY_CODE, где CODE - символьный код свойства (как в обычном гетлисте).
Если свойства есть в select, то их значения в результате будут находиться в тех же ключах, если не было задано алиасов (в апи инфоблоков, как помните, они находятся в PROPERTY_CODE_VALUE).
Поля при получении экранируются htmlspecialcharsbx(), оригинальные значения лежат в ключах с префиксом ~ (аналогично обычному гетлисту).
По умолчанию ORM выбирает только скалярные поля, поэтому для получения желаемых свойств их надо явно указывать в select. Либо использовать хелпер для получения всех свойств.
Для получения ссылки на элемент надо в select указать DETAIL_PAGE_URL. Это динамически вычисляемое поле, поэтому по умолчанию оно не заполняется.
Постраничная навигация стартует автоматически, если в выборке были указаны и limit, и offset.
При фильтре по множественному свойству элементы могут дублироваться. Чтобы этого избежать, надо добавить в select поле DISTINCT (см. комментарии к посту).
Что внутри Главный класс - ElementTable - это наследник от DataManager с getMap() (метод, описывающий поля сущности), который считывает свойства и строит по ним поля-референсы PROPERTY_*. Референсы эти ведут на:
SinglePropertyElementTable для единичных свойств. На каждый инфоблок (читай класс) присутствует только один референс на SinglePropertyElementTable и он доступен по ключу PROPERTY. Поля референса - это символьные коды свойств. Таким образом, в гетлисте мы можем обращаться к свойствам по ключу PROPERTY.CODE. Предлагаемые же PROPERTY_CODE сделаны просто для удобства и совместимости с обычным апи инфоблоков и являются референсами на поля сущности SingleProperty.
MultiplePropertyElementTable. Поскольку у каждого множественного свойства своя таблица в БД, то и сущностей MultipleProperty создаётся на каждое свойство по одной. Сущности доступны по ключу PROPERTY_CODE_ENTITY. Так как таблицы множественных свойств имеют разные колонки в зависимости от типа свойства (VALUE, VALUE_ENUM, VALUE_NUM), не пренебрегайте фильтр через эту сущность для достижения наилучшей производительности (вообще при работе с любой orm-сущностью советую всегда мониторить генерируемый sql, встречаются подвохи). Для сущности элемента инфоблока PROPERTY_CODE является референсом PROPERTY_CODE_ENTITY.VALUE.
Постраничная навигация Как выше уже упоминал, постраничка стартует автоматически, если указаны limit и offset в запросе. Поскольку в ORM такое понятие отсутствует, пришлось немного извратиться. Объект \Main\DB\Result (результат работы getList()) дополняется свойством oldCDBResult - старым CDBResult, который содержит стандартную информацию о текущей странице, количестве элементов и т.п, и используется для получения кода постранички. С первого раза, полагаю, понять сложно, поэтому вот пример кодом "было/стало".
Так выглядит классическая постраничка в news.list:
Хелпер для получения всех свойств Чтобы получить свойства элемента, можно в явном виде перечислить их в select. Но если их много или же нужна дополнительная информация о них, можно получить сразу все свойства для всех элементов выборки с помощью вспомогательного метода:
Внутри используется магия \CIBlockElement::GetPropertyValuesArray() - та же, что применяется в catalog.section для существенного сокращения количества запросов к БД по сравнению с _CIBlockElement::GetProperties().
Хелпер для получения Query Порой при отладке требуется залезть напрямую в объект запроса, но стандартный датаменеджер сразу возвращает объект результата выборки Bitrix\Main\Db\Result. Поэтому был добавлен ещё один метод, являющийся полной копией DataManager::getList(), но возвращающий Bitrix\Main\Entity\Query вместо Result:
$query = MyIblockTable::getQuery(...)
Пример компонента Если вы смогли дочитать до этого места, то наверняка уже заглянули на гитхаб и увидели там компонент. Это простой пример компонента, использующий для вывода списка элементов orm-обёртку. Изначально это был компонент в рабочем проекте, из которого я выпилил всю лишнюю логику, но не стал менять общей структуры, поэтому он может выглядеть монструозно. Основная логика находится в методах getItems() и formatResult(). А ещё в качестве бонуса там остался закомментированный код для реализации постранички со своей переменной/чпу.
Вместо тысячи слов Просто для демонстрации - пример скомпанованного запроса с фильтром по свойствам инфоблока и сторонней таблице и сортировкой по агрегированному полю сторонней таблицы. Быстро, просто и безболезненно.
Загальский Андрей, не могу ничего сказать, поскольку ещё не сталкивался с такой проблемой. На страницах имеются несколько выборок по разным инфоблокам, пока всё работает.
Нейман Андрей, ок попробую потестировать сейчас... У меня так и не получилось обойти данное ограничение (причем даже не получилось найти где обламывает)...
Нейман Андрей, да ошибок нет... т.к. классы имеют разные названия \Iblock105Table - добавляется ид инфоблока... У меня же всегда одно название класса...
А кто нибудь может подсказать почему битриксоиды сами не реализовали подобную ORM сущность для инфоблоков со свойствами из коробки? Или это идет как то в разрез с новой идеологией и нам вообще этого не стоит ожидать?
внутри createEntity нужно добавить проверку на существование класса сущности, а то ошибка возникает если на одном хите в разных местах вызывается createEntity к одному и тому же ИБ.
Группы на сайте создаются не только сотрудниками «1С-Битрикс», но и партнерами компании. Поэтому мнения участников групп могут не совпадать с позицией компании «1С-Битрикс».