Потребовалось для одного проекта скрестить инфоблоки с собственными таблицами, всяческими сквозными фильтрами и сортировками. Да и ещё и работало чтоб быстро. Первое, что приходит на ум - 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(). А ещё в качестве бонуса там остался закомментированный код для реализации постранички со своей переменной/чпу.
Вместо тысячи слов Просто для демонстрации - пример скомпанованного запроса с фильтром по свойствам инфоблока и сторонней таблице и сортировкой по агрегированному полю сторонней таблицы. Быстро, просто и безболезненно.
Нейман Андрей, то есть, если я хочу сгруппировать записи, то в SELECT ничего кроме сгруппированных полей не получить?... в принципе логично. Не очень логично, что система позволяет передать поля (или принять) отличные от используемых в group. Опять же - тяжело без доки. Вот по CIBlockElement::GetList в chm-файлике сразу написано, что можно передавать, что нельзя и какие могут быть исключения, а с ORM целое расследование, информацию по крупицам и по слухам собирать надо.
Статья хорошая, но она все таки больше по чистому SQL, чем по использованию ORM. Это все равно что, для умения водить машину потребовалось бы выучить передаточные числа и сопряжения всех звенок двигателя и кпп. Я предполагал, что ORM сделали, как раз, что бы скрыть нутро и пользоваться единообразной системой.
Лично вам - огромное спасибо! Помогли разобраться. Возможно и другие искатели прик... документации наткнутся на эту беседу и почерпнут что-то полезное о группировке полей
в принципе логично. Не очень логично, что система позволяет передать поля (или принять) отличные от используемых в group.
Система делает иначе - она дополняет группировку выбираемыми полями для соблюдения стандарта.
Статья хорошая, но она все таки больше по чистому SQL, чем по использованию ORM
Полагаю, вы не поняли суть статьи. Она не научит вас работе с crm. Она объясняет, почему в группировку попадают линие поля. Другими словами, она ответит на ваш вопрос "почему получилось то, что получилось". Если вы выбираете два поля из записей, группируя их по первому, то во втором поле в случае mysql может быть что угодно. И если это что угодно используется дальше, ваш код нерабочий, он ведёт себя непредсказуемо, а вы должны его контролировать. Orm пытается избежать этой ситуации, строя запрос по-правильному. Но "по-правильному" не всегда значит так, как хочет разработчик.
Опять же - тяжело без доки. Вот по CIBlockElement::GetList в chm-файлике сразу написано, что можно передавать, что нельзя и какие могут быть исключения, а с ORM целое расследование, информацию по крупицам и по слухам собирать надо.
В гетлисте инфоблока огромная часть посвящена описанию полей, которые в случае orm/hl вы сами придумываете. Но что ж вы, простите, докопались-то до этой chm:-) Замените надпись "учебный курс" на chm и всё. Раздела ORM, на который уже неоднократно ссылались, хватает заглаза и для быстрого старта, и ответов на 95% вопросов (и +2 от статей Вадима, на которые выше приводились ссылки). Там и описание методов (как в chm), и примеры (как в chm), и предостережения (как в chm). Только это не chm, а всего несколько страниц курса, кратких и ёмких, требующих только несколько большего бэкграунда (как и весь d7, собственно). Для глубоких же копаний уже ни одна документация не поможет, просто открываете код и смотрите, что там происходит (но по вашим проблемам в этом пока нет необходимости, всё находится на уровне понимания написанного в документации). Конечно, подводные камни порой встречаются и я бы тоже хотел страницу, где будут даны сразу все решения моих проблем, но это недостижимо. А вот прочитать главу от корки до корки и взглянуть на свой код по-новому - это очень даже реально)
Нейман Андрей, благодарю за разъяснения. Обязательно изучу (без chm) и попробую ORM в действии (не смотря, на отсутствие в chm)!
Но, позвольте еще один вопрос задать по методу getList. Есть поле UF_ELEMENT с привязкой к инфоблоку. Почему такой select не делает выборку в getList'е: 'UF_ELEMENT.NAME'?
А не подскажите как реализовать связку 2 инфоблоков(элементы и торговые предложения) и для каждого элемента вывести все торговые предложения? связка идет по свойству PROPERTY_CML2_LINK(свойства торговых предложений)
Мигунов Игорь, общая схема такая же, как и при работе со стандартным апи. Только объявляете ReferenceField на товары в ИБ предложений через runtime. А вообще по вопросу сложно понять, чем не устраивает простое апи/стандартные компоненты.
Нейман Андрей, в моем функционале нужно получить количество товаров и торговых предложений отфильтрованных и сгруппированных по свойствам товаров. Много запросов получается через стандартное апи. Да и множественная группировка насколько я знаю одним махом невозможна. Делается для смарт фильтра. Если есть другое решение - покажите пожалуйста. Буду благодарен. Я не нашел.
Нейман Андрей написал: Только объявляете ReferenceField на товары в ИБ предложений через runtime.
Суть ясна. Не совсем понятно как это выглядит. Не затруднит немного кода показать?
Закомментированное написал на всякий случай, если вдруг будет ругаться на CML2_LINK. Код на реальных данных не тестировал, просто набросок. Можно объявить референс напрямую через ReferenceField, как я выше писал, но если будете постраничку использовать, возникнут проблемы из-за особенностей ормки.
Жуков Евгений, почему? ОРМ не раскроет такое выражение? (у меня колонки из таблицы единичных свойств PROPERTY мапятся к таблице элементов как PROPERY_#CODE# => PROPERTY.PROPERTY_#ID#)
Нейман Андрей написал: ОРМ не раскроет такое выражение?
orm здесь не при чем. Для свойства привязки торговых предложений к основному товару жестко отслеживается только ID и XML_ID. CODE пользователь может менять совершенно спокойно. Кроме того, использование символьных кодов, хоть и удобно разработчику, несет в себе массу "подводных камней".
Жуков Евгений, по привязке-то всё понятно. По остальному - складывается впечатление, что мы про разные вещи говорим. Если у меня объявлен алиас PROPERTY_123 на PROPERTY_CODE, почему я не могу им воспользоваться? Особенно учитывая, что код CML2_LINK используется везде и всюду в компонентах, поэтому надо быть сумасшедшим, чтобы его во что-то поменять.
Загальский Андрей, не могу ничего сказать, поскольку ещё не сталкивался с такой проблемой. На страницах имеются несколько выборок по разным инфоблокам, пока всё работает.
Нейман Андрей, ок попробую потестировать сейчас... У меня так и не получилось обойти данное ограничение (причем даже не получилось найти где обламывает)...
Нейман Андрей, да ошибок нет... т.к. классы имеют разные названия \Iblock105Table - добавляется ид инфоблока... У меня же всегда одно название класса...
А кто нибудь может подсказать почему битриксоиды сами не реализовали подобную ORM сущность для инфоблоков со свойствами из коробки? Или это идет как то в разрез с новой идеологией и нам вообще этого не стоит ожидать?
внутри createEntity нужно добавить проверку на существование класса сущности, а то ошибка возникает если на одном хите в разных местах вызывается createEntity к одному и тому же ИБ.
Группы на сайте создаются не только сотрудниками «1С-Битрикс», но и партнерами компании. Поэтому мнения участников групп могут не совпадать с позицией компании «1С-Битрикс».