Потребовалось для одного проекта скрестить инфоблоки с собственными таблицами, всяческими сквозными фильтрами и сортировками. Да и ещё и работало чтоб быстро. Первое, что приходит на ум - 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(). А ещё в качестве бонуса там остался закомментированный код для реализации постранички со своей переменной/чпу.
Вместо тысячи слов Просто для демонстрации - пример скомпанованного запроса с фильтром по свойствам инфоблока и сторонней таблице и сортировкой по агрегированному полю сторонней таблицы. Быстро, просто и безболезненно.
kopoBko, в том-то и дело, что названия классов, неймсейсов в новом ядре крайне громоздки, выучить их пока что кажется невозможным. Вот со старым ядром как просто - в справочнике начал писать "cib" и у вот уже список методов для работы с инфоблоками, описание входных параметров, примеры. Все что нужно и в одном месте. Буквально на днях делал небольшой функционал на highload-блоках. Несколько часов потратил просто на поиск названий ... не знаю как точно назвать но типа такого \bitrix\highloadblock\infoblock\tabledata\class\entity\getlist и передаваемых параметров в методы. Экспериментально узнал, что не поддерживается выборка по свойствам привязки, типа "UF_BRAND.NAME". Без документации все эти вещи приходится часами гуглить, догадываться, смотреть исходный код. Цензурных слов, что бы описать эту ситуацию у меня нет.
Быть может вы знаете секрет, как с этим работать и жить без документации. Но мне без справочника трудновато.
P.S. Думал уже не писать это сообщение и посмотреть, что же пишут в обучающих курсах... ну ага, ссылки битые, самое нужное (судя по тексту ссылки) не открывается. В общем, без локального справочника никак нельзя.
Постоев Олег, хайлоадблоки это динамический класс, а тут статикой описываешь там нужно генерить класс, получать его, дальше работать. Тут проще. 3-4 таблицы описываешь и уже выучил что и как и за чем идет. а так да, сам перерыл всю папку lib модуля main на изучение
Постоев Олег написал: Быть может вы знаете секрет, как с этим работать и жить без документации. Но мне без справочника трудновато.
Весь секрет в коде:-) Новый код (который в папках lib) хорошо прокомментированн и структурирован. Идёте в точку входа (конкретный метод или компонент) и лезете вглубь, расширяя кругозор. Ну а для начала ознакомиться с постами Вадима Думбравану (один из них вам привели выше) и общим курсом по orm.
Постоев Олег написал: ну ага, ссылки битые, самое нужное (судя по тексту ссылки) не открывается
Хм... Поправил ссылки, благодарю. Они все ведут на разные главы одного и того же раздела - ORM. Для старта этого вполне достаточно, ну а дальше - только ковыряния и ковыряния.
Респект за работу и исходники на Гитхабе. Модуль конечно нужен. Во-первых, будет порядок в структуре каталогов, во-вторых, вроде бы Композер с Битриксом подружили, а значит, если чуть допилите, можно будет просто и удобно ставить его из консоли.
По-сути, используя ORM-генератор, нагенерировать сущности к таблицам инфоблока не трудно, однако спасибо за пример, а также за качественно оформленную статью.
Чебан Валерий, нагенерить - нетрудно. Но генерация не мапит автоматом символьные коды (не будем же мы делать выборки по условиям PROPERTY_59), не линкует связи и не делает другие мелочи, на которые натыкаешься в процессе разработки. Обёртка как раз позволяет не заниматься этим и снять кучу рутины.
Чебан Валерий, скорее всего, связано с принятым именованием сущностей и закладкой на будущее (из документации):
Внимание! Несмотря на то, что в пример под сущностью подразумевается Книга (Book), к имени класса дописан постфикс: BookTable. Это сделано специально - имя описательного класса сущности всегда должно завершаться словом Table. Основное имя Book в этом же пространстве имен считается зарезервированным, в будущем предполагается использовать основное имя (в данном случае - класс Book) для представления элементов сущности в виде объектов (в настоящий момент данные сущности представлены массивами, как и в старых методах getList).
Я как-то не задумывался над этим и именовал файлы по имени класса, содержащегося в них.
Медведев Дмитрий, мне не хватает локального справочника функций в chm файле. Не пойму, его либо не обновляют, либо ORM можно считать недокументированной и использовать нельзя. В IDE эти функции будут если для каждого проекта выгружать всю папку bitrix? Не, спасибо, мне local хватает, я думаю, эту директорию как раз для этого придумали (ну ладно, для этого после контроля версий).
посмотрел уже более внимательно код, хорошо получилось. еще приятно обрадовало, что по модулю инфоблока уже больше сущностей в последнем релизе описано.
Постоев Олег, как правильно пишет Никита, можно добавлять bitrix/modules как внешнюю библиотеку.
Собрать описание API - у нас витает эта идея. Первый шаг - мы начали использовать phpdoc, чтобы на основе него автоматом генерить api reference. Но до конца пока не довели концепцию - какие методы стоит выгружать в доку, какие нет (public - еще не значит, что мы гарантируем обратную совместимость). Как наступит более свободный график, постараемся довести идею до реализации, и тогда у вас будет доступ к постоянно актуальной доке по апи.
А пока многие разработчики, в том числе внутри нашей компании, отмечают, что даже просто phpdoc помогает. Как по мне, так смотреть доку в IDE быстрее, удобнее и способствует сокращению ошибок на этапе написания кода.
Медведев Дмитрий написал: Постоев Олег, как правильно пишет Никита, можно добавлять bitrix/modules как внешнюю библиотеку.
Отличная возможность. Работю в NetBeans, вроде было что-то подобное, но не знал что так можно. Спасибо!
А вот по поводу документации. Буквально сейчас столкнулся с необходимостью группировки выборки из highload. Все что удалось найти по этой теме - это заметка в блоге http://dev.1c-bitrix.ru/community/blo...ad/orm.php где сказано, что можно передать параметр 'group' и всё. В смысле вообще всё. То есть ни примеров, ни формата массива нет, где искать если не в документации? Включить группировку записей так и не получилось. Сделал foreach и вручную...
Постоев Олег, перейдите по ссылкам в моём посте, они все ведут на разные разделы документации по орм, в том числе и на группировку. Если же они не кидают на главную страницу курса (там какие-то чудеса прлисходят), откройте Ядро D7 > ORM > Выборка данных.
Постоев Олег, с большой долей увернности скажу, что вы делаете что-то неправильно. HL это надстройка над orm, getlist в ней не переопределяется. Соответственно, группировка там есть и работает. Перепроверьте названия полей, посмотрите генерируемый sql-запрос.
SEL ECT
`sale_position`.`ID` AS `ID`,
`sale_position`.`UF_NAME` AS `UF_NAME`,
`sale_position`.`UF_SALE` AS `UF_SALE`,
`sale_position`.`UF_SECTION` AS `UF_SECTION`,
`sale_position`.`UF_COUNT` AS `UF_COUNT`,
`sale_position`.`UF_COUNT_TYPE` AS `UF_COUNT_TYPE`,
`sale_position`.`UF_NOTE` AS `UF_NOTE`,
`sale_position`.`UF_SORT` AS `UF_SORT`
FR OM `als_sale_position` `sale_position`
WHERE UPPER(`sale_position`.`UF_NAME`) like upper('%')
AND `sale_position`.`UF_SECTION` = 202
GROUP BY `sale_position`.`UF_NAME`, `sale_position`.`ID`, `sale_position`.`UF_SALE`, `sale_position`.`UF_SECTION`, `sale_position`.`UF_COUNT`, `sale_position`.`UF_COUNT_TYPE`, `sale_position`.`UF_NOTE`, `sale_position`.`UF_SORT`
ORDER BY `sale_position`.`UF_SORT` ASC
LIMIT 0, 20
Нейман Андрей, только одно? У меня в select как раз все нужные поля. Или нужно не все поля указывать? Если я создам одно доп.поле, например UF_TMP и никогда не буду его выбирать - группировка заработает?
Постоев Олег, что вы указали в селекте, то попадёт и в группировку. Соответственно, указали ID - потеряли группировку по нужному полю. Настоятельно рекомендую ознакомиться с вышеприведённой ссылкой, чтобы лучше понять причины (если память не изменяет, автор объяснения как раз Дмитрий Медведев).
Нейман Андрей, то есть, если я хочу сгруппировать записи, то в 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С-Битрикс».