Bitrix orm add error

Битрикс. ORM в новом ядре Реализация ORM в ядре D7 призвана абстрагировать разработчика от механики работы с таблицами на уровне запросов к БД, введя понятие сущности и поля сущности. Сущность — это таблица, поля сущности — столбцы или «ссылки» на другие сущности, а DataManager — система управления данными. Для каждой сущности нужно создать описание, […]

Содержание

  1. Битрикс. ORM в новом ядре
  2. Выполнение запросов
  3. Пример № 1
  4. Пример № 2
  5. Пример № 3
  6. Пример № 4
  7. Пример № 5
  8. Пример № 6
  9. Пример № 7
  10. Bitrix orm add error
  11. Как построен курс
  12. Начальные требования к подготовке
  13. У нас часто спрашивают, сколько нужно заплатить
  14. Баллы опыта
  15. Тесты
  16. Комментарии к статьям
  17. Что дальше?
  18. Для преподавания офлайн
  19. Если нет интернета
  20. ORM OM NOM NOM или тайны ORM в 1С-Битрикс
  21. О чем и для кого эта статья
  22. Базовые возможности ORM в 1С-Битрикс: Управление сайтом
  23. Автоматическая генерация ORM-классов
  24. Работа с удаленными БД
  25. Нюансы работы с разными типами БД
  26. Реальный пример: использование ORM для подключения из PHP к MSSQL в Linux
  27. Выбор данных из хранимых процедур вместо таблиц
  28. Выводы

Битрикс. ORM в новом ядре

Реализация ORM в ядре D7 призвана абстрагировать разработчика от механики работы с таблицами на уровне запросов к БД, введя понятие сущности и поля сущности. Сущность — это таблица, поля сущности — столбцы или «ссылки» на другие сущности, а DataManager — система управления данными. Для каждой сущности нужно создать описание, например:

Класс описывает таблицу БД b_iblock_element , которая хранит элементы инфоблоков:

В методе getMap() перечислены все поля таблицы, включая описание связей с другими сущностями. Таким образом указано отношение столбца IBLOCK_ID текущей таблицы и столбца ID сущности Iblock . В дальнейшем по reference-полям возможно выбирать поля связанных сущностей и использовать их в фильтрах.

Автоматически сгенерировать класс с описанием любой таблицы можно на странице «Настройки • Производительность • Таблицы», добавив параметр orm=y в адрес:

Выполнение запросов

Тренироваться будем на таблицах типов инфоблока, самих инфоблоков, элементов и разделов. Для них описаны сущности TypeTable , IblockTable , ElementTable и SectionTable , их можно посмотреть в исходниках модуля iblock .

Пример № 1

Выбираем элементы инфоблока с идентификатором 5:

Список методов BitrixMainEntityQuery :

  • setSelect() , setGroup() — устанавливает массив с именами полей
  • addSelect() , addGroup() — добавляет имя поля
  • getSelect() , getGroup() — возвращает массив с именами полей
  • setFilter() — устанавливает одно- или многомерный массив с описанием фильтра
  • addFilter() — добавляет один параметр фильтра со значением
  • getFilter() — возвращает текущее описание фильтра
  • setOrder() — устанавливает массив с именами полей и порядком сортировки
  • addOrder() — добавляет одно поле с порядком сортировки
  • getOrder() — возвращает текущее описание сортировки
  • setLimit() , setOffset() — устанавливает значение
  • getLimit() , getOffset() — возвращает текущее значение
  • registerRuntimeField() — регистрирует новое временное поле для исходной сущности

Пример № 2

Через сущность «элемент» можно выбирать или ставить условия на поля связанной сущности «инфоблок». Связанная таблица по умолчанию присоединяется с помощью LEFT JOIN . Вспомним reference-поле IBLOCK в описании ElementTable и выберем данные самого инфоблока вместе с элементами инфоблока:

Пример № 3

В запросах можно использовать агрегатные функции MySQL. Для это служит метод registerRuntimeField() , регистрирующий новое поле на время выполнения запроса. Посмотрим, сколько активных элементов в инфоблоке:

Пример № 4

Выбираем разделы инфоблока с идентифкатором 5 и подсчитываем количество элементов в каждом; учитываем только активные разделы и элементы:

Добавим еще одно условие, чтобы выбирать только разделы, содержащие более трех элементов:

Пример № 5

Runtime-поле может быть не только вычисляемым значением, но и ссылкой на другую сущность. Т.е. в методе getMap() можно не описывать связь, а сформировать ее прямо в запросе. Например, создадим объект Query для сущности IblockTable , свяжем ее с ElementTable и выберем элемент с ID=349 :

Пример № 6

В определении runtime-reference-поля можно указывать тип соединения ( LEFT , RIGHT , INNER ), а в фильтре использовать сложную логику, как в CIblockElement::GetList() :

Пример № 7

Получаем пользовательские свойства элементов инфоблока с идентификатором 5:

Источник

Bitrix orm add error

Цитатник веб-разработчиков В тексте курса вы встретите цитаты, высказанные в разное время разработчиками системы и разработчиками проектов на базе Bitrix Framework. Надеемся, что такие неформальные замечания внесут некоторое разнообразие в процесс изучения. Заодно опытные специалисты поделятся и своим опытом.

Имена авторов цитат даются в том написании, в каком авторы зарегистрировали себя на сайте «1С-Битрикс». .

Евгений Смолин: Сам кувыркался с непонятками (пока курсы учебные не прочитал и руками не попробовал то, что там написано). Битрикс из тех систем, где изучение «методом тыка» не очень эффективно без предварительного изучения учебных курсов. Уважаемые новички, потратьте немного своего драгоценного времени, пройдите пару-тройку учебных курсов и масса вопросов просто испарится — там есть ответы на множество вопросов.

Курс для разработчиков — продолжение линейки учебных курсов по Bitrix Framework. Получение сертификата по курсу рекомендуется после успешной сдачи тестов по всей линейке курсов, так как без понятия о работе Контент-менеджера и Администратора создание успешных сайтов будет затруднено.

Чтобы научиться программировать в Bitrix Framework, нет необходимости изучать всю линейку курсов. Но есть моменты, которые необходимо знать разработчикам о системе, они раскрыты в начальных курсах:

  • Интерфейс программы — в главе Элементы управления курса Контент-менеджер.
  • Компоненты 2.0 (начальные сведения) в главе Компоненты 2.0 (начальные сведения) курса Контент-менеджер.
  • Информационные блоки — в главе Информационные блоки (начальные сведения) курса Контент-менеджер.
  • Управление доступом к файлам, элементам контента, модулям и другие права доступа в главе Управление доступом курса Администратор. Базовый.
  • Работа с инструментами системы — в главе Работа с инструментами курса Администратор. Базовый.
  • Модуль Поиск — в главе Поиск курса Администратор. Базовый.
  • Вся информация по администрированию модулей размещена в курсах:
    • Администрирование. Модули — модули «1С-Битрикс: Управление сайтом»
    • Администратор. Бизнес — модули «1С-Битрикс: Управление сайтом», связанные с коммерческой деятельностью в Интернете.
    • Администратор «1С-Битрикс: Корпоративный портал» — модули «1С-Битрикс: Корпоративный портал»

    Как построен курс

    Общепринятая градация квалификации разработчиков в рамках курса обозначает что:

    • Junior сможет создавать простые сайты работая со штатными компонентами и модифицируя их шаблоны.
    • Middle разработчик может работать с API Bitrix Framework.
    • Senior умеет работать над производительностью и безопасностью сайтов, создавать свои модули и компоненты.

    Начальные требования к подготовке

    Для успешного изучения курса и овладения мастерством разработки сайтов на Bitrix Framework необходимо владеть (хотя бы на начальном уровне):

    • основами PHP, баз данных;
    • основами HTML, CSS.

    У нас часто спрашивают, сколько нужно заплатить

    Курс полностью бесплатен. Изучение курса, прохождение итоговых тестов и получение сертификатов — ничего из этого оплачивать не нужно.

    Ещё у нас есть Академия 1С-Битрикс, где можно обучиться на платной основе на курсах нашей компании либо наших партнёров.

    Баллы опыта

    В конце каждого урока есть кнопка Прочитано! . При клике на неё в Вашу итоговую таблицу опыта добавляется то количество баллов, которое указано в прочитанном После нажатия кнопки Прочитано! появится
    окно подтверждения:

    уроке.

    Периодически мы заново оцениваем сложность уроков, увеличивая/уменьшая число баллов, поэтому итоговое количество набранных Вами баллов может отличаться от максимально возможного. Не переживайте! Отличный результат — это если общее число набранных Вами баллов отличается от максимального на 1-2%.

    Тесты

    После изучения курса вам будет предложено пройти тесты на сертификацию. При успешной сдаче последовательности тестов на странице Моё обучение можно просмотреть результат обучения и загрузить сертификат в формате PDF.

    Также Вы можете поделиться ссылкой на страницу со своими сертификатами. Для этого на странице Моё обучение отметьте опцию Разрешить публичный доступ к резюме студента и скопируйте ссылку на страницу резюме . Страница с Вашим резюме будет доступна всем, кому Вы отправите ссылку на неё.

    Комментарии к статьям

    На каждой странице курса авторизованный на сайте посетитель может дать комментарий к содержимому страницы. Комментарий — не форум, там не ведётся обсуждений или разъяснений. Это инструмент для сообщений нам об ошибках, неточностях. Для отправки комментария воспользуйтесь расположенной в правом нижнем углу окна браузера кнопкой:

    Что дальше?

    Одновременно с изучением курса Разработчик Bitrix Framework вам придётся обращаться к информации о других технологиях Bitrix Framework. Эта информация размещена в следующих курсах:

  • Технология Композитный сайт — ускорение работы сайта.
  • Разработка и эксплуатация высоконагруженных проектов — для тех кто делает «тяжёлые» проекты.
  • Бизнес-процессы — настройка и создание бизнес-процессов.
  • Бот платформа Битрикс24 — приложения, основанных на чатах в веб-мессенджере.
  • Маркетплейс Битрикс24 — приложения для коробочной и облачной версий Битрикс24.
  • Маркетплейс Bitrix Framework — расширение функционала проектов на основе Bitrix Framework с помощью сторонних модулей и решений.
  • Многосайтовость — система многосайтовости и принципы работы с многосайтовой конфигурацией.

Для преподавания офлайн

Если данный курс берётся в качестве основы для офлайнового преподавания, то рекомендуемая продолжительность: 5 дней (40 академических часов).

Если нет интернета

Скачать материалы курса в формате EPUB. Файлы формата EPUB Чем открыть файл на
Android:
EPUB Reader
CoolReader
FBReader
Moon+ Reader
eBoox

iPhone:
FBReader
CoolReader
iBook
Bookmate

Windows:
Calibre
FBReader
Icecream Ebook Reader
Плагины для браузеров:
EpuBReader – для Firefox
Readium – для Google Chrome

iOS
Marvin for iOS
ShortBook

Linux:
Calibre
FBReader
Cool Reader
Okular обновляются периодически, поэтому возможно некоторое отставание их от онлайновой версии курса. Версия файла — от 12.12.2022.

Источник

ORM OM NOM NOM или тайны ORM в 1С-Битрикс

  • О чем и для кого эта статья
  • Базовые возможности ORM в 1С-Битрикс: Управление сайтом
  • Автоматическая генерация ORM-классов
  • Работа с удаленными БД
  • Нюансы работы с разными типами БД
  • Реальный пример: использование ORM для подключения из PHP к MSSQL в Linux
  • Выбор данных из хранимых процедур вместо таблиц
  • Выводы

О чем и для кого эта статья

Эта статья – одна из самых популярных страниц нашего сайта. Несколько сотен человек каждый день читают ее. Мы опубликовали важнейшую для мира Битрикс-разработки справочную информацию за полгода до появления официальной документации.

Статья будет вам интересна, если Вы уже видели и слышали что-то про ORM в 1С-Битрикс: Управление сайтом, и даже пробовали разрабатывать сайты на 1С-Битрикс с собственными ORM-классами. Статья состоит из трех частей:

  • Краткое напоминание о базовых возможностях ORM в БУС;
  • Генератор ORM-классов;
  • Подробно про работу ORM c БД. Реальный случай из практики: использование ORM для подключения из linux’а к MSSQL БД и выбора данных из хранимых процедур вместо таблиц.

Базовые возможности ORM в 1С-Битрикс: Управление сайтом

Согласно SRP, каждый класс в программе должен иметь только одну обязанность. Например: работа с форматом валют, работа с языковым пакетом, работа с настройками приложения.

В такой серьезной системе, как 1С-Битрикс: Управление сайтом классов и обязанностей должно быть немало. Моя IDE подсказывает, что в Интернет-магазине версии 16.0.1 объявлено 2759 классов. Я точно уверен, что самая многочисленная “группа” — классы для работы с таблицами в БД. CIBlockElement для работы с таблицей b_iblock_element (элементы инфоблоков), CSaleOrder для таблицы b_sale_order (заказы), CUser для работы с b_user (пользователи) и т.д. В БД сейчас 413 таблиц. Выходит, должно быть 413 классов для работы с каждой из них.

До недавнего времени каждый такой класс обладал уникальностью снежинки. Да, во многих выборка из таблицы выполнялась в методе с названием “GetList”, но это не более чем соглашение. Это нигде не было закреплено и никем не проверялось.

Более того, несмотря на общее название, эти методы вполне могли работать по-разному. вспомните 3 метода, имеющих одинаковое название:

  • CUser::GetList;
  • CIBlockElement::GetList;
  • CSaleOrder::GetList.

Пожалуй, только опытный программист сможет сходу назвать особенности их применения без подглядывания в справку. А ведь есть еще такие острые вопросы, как добавление в БД, обработка ошибок и т.п. И, зачастую, каждый класс решает “сам за себя”.

Но сейчас ситуация меняется. Появилось новое ядро D7, с новыми идеями, механизмами и концепциями. Поговорим об одной из них, краеугольной — ORM (Object Relation Model). Об ORM подробно рассказывают в блоге разработчиков и есть отличное руководство в документации. Постараюсь не повторяться и буду считать, что вы уже написали пару ORM-классов, ориентируясь на эти статьи.

Я выделю только основное. Если класс отвечает за доступ к таблице БД, он должен быть наследником класса BitrixMainEntityDataManager и должен переопределять только два метода:

  1. getTableName для получения имени таблицы;
  2. getMap для получения массива колонок таблицы — объектов BitrixMainEntityField

Метод getFilePath, который упоминается в блоге, больше не является обязательным!
Хочется похвалить разработчиков за ORM. Это не просто слова и планы на будущее. На момент написания этого текста (декабрь 2015) переведено на “новые рельсы” 215 классов. То есть, больше половины всех классов для работы с БД уже имеют D7-аналоги и используются.

Выводы по базовым возможностям ORM в 1С-Битрикс: УС. У нас появился очень мощный API для работы с БД с правильной современной архитектурой. Стоит незамедлительно переводить все свои старые классы на “ORM-рельсы”. Руководство по разработке этих классов достаточно подробное и отвечает на главные вопросы.

Автоматическая генерация ORM-классов

Первое, о чем хочется рассказать в этой статье об ORM — генератор ORM классов. По неизвестной мне причине он тщательно скрыт в недрах панели управления сайтом. Когда пользуешься им, возникает ощущение, что прикасаешься к чему-то запретному 🙂 Хотя в среде разработчиков о нем часто говорят: и на сайте идей, и в блогах.

Чтобы его использовать, нужно открыть страницу Настройки > Производительность > Таблицы и добавить GET-параметр orm=y. Адрес будет выглядеть так: /bitrix/admin/perfmon_tables.php?lang=ru&orm=y

После этого для любой таблицы в БД сайта можно автоматически создавать ORM-класс. Для примера выберем штатную таблицу шаблонов сайта b_site_template.
После перезагрузки страницы имеем следующий код: код в Gist. Результат после небольших преобразований может быть сведен к настоящему классу для работы с шаблонами, расположенному по пути /bitrix/modules/main/lib/sitetemplate.php.

Важная особенность генератора: поля описываются не объектами-потомками BitrixMainEntityField, а ассоциативными массивами. Этот формат считается устаревшим, хотя поддерживается и используется во многих системных классах.

Отличное подспорье для создателей модулей: генератор позволит поставить производство классов на поток.

Работа с удаленными БД

Помимо очевидного, есть под капотом ORM и куда более интересные возможности. Например, работа с разными БД (вертикальный шардинг)! Теперь, заканчивая работу по созданию класса никто не мешает сказать “ах да, эта таблица лежит в другой БД с другим логином и паролем”.

Никаких ограничений по сравнению с “локальными” таблицами нет. По таблицам внешней БД можно точно так же делать выборки, изменять записи, группировать и т.п. Нельзя только пытаться сделать JOIN таблиц в разных БД.

Чтобы указать, в какой БД требуется искать таблицу, в ORM-классе требуется переопределить метод getConnectionName. Здесь указывается псевдоним подключения (по умолчанию “default” — главное подключение, та же БД, в которую установлены штатные таблицы).
Само подключение должно быть вручную прописано в настройках ядра D7 (файл /bitrix/.settings.php), узел connections > value > имя подключения. value >» src=»https://www.intervolga.ru/upload/medialibrary/671/67171749488e8cef98b3a4c4660885f1.png» height=»589″ title=»узел connections > value >»> Если с host, database, login, password вопросов, в принципе, нет, то первый параметр className заслуживает отдельного внимания. Для этого потребуется сделать небольшое отступление и рассказать об организации работы с БД в новом ядре.

Нюансы работы с разными типами БД

В новом ядре изменилась так же работа с БД. “Главными” по этому вопросу стали классы в пространстве имен BitrixMainDB. Конкретно за подключение к БД и выполнение всех запросов отвечают классы семейства BitrixMainDBConnection, а именно:

  • BitrixMainDBOracleConnection — подключение к Oracle-БД с использованием http://php.net/manual/en/book.oci8.php;
  • BitrixMainDBMssqlConnection — подключение к Microsoft SQL-БД с использованием http://php.net/manual/en/book.sqlsrv.php (расширение доступно только в Windows-версии PHP);
  • BitrixMainDBMysqliConnection — подключение к MySQL через http://php.net/manual/en/book.mysqli.php;
  • BitrixMainDBMysqlConnection — подключение к MySQL через http://php.net/manual/en/book.mysql.php(расширение запрещено с PHP 5.5).

Наверное, для 95% сайтов хватает этого набора (а для 90% только BitrixMainDBMysqliConnection). А что же делать, если подключение к БД очень экзотическое?

Например, у заказчика чрезвычайно устаревшая (или наоборот, слишком свежая) версия СУБД и невозможно использовать встроенный драйвер для PHP. В таких случаях на помощь спешат, конечно же, программисты.

Чтобы добавить в 1С-Битрикс: Управление сайтом поддержку нового типа БД, необходимо следовать простой инструкции:

  1. Создать класс подключения (наследник BitrixMainDBConnection). В нем определить все “базовые” операции с БД: подключение, отключение, выполнение произвольного запроса, работу с транзакциями;
  2. Создать класс SQL-хелпер (наследник BitrixMainDBSqlHelper) и возвращать его экземпляр в методе createSqlHelper. Класс предназначен для самой низкоуровневой работы с БД — он добавляет экранирование, работает с датами, предоставляет доступ к базовым SQL-функциям и т.п.;
  3. Создать класс для результата выборки (наследник BitrixMainDBResult). В нем требуется определить методы-обертки над традиционными функциями работы с результатом выборки.

Теперь вы знаете, какие className можно указывать в /bitrix/.settings.php и как создавать собственные подключения к БД.

Реальный пример: использование ORM для подключения из PHP к MSSQL в Linux

Как уже было отмечено выше, класс BitrixMainDBMssqlConnection основан на расширении sqlsrv, которое доступно только на windows-сервере. В одном из наших проектов возникла необходимость подключиться к MSSQL с linux-сервера, то есть решение от 1C-Битрикс нам не подходило (а компиляция драйвера в linux ничем хорошим не закончилась). Помогла природная смекалка и знание ООП.

На сервер было установлено расширение mssql (http: php.net/manual/ru/book.mssql.php) и была разработана следующая архитектура: Был разработан собственный набор MSSQL-классов, многие методы были унаследованы от стандартных BitrixMainDBMssql*. Пришлось буквально в паре десятков мест произвести замены вроде sqlsrv_query => mssql_query. К этому пакету (и способу его получения) вернемся в завершении статьи.

Выбор данных из хранимых процедур вместо таблиц

ORM подходит даже для таких экзотических запросов, как выборка данных не из таблицы, а из хранимых процедур. Такие процедуры могут быть созданы в MSSQL-базе данных. Что ж, попробуем “обмануть” ORM и подсунуть ей процедуру вместо имени таблицы.

Укажем название функции в методе getTableName.
Однако, сразу такой код работать не будет. Дело в том, что при использовании подключения BitrixMainDBMssqlConnection все вхождения имен таблиц проходят через экранирование. Попытка сразу выполнить такой запрос приведет к выбрасыванию исключения:

MS Sql query error: Invalid object name ‘foo_table_procedure()’. (400)

SELECT

[base].[bar] AS [BAR],

[base].[baz] AS [BAZ],

FROM [foo_table_procedure()] [base]

Увы, не получилось. На самом деле, мы в одном шаге от успеха, помешали только знаки “[“ и “]”, которыми MssqlSqlHelper защитил имя используемой “таблицы”. Проблема решается “в лоб” созданием собственного подключения (Connection) и SqlHelper’а.

На сервер было установлено расширение mssql (http: php.net/manual/ru/book.mssql.php) и была разработана следующая архитектура:
Где self::isKnownFunctionCall — метод проверки, который возвращает true, если в $identifier находится “foo_table_procedure()”.

Выводы

Новое ядро D7 уже здесь. Каждый месяц появляются все новые и новые классы, они постепенно заменяют старые. Если в Ваших проектах или модулях есть классы, чья ответственность — предоставление доступа к 1 таблице в БД (локальной или сторонней) — требуется его переписать, поставить “на рельсы» одной из ключевых фич в новом 1С-Битрикс: Управлении сайтом.

Что касается пакета классов для подключения с linux-сервера к MSSQL БД. Если Вы хотите получить этот пакет (3 класса: MssqlConnection, MssqlSqlHelper, MsssqlResult), поделитесь статьей в социальных сетях и заполните форму в конце страницы. Ссылка для скачивания файлов придёт вам на почту.

Источник

В старом ядре

На каждую сущность программируется свой GetList, Update, Add, Delete. В основном копи-пастом. Недостатки: разный набор параметров; разный синтаксис полей фильтров; события могут быть или не быть; иногда разный код под разные БД (Add).

Какая цель поставлена в новом ядре

Сделать операции выборки и сохранения в БД однотипными, с одинаковыми параметрами и фильтрами. По возможности таблицы сущностей должны обслуживаться с минимумом нового кода. Стандартные события добавления/изменения/удаления должны быть доступны автоматически.

Показать скрытое содержание

Реализация

Введены три понятия:
— сущности (BitrixMainEntityBase)
— поля сущностей (BitrixMainEntityField и его наследники)
— датаменеджер (BitrixMainEntityDataManager)

Сущность описывает таблицу в БД, в т.ч. содержит поля сущностей. Датаменеджер производит операции выборки и изменения сущности. На практике работа в основном ведется на уровне датаменеджера.

Пример

Начнем с простого примера использования датаменеджера для своей сущности:

use BitrixMainEntity;

class CultureTable extends EntityDataManager
{
   const LEFT_TO_RIGHT = 'Y';
   const RIGHT_TO_LEFT = 'N';

   public static function getFilePath()
   {
      return __FILE__;
   }

   public static function getTableName()
   {
      return 'b_culture';
   }

   public static function getMap()
   {
      return array(
         'ID' => array(
            'data_type' => 'integer',
            'primary' => true,
            'autocomplete' => true,
         ),
         'CODE' => array(
            'data_type' => 'string',
         ),
         'NAME' => array(
            'data_type' => 'string',
            'required' => true,
            'title' => Loc::getMessage("culture_entity_name"),
         ),
         'FORMAT_DATE' => array(
            'data_type' => 'string',
            'required' => true,
            'title' => Loc::getMessage("culture_entity_date_format"),
         ),
         'FORMAT_DATETIME' => array(
            'data_type' => 'string',
            'required' => true,
            'title' => Loc::getMessage("culture_entity_datetime_format"),
         ),
         'FORMAT_NAME' => array(
            'data_type' => 'string',
            'required' => true,
            'title' => Loc::getMessage("culture_entity_name_format"),
         ),
         'WEEK_START' => array(
            'data_type' => 'integer',
         ),
         'CHARSET' => array(
            'data_type' => 'string',
            'required' => true,
            'title' => Loc::getMessage("culture_entity_charset"),
         ),
         'DIRECTION' => array(
            'data_type' => 'boolean',
            'values' => array(self::RIGHT_TO_LEFT, self::LEFT_TO_RIGHT),
         ),
      );
   }
}

1. Название сущности CultureTable состоит из собственно названия сущности и обязательного суффикса Table. Почему суффикс? Название класса Culture резервируется на будущее для «настоящей» ORM.
2. Класс должен быть наследован от BitrixMainEntityDataManager.
3. Должны быть перекрыты два абстрактных метода:
— getFilePath() возвращает путь к файлу сущности (в TODO есть избавиться от этого)
— getMap() возвращает массив, описывающий поля сущности. О полях ниже.
4. Метод getTableName() может быть определен и должен вернуть название таблицы. Если метод не переопределен, то базовая сущность попытается получить имя таблицы автоматически, например из BitrixSaleOrderTable получилось бы b_sale_order.

Поля сущностей

Рассмотрим подробнее, как описываются поля сущности. Метод getMap() должен вернуть массив полей сущности вида:

array(
   "FIELD_CODE" => array(
      'data_type' => 'тип поля',
      другие атрибуты
   ),
   …
) 

Атрибут data_ type описывает тип поля. Сейчас доступны типы:

boolean (наследует ScalarField)
date (наследует ScalarField)
datetime  (наследует DateField)
enum (наследует ScalarField)
float (наследует ScalarField)
integer (наследует ScalarField)
string (наследует ScalarField)
text (наследует StringField)

«Другие атрибуты» могут зависеть от типа поля.

Первичный ключ указывается с помощью атрибута ‘primary’=> true. Если первичный ключ  — целочисленный с авто увеличением, то нужно указать атрибут ‘autocomplete’ => true.

Атрибут ‘required’ => true указывает на обязательность поля.

Атрибут ‘format’ => ‘паттерн’ валидирует значение по шаблону регулярного выражения, например ‘/^[A-Z][A-Za- z0-9]+$/’.

Атрибут expression позволяет получить вычисляемое значение, например:

 'VAT_RATE_PRC' => array(
   'data_type' => 'float',
   'expression' => array(
      '100 * %s', 'VAT_RATE'
   )
),

Сущности могут быть связаны через поле типа reference. При этом в качестве типа указывается название сущности, на которую идет ссылка, например:

'ORDER' => array(
   'data_type' => 'Order',
   'reference' => array(
      '=this.ORDER_ID' => 'ref.ID'
   )
),

Атрибут values задает словарь для поля типа bool , например:

'DIRECTION' => array(
   'data_type' => 'boolean',
   'values' => array(self::RIGHT_TO_LEFT, self::LEFT_TO_RIGHT),
),

Атрибут title задает текстовое название поля.

Датаменеджер

Как видим, класс сущности практически не содержит кода. Базовый класс датаменеджера дает нам следующие методы:

— CultureTable::update($ID, $arFields)
— CultureTable::add($arFields)
— CultureTable::Delete ($ID)
— и наш любимый CultureTable::getList($arParams)
— есть также базовый checkFields(Result $result, array $data, $id = null)

Обратите внимание, все методы статические. Сущность может переопределить эти базовые методы, если это необходимо.

Выборка данных

Для выборки данных используется статический метод getList(). На вход принимается массив параметров:

‘select’ – массив выбираемых в SELECT полей;
‘filter’ – массив условий фильтра для WHERE;
‘group’ – массив полей для группировки;
‘order’ – массив сортировки;
‘limit’ – ограничение количества записей;
‘offset’ – смещение начала выборки;
‘count_total’ – нужно ли считать количество записей выборки.

Пример использования:

$cultureList = CultureTable::getList(array(
     'select' =>array('ID', 'NAME'),
     'order' => array('NAME' =>'ASC'),
     'filter'=>array('=CHARSET'=>'Windows-1251'),
));

Метод возвращает объект нового ядра BitrixMainDBResult, с помощью которого можно выбирать записи:

$cultureRes = CultureTable::getList(array('order'=>array('name'=>'asc')));
while($cult = $cultureRes->fetch())
{
   var_dump($cult);
}

Сортировка может быть задана по нескольким полям. Фильтр задается в расширенном синтаксисе, который применяется в инфоблоках. Использование параметров, неизвестных методу, приведет к выбросу исключения.

Для выборки одной записи по первичному ключу может применяться метод getById():

$cultureDb = CultureTable::getById($cultureId);

Модификация данных

Статические методы add(), update(), delete() служат для модификации данных сущности и вызывают соответствующие события. Методы add() и update() производят встроенную проверку полей сущностей, вызывая метод checkFields().

Методы модификации данных возвращают объекты типов BitrixMainEntityAddResult, BitrixMainEntityUpdateResult и BitrixMainEntityDeleteResult. Эти классы наследованы от BitrixMainEntityResult. Назначение этих объектов – сообщить об успешности операции и дать доступ к ошибкам.

checkFields()

Метод checkFields() датаменеджера в настоящее время проверяет поля на обязательные значения (атрибут поля сущности required) и соответствие маске (атрибут format). Для дополнительных проверок класс сущности может переопределить этот метод:

public static function checkFields(Result $result, array $data, $id = null)

Параметр $result – объект результата. В случае ошибки метод должен добавить ошибку в этот объект. Ошибка может быть либо общая для всей сущности, либо относиться к конкретному полю. В первом случае добавляется объект типа BitrixMainEntityEntityError, вот втором — BitrixMainEntityFieldError:

$result->addError(new EntityEntityError("Нельзя изменить запись номер 1."));
$result->addError(new EntityFieldError(static::getEntity()->getField('NAME'), 'Поле «имя» очень странное.'));

Если ни одна ошибка не была добавлена, то проверка считается успешной.

Параметр $data содержит массив вида ‘код поля’=>’значение поля’.

Параметр $id содержит значение первичного ключа записи, если производится update, либо не указан, если производится add.

Метод не возвращает результат. Фактический результат содержится в объекте $result.

add()

public static function add(array $data)

Параметр $data содержит массив вида ‘код поля’=>’значение поля’.

Метод возвращает объект типа AddResult. Кроме проверки на успешность, объект результата содержит также идентификатор добавленной записи (только для целочисленных первичных ключей с автоинкрементом). Пример:

     $arFields = array(
              "NAME" => $request['NAME'],
              "FORMAT_DATE" => $request['FORMAT_DATE'],
              "FORMAT_DATETIME" => $request['FORMAT_DATETIME'],
              "WEEK_START" => intval($request["WEEK_START"]),
              "FORMAT_NAME" => CSite::GetNameFormatByValue($request["FORMAT_NAME"]),
              "CHARSET" => $request['CHARSET'],
              "DIRECTION" => $request['DIRECTION'],
              "CODE" => $request['CODE'],
     );
 
     if($ID > 0)
     {
              $result = CultureTable::update($ID, $arFields);
     }
     else
     {
              $result = CultureTable::add($arFields);
              $ID = $result->getId();
     }
 
     if($result->isSuccess())
     {
              if($request["save"] <> '')
                     LocalRedirect(BX_ROOT."/admin/culture_admin.php?lang=".LANGUAGE_ID);
              else
                     LocalRedirect(BX_ROOT."/admin/culture_edit.php?lang=".LANGUAGE_ID."&ID=".$ID."&".$tabControl->ActiveTabParam());
     }
     else
     {
              $errors = $result->getErrorMessages();
     }

update()

public static function update($primary, array $data)

Параметр $primary содержит значение первичного ключа. Для составных ключей можно передать массив значений.

Параметр $data содержит массив вида ‘код поля’=>’значение поля’.

Метод возвращает объект типа UpdateResult. Пример см. выше.

delete()

public static function delete($primary)

Параметр $primary содержит значение первичного ключа. Для составных ключей можно передать массив значений.

Метод возвращает объект типа DeleteResult. Пример:

$result = CultureTable::delete($ID);
if(!$result->isSuccess())
{
          $adminList->AddGroupError("(ID=".$ID.") ".implode("<br>", $result->getErrorMessages()), $ID);
}

Результат

Как видно из примеров, объект результата содержит в себе признак успешности (isSuccess() возвращает булевое значение) и список ошибок (getErrorMessages() возвращает массив строк с ошибками):

if(!$result->isSuccess())
          $errors = $result->getErrorMessages();

Метод getErrors() возвращает массив объектов типов EntityEntityError или EntityFieldError.

Класс AddResult содержит метод getId(), который возвращает идентификатор добавленной записи.

События

Датаменеджер при модификации данных отправляет события.  Названия событий строятся автоматически и имеют вид <КодСущности> On( Before|<пусто>| After)( Add| Update| Delete), например CultureOnBeforeDelete. Отменить операцию могут только обработчики событий OnBefore.

При добавлении записи порядок событий следующий (указаны параметры для обработчика):

OnBeforeAdd (array(«fields»=>$data))
checkFields()
OnAdd(array(«fields»=>$data))
add
OnAfterAdd(array(«id»=>$id, «fields»=>$data))

Если обработчик OnBeforeAdd вернет ошибку, то добавление не будет произведено. О реализации обработчиков см. ниже.

При обновлении записи порядок событий следующий:

OnBeforeUpdate(array(«id»=>$primary, «fields»=>$data))
checkFields()
OnUpdate(array(«id»=>$primary, «fields»=>$data))
update
OnAfterUpdate(array(«id»=>$primary, «fields»=>$data))

Если обработчик OnBeforeUpdate вернет ошибку, то обновление не будет произведено.

При удалении записи порядок событий следующий:

OnBeforeDelete(array(«id»=>$primary))
OnDelete(array(«id»=>$primary))
delete
OnAfterDelete(array(«id»=>$primary))

Если обработчик OnBeforeDelete вернет ошибку, то удаление не будет произведено.

Обработчики событий

Разберем код обработчика на основе примера:

use BitrixMain;
use BitrixMainEntity;
 
$eventManager = MainEventManager::getInstance();
$eventManager->addEventHandler("main", "CultureOnBeforeUpdate", "CultureBeforeUpdate");
 
function CultureBeforeUpdate(EntityEvent $event)
{
   $errors = array();

   $primary = $event->getParameter("id");
   if($primary["ID"] == 1)
   {
      $errors[] = new EntityEntityError("Нельзя изменить запись номер 1.");
   }
   
   $fields = $event->getParameter("fields");
   if($fields["CHARSET"] == "UTF-8")
   {
      $entity = $event->getEntity();
      $errors[] = new EntityFieldError($entity->getField("CHARSET"), "На этом проекте недопустима кодировка UTF-8.");
   }

   $changedFields = array();

   //slightly change encoding
   if($fields["CHARSET"] == "Windows-1252")
   {
      $changedFields["CHARSET"] = "Windows-1251";
   }

   //impossible to change day of week
   $unsetFields = array("WEEK_START");

   $result = new EntityEventResult();
   if(!empty($errors))
   {
      $result->setErrors($errors);
   }
   else
   {
      $result->modifyFields($changedFields);
      $result->unsetFields($unsetFields);
   }

   return $result;
}

Функция обработчика получает на вход экземпляр объекта события EntityDataManagerEvent $event. Класс DataManagerEvent является наследником BitrixMainEvent.

Событие содержит в себе входящие параметры, переданные при отправке события. Получить значение параметра мы можем так:

$primary = $event->getParameter("id");

Либо сразу получить все параметры в виде ассоциативного массива:

$params = $event->getParameters();

Обработчик может вернуть результат в виде объекта типа EntityEventResult (наследник MainEventResult). Результат проверяется только для событий OnBefore, для остальных событий он игнорируется.

Результат по умолчанию создается как успешный. Если нужно вернуть ошибки (и прервать тем самым обновление данных), то используется метод, в который передается массив ошибок:

$result->setErrors($errors);

Массив может содержать объекты типа EntityEntityError (для всей сущности) или EntityFieldError (для поля сущности).

UPD. Теперь обработчик OnBefore может вернуть массив измененных полей (14.0.4). Этот массив мержится с исходными данными.

UPD2. Объект результата события EntityEventResult позволяет указать поля, которые будут изменены или удалены из исходных данных:

$result->modifyFields($changedFields);
$result->unsetFields($unsetFields);

UPD3. Начиная с ядра 14.0.6 объекты результата UpdateResult и AddResult содержат сохраненные данные, доступные через $result->getData().

Открытые члены

  __construct ()
 
  setId ($id)
 
  getId ()
 
  setPrimary ($primary)
 
  getPrimary ()
 
  getResource ()
 
  setReplacedAliases (array $replacedAliases)
 
  addReplacedAliases (array $replacedAliases)
 
  setSerializedFields (array $serializedFields)
 
  addFetchDataModifier ($fetchDataModifier)
 
  fetchRaw ()
 
  fetch (BitrixMainTextConverter $converter=null)
 
  fetchAll (BitrixMainTextConverter $converter=null)
 
  getFields ()
 
  getSelectedRowsCount ()
 
  getTrackerQuery ()
 
  getConverters ()
 
  setConverters ($converters)
 
  setCount ($n)
 
  getCount ()
 
  getIterator ()
 

Защищенные данные

  $primary
 
  $connection
 
  $resource
 
  $trackerQuery = null
 
  $converters = array()
 
  $serializedFields = array()
 
  $replacedAliases = array()
 
  $fetchDataModifiers = array()
 
  $count
 

См. определение в файле addresult.php строка 11

◆ __construct()

См. определение в файле addresult.php строка 16

17 {

18 parent::__construct();

19 }

◆ addFetchDataModifier()

addFetchDataModifier (   $fetchDataModifier )
inherited

Modifier should accept once fetched array as an argument, then modify by link or return new array:

  • function (&$data) { $data[‘AGE’] -= 7; }
  • function ($data) { $data[‘AGE’] -= 7; return $data; }
Аргументы
callable $fetchDataModifier Valid callback.
Возвращает
void
Исключения

BitrixMainArgumentException

Переопределяется в Result.

См. определение в файле result.php строка 123

124 {

125 if (!is_callable($fetchDataModifier))

126 {

127 throw new BitrixMainArgumentException(‘Data Modifier should be a callback’);

128 }

129

130 $this->fetchDataModifiers[] = $fetchDataModifier;

131 }

◆ addReplacedAliases()

addReplacedAliases ( array  $replacedAliases )
inherited

Extends list of aliased columns.

Аргументы
array[string]string $replacedAliases Aliases map from tech to human.
Возвращает
void
См. также
BitrixMainDbResultsetReplacedAliases

Переопределяется в Result.

См. определение в файле result.php строка 96

97 {

98 $this->replacedAliases = array_merge($this->replacedAliases, $replacedAliases);

99 }

◆ fetch()

Fetches one row of the query result and returns it in the associative array of converted data or false on empty data.

Аргументы
BitrixMainTextConverter $converter Optional converter to encode data on fetching.
Возвращает
array|false

Переопределяется в Result.

См. определение в файле result.php строка 167

168 {

170

171 if (!$data)

172 {

173 return false;

174 }

175

176 if ($this->converters)

177 {

178 foreach ($this->converters as $field => $convertDataModifier)

179 {

180 $data[$field] = call_user_func_array($convertDataModifier, array($data[$field]));

181 }

182 }

183

184 if ($this->serializedFields)

185 {

186 foreach ($this->serializedFields as $field)

187 {

188 if (isset($data[$field]))

189 $data[$field] = unserialize($data[$field]);

190 }

191 }

192

193 if ($this->replacedAliases)

194 {

195 foreach ($this->replacedAliases as $tech => $human)

196 {

197 $data[$human] = $data[$tech];

198 unset($data[$tech]);

199 }

200 }

201

202 if ($this->fetchDataModifiers)

203 {

204 foreach ($this->fetchDataModifiers as $fetchDataModifier)

205 {

206 $result = call_user_func_array($fetchDataModifier, array(&$data));

207

208 if (is_array($result))

209 {

210 $data = $result;

211 }

212 }

213 }

214

215 if ($converter != null)

216 {

217 foreach ($data as $key => $val)

218 {

219 $data[$key] = $converter->encode(

220 $val,

221 (isset($data[$key.«_TYPE»])? $data[$key.«_TYPE»] : BitrixMainTextConverter::TEXT)

222 );

223 }

224 }

225

226 return $data;

227 }

◆ fetchAll()

Fetches all the rows of the query result and returns it in the array of associative arrays. Returns an empty array if query has no data.

Аргументы
BitrixMainTextConverter $converter Optional converter to encode data on fetching.
Возвращает
array

Переопределяется в Result.

См. определение в файле result.php строка 237

238 {

239 $res = array();

240 while ($ar = $this->fetch($converter))

241 {

242 $res[] = $ar;

243 }

244 return $res;

245 }

fetch(BitrixMainTextConverter $converter=null)

◆ fetchRaw()

Fetches one row of the query result and returns it in the associative array of raw DB data or false on empty data.

Возвращает
array|false

Переопределяется в Result.

См. определение в файле result.php строка 138

139 {

140 if ($this->trackerQuery != null)

141 {

142 $this->trackerQuery->restartQuery();

143 }

144

146

147 if ($this->trackerQuery != null)

148 {

149 $this->trackerQuery->refinishQuery();

150 }

151

152 if (!$data)

153 {

154 return false;

155 }

156

157 return $data;

158 }

◆ fetchRowInternal()

fetchRowInternal ( )
abstractprotectedinherited

◆ getConverters()

getConverters ( )
inherited

◆ getCount()

Returns record count. It’s required to set record count explicitly before.

Возвращает
int
Исключения

BitrixMainObjectPropertyException

Переопределяется в Result.

См. определение в файле result.php строка 308

309 {

310 if($this->count !== null)

311 {

313 }

314 throw new BitrixMainObjectPropertyException(«count»);

315 }

◆ getFields()

getFields ( )
abstractinherited

◆ getId()

Returns id of added record

Возвращает
int|array

См. определение в файле addresult.php строка 30

31 {

32 if (is_array($this->primary) && count($this->primary) == 1)

33 {

34 return end($this->primary);

35 }

36

38 }

◆ getIterator()

getIterator ( )
inherited

◆ getPrimary()

◆ getResource()

getResource ( )
inherited

Returns database-specific resource of this result.

Возвращает
null|resource

Переопределяется в Result.

См. определение в файле result.php строка 69

◆ getSelectedRowsCount()

getSelectedRowsCount ( )
abstractinherited

◆ getTrackerQuery()

getTrackerQuery ( )
inherited

Returns current query tracker.

Возвращает
BitrixMainDiagSqlTrackerQuery|null

Переопределяется в Result.

См. определение в файле result.php строка 273

◆ setConverters()

setConverters (   $converters )
inherited

◆ setCount()

Sets record count.

Аргументы

Переопределяется в Result.

См. определение в файле result.php строка 298

299 {

300 $this->count = (int)$n;

301 }

◆ setId()

См. определение в файле addresult.php строка 21

22 {

23 $this->primary = array(‘ID’ => $id);

24 }

◆ setPrimary()

◆ setReplacedAliases()

setReplacedAliases ( array  $replacedAliases )
inherited

Sets list of aliased columns. This allows to overcome database limits on length of the column names.

Аргументы
array[string]string $replacedAliases Aliases map from tech to human.
Возвращает
void
См. также
BitrixMainDbResultaddReplacedAliases

Переопределяется в Result.

См. определение в файле result.php строка 83

◆ setSerializedFields()

setSerializedFields ( array  $serializedFields )
inherited

Sets internal list of fields which will be unserialized on fetch.

Аргументы
array $serializedFields List of fields.
Возвращает
void

Переопределяется в Result.

См. определение в файле result.php строка 108

◆ $connection

◆ $converters

◆ $count

◆ $fetchDataModifiers

$fetchDataModifiers = array()

protectedinherited

◆ $primary

◆ $replacedAliases

$replacedAliases = array()

protectedinherited

◆ $resource

Bitrix Main DB Result $resource

protectedinherited

◆ $serializedFields

$serializedFields = array()

protectedinherited

◆ $trackerQuery


Объявления и описания членов класса находятся в файле:

  • C:/Bitrix/modules/main/lib/orm/data/addresult.php

Реализация ORM в ядре D7 призвана абстрагировать разработчика от механики работы с таблицами на уровне запросов к БД, введя понятие сущности и поля сущности.

  • Сущность это таблица
  • Поля сущности столбцы или «ссылки» на другие сущности
  • DataManager класс системы управления данными от которого наследуемся

Нужно понимать, работа в ORM возможна только если есть класс описывающий таблицу, с которой нужно работать. Класс может быть сгенерирован и находиться в ядре, а может и отсутствовать. Всё зависит от таблицы данных с которой нужно работать. Работу можно разделить на следующие этапы:

  1. В папочке bitrix пытаемся найти класс, если класс удалось найти значит продолжаем работать с файлом в котором он описан
  2. Если в папочке bitrix не удалось найти класс, значит его нужно сгенерировать, как это сделать автоматически написано ниже в статье
  3. Нужно убедиться что система видит класс, если класс не подключен его нужно подключить, есть три варианта:
    • через автозагрузку классов
    • через Composer
    • через модуль
  4. Пишем необходимые запросы используя API bitrix и обрабатываем их

В классе обязательно должны быть два метода:

  • getMap() перечисляются все поля таблицы
  • getTableName() указывается название базы данных

Пример класса через который можно работать с таблицей b_iblock:

/bitrix/modules/iblock/lib/element.php<?
namespace BitrixIblock;

use BitrixMainLocalizationLoc,
	BitrixMainORMDataDataManager,
	BitrixMainORMFields,
	BitrixMainType;

Loc::loadMessages(__FILE__);

/**
 * Class IblockTable
 * 
 * Fields:
 * <ul>
 * <li> ID int mandatory
 * <li> TIMESTAMP_X datetime optional default current datetime
 * <li> IBLOCK_TYPE_ID string(50) mandatory
 * <li> LID string(2) mandatory
 * <li> CODE string(50) optional
 * <li> API_CODE string(50) optional
 * <li> REST_ON bool ('N', 'Y') optional default 'N'
 * <li> NAME string(255) mandatory
 * <li> ACTIVE bool ('N', 'Y') optional default 'Y'
 * <li> SORT int optional default 500
 * <li> LIST_PAGE_URL string(255) optional
 * <li> DETAIL_PAGE_URL string(255) optional
 * <li> SECTION_PAGE_URL string(255) optional
 * <li> CANONICAL_PAGE_URL string(255) optional
 * <li> PICTURE int optional
 * <li> DESCRIPTION text optional
 * <li> DESCRIPTION_TYPE enum ('text', 'html') optional default 'text'
 * <li> RSS_TTL int optional default 24
 * <li> RSS_ACTIVE bool ('N', 'Y') optional default 'Y'
 * <li> RSS_FILE_ACTIVE bool ('N', 'Y') optional default 'N'
 * <li> RSS_FILE_LIMIT int optional
 * <li> RSS_FILE_DAYS int optional
 * <li> RSS_YANDEX_ACTIVE bool ('N', 'Y') optional default 'N'
 * <li> XML_ID string(255) optional
 * <li> TMP_ID string(40) optional
 * <li> INDEX_ELEMENT bool ('N', 'Y') optional default 'Y'
 * <li> INDEX_SECTION bool ('N', 'Y') optional default 'N'
 * <li> WORKFLOW bool ('N', 'Y') optional default 'Y'
 * <li> BIZPROC bool ('N', 'Y') optional default 'N'
 * <li> SECTION_CHOOSER string(1) optional
 * <li> LIST_MODE string(1) optional
 * <li> RIGHTS_MODE string(1) optional
 * <li> SECTION_PROPERTY string(1) optional
 * <li> PROPERTY_INDEX string(1) optional
 * <li> VERSION int optional default 1
 * <li> LAST_CONV_ELEMENT int optional default 0
 * <li> SOCNET_GROUP_ID int optional
 * <li> EDIT_FILE_BEFORE string(255) optional
 * <li> EDIT_FILE_AFTER string(255) optional
 * <li> SECTIONS_NAME string(100) optional
 * <li> SECTION_NAME string(100) optional
 * <li> ELEMENTS_NAME string(100) optional
 * <li> ELEMENT_NAME string(100) optional
 * <li> PICTURE reference to {@link BitrixFileFileTable}
 * <li> IBLOCK_TYPE_ID reference to {@link BitrixIblockIblockTypeTable}
 * <li> LID reference to {@link BitrixLangLangTable}
 * <li> SOCNET_GROUP_ID reference to {@link BitrixSonetSonetGroupTable}
 * </ul>
 *
 * @package BitrixIblock
 **/

class IblockTable extends DataManager
{
	/**
	 * Returns DB table name for entity.
	 *
	 * @return string
	 */
	public static function getTableName()
	{
		return 'b_iblock';
	}

	/**
	 * Returns entity map definition.
	 *
	 * @return array
	 */
	public static function getMap()
	{
		return [
			new FieldsIntegerField(
				'ID',
				[
					'primary' => true,
					'autocomplete' => true,
					'title' => Loc::getMessage('IBLOCK_ENTITY_ID_FIELD')
				]
			),
			new FieldsDatetimeField(
				'TIMESTAMP_X',
				[
					'default' => function()
					{
						return new TypeDateTime();
					},
					'title' => Loc::getMessage('IBLOCK_ENTITY_TIMESTAMP_X_FIELD')
				]
			),
			new FieldsStringField(
				'IBLOCK_TYPE_ID',
				[
					'required' => true,
					'validation' => [__CLASS__, 'validateIblockTypeId'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_IBLOCK_TYPE_ID_FIELD')
				]
			),
			new FieldsStringField(
				'LID',
				[
					'required' => true,
					'validation' => [__CLASS__, 'validateLid'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_LID_FIELD')
				]
			),
			new FieldsStringField(
				'CODE',
				[
					'validation' => [__CLASS__, 'validateCode'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_CODE_FIELD')
				]
			),
			new FieldsStringField(
				'API_CODE',
				[
					'validation' => [__CLASS__, 'validateApiCode'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_API_CODE_FIELD')
				]
			),
			new FieldsBooleanField(
				'REST_ON',
				[
					'values' => array('N', 'Y'),
					'default' => 'N',
					'title' => Loc::getMessage('IBLOCK_ENTITY_REST_ON_FIELD')
				]
			),
			new FieldsStringField(
				'NAME',
				[
					'required' => true,
					'validation' => [__CLASS__, 'validateName'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_NAME_FIELD')
				]
			),
			new FieldsBooleanField(
				'ACTIVE',
				[
					'values' => array('N', 'Y'),
					'default' => 'Y',
					'title' => Loc::getMessage('IBLOCK_ENTITY_ACTIVE_FIELD')
				]
			),
			new FieldsIntegerField(
				'SORT',
				[
					'default' => 500,
					'title' => Loc::getMessage('IBLOCK_ENTITY_SORT_FIELD')
				]
			),
			new FieldsStringField(
				'LIST_PAGE_URL',
				[
					'validation' => [__CLASS__, 'validateListPageUrl'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_LIST_PAGE_URL_FIELD')
				]
			),
			new FieldsStringField(
				'DETAIL_PAGE_URL',
				[
					'validation' => [__CLASS__, 'validateDetailPageUrl'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_DETAIL_PAGE_URL_FIELD')
				]
			),
			new FieldsStringField(
				'SECTION_PAGE_URL',
				[
					'validation' => [__CLASS__, 'validateSectionPageUrl'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_SECTION_PAGE_URL_FIELD')
				]
			),
			new FieldsStringField(
				'CANONICAL_PAGE_URL',
				[
					'validation' => [__CLASS__, 'validateCanonicalPageUrl'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_CANONICAL_PAGE_URL_FIELD')
				]
			),
			new FieldsIntegerField(
				'PICTURE',
				[
					'title' => Loc::getMessage('IBLOCK_ENTITY_PICTURE_FIELD')
				]
			),
			new FieldsTextField(
				'DESCRIPTION',
				[
					'title' => Loc::getMessage('IBLOCK_ENTITY_DESCRIPTION_FIELD')
				]
			),
			new FieldsStringField(
				'DESCRIPTION_TYPE',
				[
					'values' => array('text', 'html'),
					'default' => 'text',
					'title' => Loc::getMessage('IBLOCK_ENTITY_DESCRIPTION_TYPE_FIELD')
				]
			),
			new FieldsIntegerField(
				'RSS_TTL',
				[
					'default' => 24,
					'title' => Loc::getMessage('IBLOCK_ENTITY_RSS_TTL_FIELD')
				]
			),
			new FieldsBooleanField(
				'RSS_ACTIVE',
				[
					'values' => array('N', 'Y'),
					'default' => 'Y',
					'title' => Loc::getMessage('IBLOCK_ENTITY_RSS_ACTIVE_FIELD')
				]
			),
			new FieldsBooleanField(
				'RSS_FILE_ACTIVE',
				[
					'values' => array('N', 'Y'),
					'default' => 'N',
					'title' => Loc::getMessage('IBLOCK_ENTITY_RSS_FILE_ACTIVE_FIELD')
				]
			),
			new FieldsIntegerField(
				'RSS_FILE_LIMIT',
				[
					'title' => Loc::getMessage('IBLOCK_ENTITY_RSS_FILE_LIMIT_FIELD')
				]
			),
			new FieldsIntegerField(
				'RSS_FILE_DAYS',
				[
					'title' => Loc::getMessage('IBLOCK_ENTITY_RSS_FILE_DAYS_FIELD')
				]
			),
			new FieldsBooleanField(
				'RSS_YANDEX_ACTIVE',
				[
					'values' => array('N', 'Y'),
					'default' => 'N',
					'title' => Loc::getMessage('IBLOCK_ENTITY_RSS_YANDEX_ACTIVE_FIELD')
				]
			),
			new FieldsStringField(
				'XML_ID',
				[
					'validation' => [__CLASS__, 'validateXmlId'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_XML_ID_FIELD')
				]
			),
			new FieldsStringField(
				'TMP_ID',
				[
					'validation' => [__CLASS__, 'validateTmpId'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_TMP_ID_FIELD')
				]
			),
			new FieldsBooleanField(
				'INDEX_ELEMENT',
				[
					'values' => array('N', 'Y'),
					'default' => 'Y',
					'title' => Loc::getMessage('IBLOCK_ENTITY_INDEX_ELEMENT_FIELD')
				]
			),
			new FieldsBooleanField(
				'INDEX_SECTION',
				[
					'values' => array('N', 'Y'),
					'default' => 'N',
					'title' => Loc::getMessage('IBLOCK_ENTITY_INDEX_SECTION_FIELD')
				]
			),
			new FieldsBooleanField(
				'WORKFLOW',
				[
					'values' => array('N', 'Y'),
					'default' => 'Y',
					'title' => Loc::getMessage('IBLOCK_ENTITY_WORKFLOW_FIELD')
				]
			),
			new FieldsBooleanField(
				'BIZPROC',
				[
					'values' => array('N', 'Y'),
					'default' => 'N',
					'title' => Loc::getMessage('IBLOCK_ENTITY_BIZPROC_FIELD')
				]
			),
			new FieldsStringField(
				'SECTION_CHOOSER',
				[
					'validation' => [__CLASS__, 'validateSectionChooser'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_SECTION_CHOOSER_FIELD')
				]
			),
			new FieldsStringField(
				'LIST_MODE',
				[
					'validation' => [__CLASS__, 'validateListMode'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_LIST_MODE_FIELD')
				]
			),
			new FieldsStringField(
				'RIGHTS_MODE',
				[
					'validation' => [__CLASS__, 'validateRightsMode'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_RIGHTS_MODE_FIELD')
				]
			),
			new FieldsStringField(
				'SECTION_PROPERTY',
				[
					'validation' => [__CLASS__, 'validateSectionProperty'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_SECTION_PROPERTY_FIELD')
				]
			),
			new FieldsStringField(
				'PROPERTY_INDEX',
				[
					'validation' => [__CLASS__, 'validatePropertyIndex'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_PROPERTY_INDEX_FIELD')
				]
			),
			new FieldsIntegerField(
				'VERSION',
				[
					'default' => 1,
					'title' => Loc::getMessage('IBLOCK_ENTITY_VERSION_FIELD')
				]
			),
			new FieldsIntegerField(
				'LAST_CONV_ELEMENT',
				[
					'default' => 0,
					'title' => Loc::getMessage('IBLOCK_ENTITY_LAST_CONV_ELEMENT_FIELD')
				]
			),
			new FieldsIntegerField(
				'SOCNET_GROUP_ID',
				[
					'title' => Loc::getMessage('IBLOCK_ENTITY_SOCNET_GROUP_ID_FIELD')
				]
			),
			new FieldsStringField(
				'EDIT_FILE_BEFORE',
				[
					'validation' => [__CLASS__, 'validateEditFileBefore'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_EDIT_FILE_BEFORE_FIELD')
				]
			),
			new FieldsStringField(
				'EDIT_FILE_AFTER',
				[
					'validation' => [__CLASS__, 'validateEditFileAfter'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_EDIT_FILE_AFTER_FIELD')
				]
			),
			new FieldsStringField(
				'SECTIONS_NAME',
				[
					'validation' => [__CLASS__, 'validateSectionsName'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_SECTIONS_NAME_FIELD')
				]
			),
			new FieldsStringField(
				'SECTION_NAME',
				[
					'validation' => [__CLASS__, 'validateSectionName'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_SECTION_NAME_FIELD')
				]
			),
			new FieldsStringField(
				'ELEMENTS_NAME',
				[
					'validation' => [__CLASS__, 'validateElementsName'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_ELEMENTS_NAME_FIELD')
				]
			),
			new FieldsStringField(
				'ELEMENT_NAME',
				[
					'validation' => [__CLASS__, 'validateElementName'],
					'title' => Loc::getMessage('IBLOCK_ENTITY_ELEMENT_NAME_FIELD')
				]
			),
			new FieldsRelationsReference(
				'FILE',
				'BitrixFileFile',
				['=this.PICTURE' => 'ref.ID'],
				['join_type' => 'LEFT']
			),
			new FieldsRelationsReference(
				'IBLOCK_TYPE',
				'BitrixIblockIblockType',
				['=this.IBLOCK_TYPE_ID' => 'ref.ID'],
				['join_type' => 'LEFT']
			),
			new FieldsRelationsReference(
				'LANG',
				'BitrixLangLang',
				['=this.LID' => 'ref.LID'],
				['join_type' => 'LEFT']
			),
			new FieldsRelationsReference(
				'SOCNET_GROUP',
				'BitrixSonetSonetGroup',
				['=this.SOCNET_GROUP_ID' => 'ref.ID'],
				['join_type' => 'LEFT']
			),
		];
	}

	/**
	 * Returns validators for IBLOCK_TYPE_ID field.
	 *
	 * @return array
	 */
	public static function validateIblockTypeId()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 50),
		];
	}

	/**
	 * Returns validators for LID field.
	 *
	 * @return array
	 */
	public static function validateLid()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 2),
		];
	}

	/**
	 * Returns validators for CODE field.
	 *
	 * @return array
	 */
	public static function validateCode()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 50),
		];
	}

	/**
	 * Returns validators for API_CODE field.
	 *
	 * @return array
	 */
	public static function validateApiCode()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 50),
		];
	}

	/**
	 * Returns validators for NAME field.
	 *
	 * @return array
	 */
	public static function validateName()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 255),
		];
	}

	/**
	 * Returns validators for LIST_PAGE_URL field.
	 *
	 * @return array
	 */
	public static function validateListPageUrl()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 255),
		];
	}

	/**
	 * Returns validators for DETAIL_PAGE_URL field.
	 *
	 * @return array
	 */
	public static function validateDetailPageUrl()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 255),
		];
	}

	/**
	 * Returns validators for SECTION_PAGE_URL field.
	 *
	 * @return array
	 */
	public static function validateSectionPageUrl()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 255),
		];
	}

	/**
	 * Returns validators for CANONICAL_PAGE_URL field.
	 *
	 * @return array
	 */
	public static function validateCanonicalPageUrl()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 255),
		];
	}

	/**
	 * Returns validators for XML_ID field.
	 *
	 * @return array
	 */
	public static function validateXmlId()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 255),
		];
	}

	/**
	 * Returns validators for TMP_ID field.
	 *
	 * @return array
	 */
	public static function validateTmpId()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 40),
		];
	}

	/**
	 * Returns validators for SECTION_CHOOSER field.
	 *
	 * @return array
	 */
	public static function validateSectionChooser()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 1),
		];
	}

	/**
	 * Returns validators for LIST_MODE field.
	 *
	 * @return array
	 */
	public static function validateListMode()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 1),
		];
	}

	/**
	 * Returns validators for RIGHTS_MODE field.
	 *
	 * @return array
	 */
	public static function validateRightsMode()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 1),
		];
	}

	/**
	 * Returns validators for SECTION_PROPERTY field.
	 *
	 * @return array
	 */
	public static function validateSectionProperty()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 1),
		];
	}

	/**
	 * Returns validators for PROPERTY_INDEX field.
	 *
	 * @return array
	 */
	public static function validatePropertyIndex()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 1),
		];
	}

	/**
	 * Returns validators for EDIT_FILE_BEFORE field.
	 *
	 * @return array
	 */
	public static function validateEditFileBefore()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 255),
		];
	}

	/**
	 * Returns validators for EDIT_FILE_AFTER field.
	 *
	 * @return array
	 */
	public static function validateEditFileAfter()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 255),
		];
	}

	/**
	 * Returns validators for SECTIONS_NAME field.
	 *
	 * @return array
	 */
	public static function validateSectionsName()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 100),
		];
	}

	/**
	 * Returns validators for SECTION_NAME field.
	 *
	 * @return array
	 */
	public static function validateSectionName()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 100),
		];
	}

	/**
	 * Returns validators for ELEMENTS_NAME field.
	 *
	 * @return array
	 */
	public static function validateElementsName()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 100),
		];
	}

	/**
	 * Returns validators for ELEMENT_NAME field.
	 *
	 * @return array
	 */
	public static function validateElementName()
	{
		return [
			new FieldsValidatorsLengthValidator(null, 100),
		];
	}
}

Автоматическая генерация класса

Для использования генератора ORM классов перейдите на страницу Настройки -> Настройки продукта -> Настройки модулей -> Монитор производительности модуль Монитор производительности должен быть установлен. На вкладке Генератор таблетов отметьте поле Разрешить генерацию таблетов для ORM.

Включение возможности генерации ORM класса

Автоматически сгенерировать класс с описанием любой таблицы можно на странице Настройки -> Производительность -> Таблицы, в меню действий доступен пункт ORM

Генератор ORM классов

Операции с сущностями

Для работы с таблицей стандартными методами D7 доступны следующие методы:

  • getList выполняет запрос и возвращает отобранные по параметрам запроса данные
  • add добавляет новый элемент
  • update обновляет строку в таблице
  • delete удаляет строку в таблице сущности по первичному ключу

GetList

Пример запроса:

// подключаем модуль, через модуль подключается класс IblockTable
BitrixMainLoader::IncludeModule("iblock");
// делаем запрос на выборку данных getList
$res = BitrixIblockIblockTable::getList(array(
'select' => array('ID')
));
// перебираем массив и выводим на экран
while ($arr = $res->fetch()) {
PR($arr);
}

Операторы:

IblockTable::getList(array(
'select' => array('ISBN', 'TITLE', 'PUBLISH_DATE'), // имена полей, которые необходимо получить
'filter' => array('=ID' => 1), // описание фильтра для WHERE и HAVING
'group' => array('PUBLISH_DATE'), // явное указание полей, по которым нужно группировать результат
'order' => array('PUBLISH_DATE' => 'DESC', 'TITLE' => 'ASC') // параметры сортировки
'limit' => 10, // количество записей
'offset' => 80, // смещение для limit
'runtime' => 'runtime' => array(new EntityExpressionField('CNT', 'COUNT(*)')) // динамически определенные поля
));CODE

Полный список операторов сравнения, которые можно использовать в filter:

'filter' => array('=ID' => 1)
  • = равно (работает и с массивами)
  • % подстрока
  • > больше
  • < меньше
  • @ IN (EXPR) в качестве значения передается массив или объект
  • !@ NOT IN (EXPR)в качестве значения передается массив или объект
  • != не равно
  • !% не подстрока
  • >< между, в качестве значения передается массив array(MIN, MAX)
  • >= больше или равно
  • <= меньше или равно
  • =% LIKE
  • %= LIKE
  • == булевое выражение для ExpressionField (например, для EXISTS() или NOT EXISTS())
  • !>< не между, в качестве значения передается массив array(MIN, MAX)
  • !=% NOT LIKE
  • !%= NOT LIKE
  • '==ID' => null условие, что поле ID равно NULL (в sql-запросе будет преобразовано в ID IS NULL)
  • '!==NAME' => null условие, что поле NAME не равно NULL (в sql-запросе будет преобразовано в NAME IS NOT NULL)

Add

Метод для добавления записи принимает на вход массив со значениями, где ключи — имена полей сущности:

// подключаем модуль, через модуль подключается класс IblockTable
BitrixMainLoader::IncludeModule("iblock");
// делаем запрос на вставку данных add
$res = BitrixIblockIblockTable::add(array(
'NAME' => 'Тест'
));

Update

Обновление записи происходит похожим образом, только к массиву значений в параметрах добавляется значение первичного ключа:

// подключаем модуль, через модуль подключается класс IblockTable
BitrixMainLoader::IncludeModule("iblock");
// делаем запрос на тзменение поля NAME в записи с ID 1
$res = BitrixIblockIblockTable::update(1, array(
'NAME' => 'Тест'
));

Delete

Для удаления записи нужен только первичный ключ:

// подключаем модуль, через модуль подключается класс IblockTable
BitrixMainLoader::IncludeModule("iblock");
// делаем запрос на удаление записи с ID 1
$res = BitrixIblockIblockTable::delete(1);

Вывод запроса на экран

Чтобы понять причину «неправильно» работающей выборки, нужно посмотреть какой sql-запрос формируетя. Рассмотрим на примере выборок из ORM ядра D7 в битриксе:

// подключаем класс для вывода SQL запроса
BitrixMainApplication::getConnection()->startTracker();
// подключаем модуль, через модуль подключается класс IblockTable
BitrixMainLoader::IncludeModule("iblock");
// делаем запрос на выборку данных getList
$res = BitrixIblockIblockTable::getList(array(
'select' => array('ID')
));
// перебираем массив и выводим на экран
while ($arr = $res->fetch()) {
PR($arr);
}
// выводим на экран SQL запрос
echo '<pre>', $res->getTrackerQuery()->getSql(), '</pre>';

Реализация ORM в ядре D7 призвана абстрагировать разработчика от механики работы с таблицами на уровне запросов к БД, введя понятие сущности и поля сущности. Сущность — это таблица, поля сущности — столбцы или «ссылки» на другие сущности, а DataManager — система управления данными. Для каждой сущности нужно создать описание, например:

<?php
/*
 * Файл /bitrix/modules/iblock/lib/element.php
 */

namespace BitrixIblock;

use BitrixMain,
    BitrixMainLocalizationLoc;
Loc::loadMessages(__FILE__);

/**
 * Class ElementTable
 * 
 * Fields:
 * ID int mandatory
 * TIMESTAMP_X datetime optional
 * MODIFIED_BY int optional
 * DATE_CREATE datetime optional
 * CREATED_BY int optional
 * IBLOCK_ID int mandatory
 * IBLOCK_SECTION_ID int optional
 * ACTIVE bool optional default 'Y'
 * ACTIVE_FROM datetime optional
 * ACTIVE_TO datetime optional
 * SORT int optional default 500
 * NAME string(255) mandatory
 * PREVIEW_PICTURE int optional
 * PREVIEW_TEXT string optional
 * PREVIEW_TEXT_TYPE enum ('text', 'html') optional default 'text'
 * DETAIL_PICTURE int optional
 * DETAIL_TEXT string optional
 * DETAIL_TEXT_TYPE enum ('text', 'html') optional default 'text'
 * SEARCHABLE_CONTENT string optional
 * WF_STATUS_ID int optional default 1
 * WF_PARENT_ELEMENT_ID int optional
 * WF_NEW string(1) optional
 * WF_LOCKED_BY int optional
 * WF_DATE_LOCK datetime optional
 * WF_COMMENTS string optional
 * IN_SECTIONS bool optional default 'N'
 * XML_ID string(255) optional
 * CODE string(255) optional
 * TAGS string(255) optional
 * TMP_ID string(40) optional
 * WF_LAST_HISTORY_ID int optional
 * SHOW_COUNTER int optional
 * SHOW_COUNTER_START datetime optional
 * PREVIEW_PICTURE reference to {@link BitrixFileFileTable}
 * DETAIL_PICTURE reference to {@link BitrixFileFileTable}
 * IBLOCK reference to {@link BitrixIblockIblockTable}
 * WF_PARENT_ELEMENT reference to {@link BitrixIblockIblockElementTable}
 * IBLOCK_SECTION reference to {@link BitrixIblockIblockSectionTable}
 * MODIFIED_BY reference to {@link BitrixUserUserTable}
 * CREATED_BY reference to {@link BitrixUserUserTable}
 * WF_LOCKED_BY reference to {@link BitrixUserUserTable}
 *
 * @package BitrixIblock
 **/

class ElementTable extends MainEntityDataManager
{
    /**
     * Returns DB table name for entity.
     *
     * @return string
     */
    public static function getTableName()
    {
        return 'b_iblock_element';
    }

    /**
     * Returns entity map definition.
     *
     * @return array
     */
    public static function getMap()
    {
        return array(
            'ID' => array( // Идентификатор
                'data_type' => 'integer',
                'primary' => true,
                'autocomplete' => true,
                'title' => Loc::getMessage('ELEMENT_ENTITY_ID_FIELD'),
            ),
            'TIMESTAMP_X' => array( // Дата изменения
                'data_type' => 'datetime',
                'title' => Loc::getMessage('ELEMENT_ENTITY_TIMESTAMP_X_FIELD'),
            ),
            'MODIFIED_BY' => array( // Кто изменил
                'data_type' => 'integer',
                'title' => Loc::getMessage('ELEMENT_ENTITY_MODIFIED_BY_FIELD'),
            ),
            'DATE_CREATE' => array( // Дата создания
                'data_type' => 'datetime',
                'title' => Loc::getMessage('ELEMENT_ENTITY_DATE_CREATE_FIELD'),
            ),
            'CREATED_BY' => array( // Кто создал
                'data_type' => 'integer',
                'title' => Loc::getMessage('ELEMENT_ENTITY_CREATED_BY_FIELD'),
            ),
            'IBLOCK_ID' => array( // Идентификатор инфоблока
                'data_type' => 'integer',
                'required' => true,
                'title' => Loc::getMessage('ELEMENT_ENTITY_IBLOCK_ID_FIELD'),
            ),
            'IBLOCK_SECTION_ID' => array( // Основной раздел
                'data_type' => 'integer',
                'title' => Loc::getMessage('ELEMENT_ENTITY_IBLOCK_SECTION_ID_FIELD'),
            ),
            'ACTIVE' => array( // Активность
                'data_type' => 'boolean',
                'values' => array('N', 'Y'),
                'title' => Loc::getMessage('ELEMENT_ENTITY_ACTIVE_FIELD'),
            ),
            'ACTIVE_FROM' => array( // Дата начала активности
                'data_type' => 'datetime',
                'title' => Loc::getMessage('ELEMENT_ENTITY_ACTIVE_FROM_FIELD'),
            ),
            'ACTIVE_TO' => array( // Дата окончания активности
                'data_type' => 'datetime',
                'title' => Loc::getMessage('ELEMENT_ENTITY_ACTIVE_TO_FIELD'),
            ),
            'SORT' => array( // Индекс сортировки
                'data_type' => 'integer',
                'title' => Loc::getMessage('ELEMENT_ENTITY_SORT_FIELD'),
            ),
            'NAME' => array( // Наименование
                'data_type' => 'string',
                'required' => true,
                'validation' => array(__CLASS__, 'validateName'),
                'title' => Loc::getMessage('ELEMENT_ENTITY_NAME_FIELD'),
            ),
            'PREVIEW_PICTURE' => array( // Картинка анонса
                'data_type' => 'integer',
                'title' => Loc::getMessage('ELEMENT_ENTITY_PREVIEW_PICTURE_FIELD'),
            ),
            'PREVIEW_TEXT' => array( // Описание для анонса
                'data_type' => 'text',
                'title' => Loc::getMessage('ELEMENT_ENTITY_PREVIEW_TEXT_FIELD'),
            ),
            'PREVIEW_TEXT_TYPE' => array( // Тип описания для анонса
                'data_type' => 'enum',
                'values' => array('text', 'html'),
                'title' => Loc::getMessage('ELEMENT_ENTITY_PREVIEW_TEXT_TYPE_FIELD'),
            ),
            'DETAIL_PICTURE' => array( // Детальная картинка
                'data_type' => 'integer',
                'title' => Loc::getMessage('ELEMENT_ENTITY_DETAIL_PICTURE_FIELD'),
            ),
            'DETAIL_TEXT' => array( // Детальное описание
                'data_type' => 'text',
                'title' => Loc::getMessage('ELEMENT_ENTITY_DETAIL_TEXT_FIELD'),
            ),
            'DETAIL_TEXT_TYPE' => array( // Тип детального описания
                'data_type' => 'enum',
                'values' => array('text', 'html'),
                'title' => Loc::getMessage('ELEMENT_ENTITY_DETAIL_TEXT_TYPE_FIELD'),
            ),
            'SEARCHABLE_CONTENT' => array( // Поисковый индекс
                'data_type' => 'text',
                'title' => Loc::getMessage('ELEMENT_ENTITY_SEARCHABLE_CONTENT_FIELD'),
            ),
            'WF_STATUS_ID' => array( // Статус в документообороте
                'data_type' => 'integer',
                'title' => Loc::getMessage('ELEMENT_ENTITY_WF_STATUS_ID_FIELD'),
            ),
            'WF_PARENT_ELEMENT_ID' => array( // Элемент-родитель
                'data_type' => 'integer',
                'title' => Loc::getMessage('ELEMENT_ENTITY_WF_PARENT_ELEMENT_ID_FIELD'),
            ),
            'WF_NEW' => array( // Флаг публикации черновика
                'data_type' => 'string',
                'validation' => array(__CLASS__, 'validateWfNew'),
                'title' => Loc::getMessage('ELEMENT_ENTITY_WF_NEW_FIELD'),
            ),
            'WF_LOCKED_BY' => array( // Кто заблокировал
                'data_type' => 'integer',
                'title' => Loc::getMessage('ELEMENT_ENTITY_WF_LOCKED_BY_FIELD'),
            ),
            'WF_DATE_LOCK' => array( // Дата блокировки
                'data_type' => 'datetime',
                'title' => Loc::getMessage('ELEMENT_ENTITY_WF_DATE_LOCK_FIELD'),
            ),
            'WF_COMMENTS' => array( // Комментарий документооборота
                'data_type' => 'text',
                'title' => Loc::getMessage('ELEMENT_ENTITY_WF_COMMENTS_FIELD'),
            ),
            'IN_SECTIONS' => array( // Входит в разделы инфоблока
                'data_type' => 'boolean',
                'values' => array('N', 'Y'),
                'title' => Loc::getMessage('ELEMENT_ENTITY_IN_SECTIONS_FIELD'),
            ),
            'XML_ID' => array( // Внешний код
                'data_type' => 'string',
                'validation' => array(__CLASS__, 'validateXmlId'),
                'title' => Loc::getMessage('ELEMENT_ENTITY_XML_ID_FIELD'),
            ),
            'CODE' => array( // Символьный код
                'data_type' => 'string',
                'validation' => array(__CLASS__, 'validateCode'),
                'title' => Loc::getMessage('ELEMENT_ENTITY_CODE_FIELD'),
            ),
            'TAGS' => array( // Теги
                'data_type' => 'string',
                'validation' => array(__CLASS__, 'validateTags'),
                'title' => Loc::getMessage('ELEMENT_ENTITY_TAGS_FIELD'),
            ),
            'TMP_ID' => array( // Временный код
                'data_type' => 'string',
                'validation' => array(__CLASS__, 'validateTmpId'),
                'title' => Loc::getMessage('ELEMENT_ENTITY_TMP_ID_FIELD'),
            ),
            'WF_LAST_HISTORY_ID' => array(
                'data_type' => 'integer',
                'title' => Loc::getMessage('ELEMENT_ENTITY_WF_LAST_HISTORY_ID_FIELD'),
            ),
            'SHOW_COUNTER' => array( // Количество показов
                'data_type' => 'integer',
                'title' => Loc::getMessage('ELEMENT_ENTITY_SHOW_COUNTER_FIELD'),
            ),
            'SHOW_COUNTER_START' => array( // Дата первого показа
                'data_type' => 'datetime',
                'title' => Loc::getMessage('ELEMENT_ENTITY_SHOW_COUNTER_START_FIELD'),
            ),
            'PREVIEW_PICTURE' => array(
                'data_type' => 'BitrixFileFile',
                'reference' => array('=this.PREVIEW_PICTURE' => 'ref.ID'),
            ),
            'DETAIL_PICTURE' => array(
                'data_type' => 'BitrixFileFile',
                'reference' => array('=this.DETAIL_PICTURE' => 'ref.ID'),
            ),
            'IBLOCK' => array(
                'data_type' => 'BitrixIblockIblock',
                'reference' => array('=this.IBLOCK_ID' => 'ref.ID'),
            ),
            'WF_PARENT_ELEMENT' => array(
                'data_type' => 'BitrixIblockIblockElement',
                'reference' => array('=this.WF_PARENT_ELEMENT_ID' => 'ref.ID'),
            ),
            'IBLOCK_SECTION' => array(
                'data_type' => 'BitrixIblockIblockSection',
                'reference' => array('=this.IBLOCK_SECTION_ID' => 'ref.ID'),
            ),
            'MODIFIED_BY' => array(
                'data_type' => 'BitrixUserUser',
                'reference' => array('=this.MODIFIED_BY' => 'ref.ID'),
            ),
            'CREATED_BY' => array(
                'data_type' => 'BitrixUserUser',
                'reference' => array('=this.CREATED_BY' => 'ref.ID'),
            ),
            'WF_LOCKED_BY' => array(
                'data_type' => 'BitrixUserUser',
                'reference' => array('=this.WF_LOCKED_BY' => 'ref.ID'),
            ),
        );
    }
    /**
     * Returns validators for NAME field.
     *
     * @return array
     */
    public static function validateName()
    {
        return array(
            new MainEntityValidatorLength(null, 255),
        );
    }
    /**
     * Returns validators for WF_NEW field.
     *
     * @return array
     */
    public static function validateWfNew()
    {
        return array(
            new MainEntityValidatorLength(null, 1),
        );
    }
    /**
     * Returns validators for XML_ID field.
     *
     * @return array
     */
    public static function validateXmlId()
    {
        return array(
            new MainEntityValidatorLength(null, 255),
        );
    }
    /**
     * Returns validators for CODE field.
     *
     * @return array
     */
    public static function validateCode()
    {
        return array(
            new MainEntityValidatorLength(null, 255),
        );
    }
    /**
     * Returns validators for TAGS field.
     *
     * @return array
     */
    public static function validateTags()
    {
        return array(
            new MainEntityValidatorLength(null, 255),
        );
    }
    /**
     * Returns validators for TMP_ID field.
     *
     * @return array
     */
    public static function validateTmpId()
    {
        return array(
            new MainEntityValidatorLength(null, 40),
        );
    }
}

Класс описывает таблицу БД b_iblock_element, которая хранит элементы инфоблоков:

--
-- Структура таблицы `b_iblock_element`
--

CREATE TABLE `b_iblock_element` (
  `ID` int(11) NOT NULL,
  `TIMESTAMP_X` datetime DEFAULT NULL,
  `MODIFIED_BY` int(18) DEFAULT NULL,
  `DATE_CREATE` datetime DEFAULT NULL,
  `CREATED_BY` int(18) DEFAULT NULL,
  `IBLOCK_ID` int(11) NOT NULL DEFAULT '0',
  `IBLOCK_SECTION_ID` int(11) DEFAULT NULL,
  `ACTIVE` char(1) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'Y',
  `ACTIVE_FROM` datetime DEFAULT NULL,
  `ACTIVE_TO` datetime DEFAULT NULL,
  `SORT` int(11) NOT NULL DEFAULT '500',
  `NAME` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `PREVIEW_PICTURE` int(18) DEFAULT NULL,
  `PREVIEW_TEXT` text COLLATE utf8_unicode_ci,
  `PREVIEW_TEXT_TYPE` varchar(4) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'text',
  `DETAIL_PICTURE` int(18) DEFAULT NULL,
  `DETAIL_TEXT` longtext COLLATE utf8_unicode_ci,
  `DETAIL_TEXT_TYPE` varchar(4) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'text',
  `SEARCHABLE_CONTENT` text COLLATE utf8_unicode_ci,
  `WF_STATUS_ID` int(18) DEFAULT '1',
  `WF_PARENT_ELEMENT_ID` int(11) DEFAULT NULL,
  `WF_NEW` char(1) COLLATE utf8_unicode_ci DEFAULT NULL,
  `WF_LOCKED_BY` int(18) DEFAULT NULL,
  `WF_DATE_LOCK` datetime DEFAULT NULL,
  `WF_COMMENTS` text COLLATE utf8_unicode_ci,
  `IN_SECTIONS` char(1) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'N',
  `XML_ID` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `CODE` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `TAGS` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `TMP_ID` varchar(40) COLLATE utf8_unicode_ci DEFAULT NULL,
  `WF_LAST_HISTORY_ID` int(11) DEFAULT NULL,
  `SHOW_COUNTER` int(18) DEFAULT NULL,
  `SHOW_COUNTER_START` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

--
-- Индексы таблицы `b_iblock_element`
--

ALTER TABLE `b_iblock_element`
  ADD PRIMARY KEY (`ID`),
  ADD KEY `ix_iblock_element_1` (`IBLOCK_ID`,`IBLOCK_SECTION_ID`),
  ADD KEY `ix_iblock_element_4` (`IBLOCK_ID`,`XML_ID`,`WF_PARENT_ELEMENT_ID`),
  ADD KEY `ix_iblock_element_3` (`WF_PARENT_ELEMENT_ID`),
  ADD KEY `ix_iblock_element_code` (`IBLOCK_ID`,`CODE`);

--
-- AUTO_INCREMENT для таблицы `b_iblock_element`
--

ALTER TABLE `b_iblock_element`
  MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=357;
COMMIT;

В методе getMap() перечислены все поля таблицы, включая описание связей с другими сущностями. Таким образом указано отношение столбца IBLOCK_ID текущей таблицы и столбца ID сущности Iblock. В дальнейшем по reference-полям возможно выбирать поля связанных сущностей и использовать их в фильтрах.

'IBLOCK' => array(
    'data_type' => 'BitrixIblockIblock',
    'reference' => array('=this.IBLOCK_ID' => 'ref.ID'),
)

Автоматически сгенерировать класс с описанием любой таблицы можно на странице «Настройки • Производительность • Таблицы», добавив параметр orm=y в адрес:

/bitrix/admin/perfmon_tables.php?lang=ru&orm=y

Выполнение запросов

Тренироваться будем на таблицах типов инфоблока, самих инфоблоков, элементов и разделов. Для них описаны сущности TypeTable, IblockTable, ElementTable и SectionTable, их можно посмотреть в исходниках модуля iblock.

Пример № 1

Выбираем элементы инфоблока с идентификатором 5:

BitrixMainLoader::includeModule('iblock');

// создаем объект Query, в качестве параметра передаем объект сущности (элемент инфоблока)
$query = new BitrixMainEntityQuery(
    BitrixIblockElementTable::getEntity()
);
// выбираем идентификатор элемента, символьный код и наименование
$query->setSelect(array('ID', 'CODE', 'NAME'))
      // идентификатор инфоблока равен 5
      ->setFilter(array('IBLOCK_ID' => 5))
      // сортируем элементы по идентификатору, по возрастанию
      ->setOrder(array('ID' => 'ASC'))
      // выбираем только три элемента
      ->setLimit(3);
// посмотрим, какой запрос был сформирован
echo $query->getQuery();
// выполняем запрос
$result = $query->exec();
// выводим результат
while ($row = $result->fetch()) {
    debug($row);
}
SELECT
    `iblock_element`.`ID` AS `ID`,
    `iblock_element`.`CODE` AS `CODE`,
    `iblock_element`.`NAME` AS `NAME`
FROM
    `b_iblock_element` `iblock_element`
WHERE
    `iblock_element`.`IBLOCK_ID` = 5
ORDER BY
    `ID` ASC
LIMIT
    0, 3
Array
(
    [ID] => 347
    [CODE] => angliyskiy-buldog
    [NAME] => Английский бульдог
)
Array
(
    [ID] => 348
    [CODE] => dalmatin
    [NAME] => Далматин
)
Array
(
    [ID] => 349
    [CODE] => afganskaya-borzaya
    [NAME] => Афганская борзая
)

Список методов BitrixMainEntityQuery:

  • setSelect(), setGroup() — устанавливает массив с именами полей
  • addSelect(), addGroup() — добавляет имя поля
  • getSelect(), getGroup() — возвращает массив с именами полей
  • setFilter() — устанавливает одно- или многомерный массив с описанием фильтра
  • addFilter() — добавляет один параметр фильтра со значением
  • getFilter() — возвращает текущее описание фильтра
  • setOrder() — устанавливает массив с именами полей и порядком сортировки
  • addOrder() — добавляет одно поле с порядком сортировки
  • getOrder() — возвращает текущее описание сортировки
  • setLimit(), setOffset() — устанавливает значение
  • getLimit(), getOffset() — возвращает текущее значение
  • registerRuntimeField() — регистрирует новое временное поле для исходной сущности

Пример № 2

Через сущность «элемент» можно выбирать или ставить условия на поля связанной сущности «инфоблок». Связанная таблица по умолчанию присоединяется с помощью LEFT JOIN. Вспомним reference-поле IBLOCK в описании ElementTable и выберем данные самого инфоблока вместе с элементами инфоблока:

BitrixMainLoader::includeModule('iblock');

// создаем объект Query, в качестве параметра передаем объект сущности (элемент инфоблока)
$query = new BitrixMainEntityQuery(
    BitrixIblockElementTable::getEntity()
);
$query->setSelect(array('ID', 'CODE', 'NAME', 'IBLOCK.ID', 'IBLOCK.CODE', 'IBLOCK.NAME'))
      ->setFilter(array('IBLOCK.ID' => 5))
      // так тоже можно
      // ->setFilter(array('IBLOCK_ID' => 5))
      ->setOrder(array('ID' => 'ASC'))
      ->setLimit(3);
// посмотрим, какой запрос был сформирован
echo $query->getQuery();
// выполняем запрос
$result = $query->exec();
// выводим результат
while ($row = $result->fetch()) {
    debug($row);
}
SELECT
    `iblock_element`.`ID` AS `ID`,
    `iblock_element`.`CODE` AS `CODE`,
    `iblock_element`.`NAME` AS `NAME`,
    `iblock_element_iblock`.`ID` AS `IBLOCK_ELEMENT_IBLOCK_ID`,
    `iblock_element_iblock`.`CODE` AS `IBLOCK_ELEMENT_IBLOCK_CODE`,
    `iblock_element_iblock`.`NAME` AS `IBLOCK_ELEMENT_IBLOCK_NAME`
FROM
    `b_iblock_element` `iblock_element` LEFT JOIN `b_iblock` `iblock_element_iblock`
    ON `iblock_element`.`IBLOCK_ID` = `iblock_element_iblock`.`ID`
WHERE
    `iblock_element_iblock`.`ID` = 5
ORDER BY
    `ID` ASC
LIMIT
    0, 3
Array
(
    [ID] => 347
    [CODE] => angliyskiy-buldog
    [NAME] => Английский бульдог
    [IBLOCK_ELEMENT_IBLOCK_ID] => 5
    [IBLOCK_ELEMENT_IBLOCK_CODE] => articles
    [IBLOCK_ELEMENT_IBLOCK_NAME] => Статьи о домашних животных
)
Array
(
    [ID] => 348
    [CODE] => dalmatin
    [NAME] => Далматин
    [IBLOCK_ELEMENT_IBLOCK_ID] => 5
    [IBLOCK_ELEMENT_IBLOCK_CODE] => articles
    [IBLOCK_ELEMENT_IBLOCK_NAME] => Статьи о домашних животных
)
Array
(
    [ID] => 349
    [CODE] => afganskaya-borzaya
    [NAME] => Афганская борзая
    [IBLOCK_ELEMENT_IBLOCK_ID] => 5
    [IBLOCK_ELEMENT_IBLOCK_CODE] => articles
    [IBLOCK_ELEMENT_IBLOCK_NAME] => Статьи о домашних животных
)

Пример № 3

В запросах можно использовать агрегатные функции MySQL. Для это служит метод registerRuntimeField(), регистрирующий новое поле на время выполнения запроса. Посмотрим, сколько активных элементов в инфоблоке:

BitrixMainLoader::includeModule('iblock');

// создаем объект Query, в качестве параметра передаем объект сущности (элемент инфоблока)
$query = new BitrixMainEntityQuery(
    BitrixIblockElementTable::getEntity()
);
$query->registerRuntimeField(
    'ACTIVE_ELEMENTS',
    array(
        // тип вычисляемого поля
        'data_type' => 'string',
        // агрегатная функция (COUNT, MAX, MIN, SUM, AVG) и поле для подстановки
        'expression' => array('GROUP_CONCAT(%s)', 'NAME')
    )
);
$query->setSelect(array('IBLOCK.NAME', 'ACTIVE_ELEMENTS'));
$query->setFilter(array('IBLOCK.ID' => 5, '=ACTIVE' => 'Y'));
// посмотрим, какой запрос был сформирован
echo $query->getQuery();
// выполняем запрос
$result = $query->exec();
// выводим результат
while ($row = $result->fetch()) {
    debug($row);
}
SELECT
    `iblock_element_iblock`.`NAME` AS `IBLOCK_ELEMENT_IBLOCK_NAME`,
    GROUP_CONCAT(`iblock_element`.`NAME`) AS `ACTIVE_ELEMENTS`
FROM
    `b_iblock_element` `iblock_element` LEFT JOIN `b_iblock` `iblock_element_iblock`
    ON `iblock_element`.`IBLOCK_ID` = `iblock_element_iblock`.`ID`
WHERE
    `iblock_element_iblock`.`ID` = 5 AND `iblock_element`.`ACTIVE` = 'Y'
GROUP BY
    `iblock_element_iblock`.`NAME`
Array
(
    [IBLOCK_ELEMENT_IBLOCK_NAME] => Статьи о домашних животных
    [ACTIVE_ELEMENTS] => Английский бульдог,Далматин,Афганская борзая,Абиссинская кошка,Сиамская 
                         кошка,Американский бобтейл,Британская короткошерстная,Лабрадор,Лайка
)

Пример № 4

Выбираем разделы инфоблока с идентифкатором 5 и подсчитываем количество элементов в каждом; учитываем только активные разделы и элементы:

BitrixMainLoader::includeModule('iblock');

// создаем объект Query, в качестве параметра передаем объект сущности (элемент инфоблока)
$query = new BitrixMainEntityQuery(
    BitrixIblockElementTable::getEntity()
);
$query->registerRuntimeField(
    'ELEMENT_COUNT',
    array(
        // тип вычисляемого поля
        'data_type' => 'integer',
        // агрегатная функция (COUNT, MAX, MIN, SUM, AVG) и поле для подстановки
        'expression' => array('COUNT(%s)', 'NAME')
    )
);
$query->registerRuntimeField(
    'ELEMENT_LIST',
    array(
        // тип вычисляемого поля
        'data_type' => 'string',
        // агрегатная функция (COUNT, MAX, MIN, SUM, AVG) и поле для подстановки
        'expression' => array('GROUP_CONCAT(%s)', 'NAME')
    )
);
$query->setSelect(array('IBLOCK_SECTION.NAME', 'ELEMENT_COUNT', 'ELEMENT_LIST'));
// учитываем только активные разделы и активные элементы
$query->setFilter(array('=ACTIVE' => 'Y', '=IBLOCK_SECTION.ACTIVE' => 'Y'));
// выбираем только разделы инфоблока с идентификатором 5
$query->addFilter('IBLOCK.ID', 5);
// посмотрим, какой запрос был сформирован
echo $query->getQuery();
// выполняем запрос
$result = $query->exec();
// выводим результат
while ($row = $result->fetch()) {
    debug($row);
}
SELECT
    `iblock_element_iblock_section`.`NAME` AS `IBLOCK_ELEMENT_IBLOCK_SECTION_NAME`,
    COUNT(`iblock_element`.`NAME`) AS `ELEMENT_COUNT`,
    GROUP_CONCAT(`iblock_element`.`NAME`) AS `ELEMENT_LIST`
FROM
    `b_iblock_element` `iblock_element`
    LEFT JOIN `b_iblock_section` `iblock_element_iblock_section`
    ON `iblock_element`.`IBLOCK_SECTION_ID` = `iblock_element_iblock_section`.`ID`
    LEFT JOIN `b_iblock` `iblock_element_iblock`
    ON `iblock_element`.`IBLOCK_ID` = `iblock_element_iblock`.`ID`
WHERE
    `iblock_element`.`ACTIVE` = 'Y' AND
    `iblock_element_iblock_section`.`ACTIVE` = 'Y' AND
    `iblock_element_iblock`.`ID` = 5
GROUP BY
    `iblock_element_iblock_section`.`NAME`
Array
(
    [IBLOCK_ELEMENT_IBLOCK_SECTION_NAME] => Породы кошек
    [ELEMENT_COUNT] => 4
    [ELEMENT_LIST] => Абиссинская кошка,Сиамская кошка,Американский бобтейл,Британская короткошерстная
)
Array
(
    [IBLOCK_ELEMENT_IBLOCK_SECTION_NAME] => Породы собак
    [ELEMENT_COUNT] => 3
    [ELEMENT_LIST] => Английский бульдог,Далматин,Афганская борзая
)
Array
(
    [IBLOCK_ELEMENT_IBLOCK_SECTION_NAME] => Служебные породы
    [ELEMENT_COUNT] => 2
    [ELEMENT_LIST] => Лабрадор,Лайка
)

Добавим еще одно условие, чтобы выбирать только разделы, содержащие более трех элементов:

$query->addFilter('>ELEMENT_COUNT', 3);
SELECT
    `iblock_element_iblock_section`.`NAME` AS `IBLOCK_ELEMENT_IBLOCK_SECTION_NAME`,
    COUNT(`iblock_element`.`NAME`) AS `ELEMENT_COUNT`,
    GROUP_CONCAT(`iblock_element`.`NAME`) AS `ELEMENT_LIST`
FROM
    `b_iblock_element` `iblock_element`
    LEFT JOIN `b_iblock_section` `iblock_element_iblock_section`
    ON `iblock_element`.`IBLOCK_SECTION_ID` = `iblock_element_iblock_section`.`ID`
    LEFT JOIN `b_iblock` `iblock_element_iblock`
    ON `iblock_element`.`IBLOCK_ID` = `iblock_element_iblock`.`ID`
WHERE
    `iblock_element`.`ACTIVE` = 'Y' AND
    `iblock_element_iblock_section`.`ACTIVE` = 'Y' AND
    `iblock_element_iblock`.`ID` = 5
GROUP BY
    `iblock_element_iblock_section`.`NAME`
HAVING
    COUNT(`iblock_element`.`NAME`) > 3
Array
(
    [IBLOCK_ELEMENT_IBLOCK_SECTION_NAME] => Породы кошек
    [ELEMENT_COUNT] => 4
    [ELEMENT_LIST] => Абиссинская кошка,Сиамская кошка,Американский бобтейл,Британская короткошерстная
)

Пример № 5

Runtime-поле может быть не только вычисляемым значением, но и ссылкой на другую сущность. Т.е. в методе getMap() можно не описывать связь, а сформировать ее прямо в запросе. Например, создадим объект Query для сущности IblockTable, свяжем ее с ElementTable и выберем элемент с ID=349:

BitrixMainLoader::includeModule('iblock');

// создаем объект Query, в качестве параметра передаем объект сущности (инфоблок)
$query = new BitrixMainEntityQuery(
    BitrixIblockIblockTable::getEntity()
);
$query->registerRuntimeField( // поле element как ссылка на таблицу b_iblock_element
    'element',
    array(
        // тип — сущность ElementTable
        'data_type' => 'BitrixIblockElementTable',
        // this.ID относится к таблице, относительно которой строится
        // запрос, т.е. b_iblock.ID = b_iblock_element.IBLOCK_ID
        'reference' => array('=this.ID' => 'ref.IBLOCK_ID'),
    )
);
// выбираем название элемента, символьный код, краткое описание, кол-во просмотров и название инфоблока
$query->setSelect(array('element.NAME', 'element.CODE', 'element.PREVIEW_TEXT', 'element.SHOW_COUNTER',  'NAME'));
// выбираем только элемент с идентификатором 349
$query->setFilter(array('element.ID' => 349));
// посмотрим, какой запрос был сформирован
echo $query->getQuery();
// выполняем запрос
$result = $query->exec();
// выводим результат
while ($row = $result->fetch()) {
    debug($row);
}
SELECT
    `iblock_iblock_element`.`NAME` AS `IBLOCK_IBLOCK_element_NAME`,
    `iblock_iblock_element`.`CODE` AS `IBLOCK_IBLOCK_element_CODE`,
    `iblock_iblock_element`.`PREVIEW_TEXT` AS `IBLOCK_IBLOCK_element_PREVIEW_TEXT`,
    `iblock_iblock_element`.`SHOW_COUNTER` AS `IBLOCK_IBLOCK_element_SHOW_COUNTER`,
    `iblock_iblock`.`NAME` AS `NAME`
FROM
    `b_iblock` `iblock_iblock` LEFT JOIN `b_iblock_element` `iblock_iblock_element`
    ON `iblock_iblock`.`ID` = `iblock_iblock_element`.`IBLOCK_ID`
WHERE
    `iblock_iblock_element`.`ID` = 349
Array
(
    [IBLOCK_IBLOCK_element_NAME] => Афганская борзая
    [IBLOCK_IBLOCK_element_CODE] => afganskaya-borzaya
    [IBLOCK_IBLOCK_element_PREVIEW_TEXT] => Изящная красавица с длинной развевающейся на бегу шелковистой шерстью...
    [IBLOCK_IBLOCK_element_SHOW_COUNTER] => 10
    [NAME] => Статьи о домашних животных
)

Пример № 6

В определении runtime-reference-поля можно указывать тип соединения (LEFT, RIGHT, INNER), а в фильтре использовать сложную логику, как в CIblockElement::GetList():

BitrixMainLoader::includeModule('iblock');

// создаем объект Query, в качестве параметра передаем объект сущности (инфоблок)
$query = new BitrixMainEntityQuery(
    BitrixIblockIblockTable::getEntity()
);
// поле element как ссылка на таблицу b_iblock_element
$query->registerRuntimeField(
    'element',
    array(
        // тип — сущность ElementTable
        'data_type' => 'BitrixIblockElementTable',
        // this.ID относится к таблице, относительно которой строится
        // запрос, т.е. b_iblock.ID = b_iblock_element.IBLOCK_ID
        'reference' => array('=this.ID' => 'ref.IBLOCK_ID'),
        // тип соединения INNER JOIN
        'join_type' => 'INNER'
    )
);
// поле type как ссылка на таблицу b_iblock_type
$query->registerRuntimeField(
    'type',
    array(
        'data_type' => 'BitrixIblockTypeTable',
        'reference' => array('=this.IBLOCK_TYPE_ID' => 'ref.ID'),
        'join_type' => 'INNER'
    )
);
// выбираем название инфоблока, символьный код инфоблока, название элемента,
// символьный код элемента и идентификатор типа инфоблока
$query->setSelect(array('NAME', 'CODE', 'element.NAME', 'element.CODE', 'type.ID'));
// выбираем элементы с идентификаторами 348 или 349
$query->setFilter(
    array(
        'LOGIC' => 'OR',
        array('element.ID' => 348),
        array('element.ID' => 349),
    )
);
// посмотрим, какой запрос был сформирован
echo $query->getQuery();
// выполняем запрос
$result = $query->exec();
// выводим результат
while ($row = $result->fetch()) {
    debug($row);
}
SELECT
    `iblock_iblock`.`NAME` AS `NAME`,
    `iblock_iblock`.`CODE` AS `CODE`,
    `iblock_iblock_element`.`NAME` AS `IBLOCK_IBLOCK_element_NAME`,
    `iblock_iblock_element`.`CODE` AS `IBLOCK_IBLOCK_element_CODE`,
    `iblock_iblock_type`.`ID` AS `IBLOCK_IBLOCK_type_ID`
FROM
    `b_iblock` `iblock_iblock`
    INNER JOIN `b_iblock_element` `iblock_iblock_element`
    ON `iblock_iblock`.`ID` = `iblock_iblock_element`.`IBLOCK_ID`
    INNER JOIN `b_iblock_type` `iblock_iblock_type`
    ON `iblock_iblock`.`IBLOCK_TYPE_ID` = `iblock_iblock_type`.`ID`
WHERE
    (`iblock_iblock_element`.`ID` = 348) OR (`iblock_iblock_element`.`ID` = 349)
Array
(
    [NAME] => Статьи о домашних животных
    [CODE] => articles
    [IBLOCK_IBLOCK_element_NAME] => Далматин
    [IBLOCK_IBLOCK_element_CODE] => dalmatin
    [IBLOCK_IBLOCK_type_ID] => content
)
Array
(
    [NAME] => Статьи о домашних животных
    [CODE] => articles
    [IBLOCK_IBLOCK_element_NAME] => Афганская борзая
    [IBLOCK_IBLOCK_element_CODE] => afganskaya-borzaya
    [IBLOCK_IBLOCK_type_ID] => content
)

Пример № 7

Получаем пользовательские свойства элементов инфоблока с идентификатором 5:

BitrixMainLoader::includeModule('iblock');

// создаем объект Query, в качестве параметра передаем объект сущности (свойства)
$query = new BitrixMainEntityQuery(
    BitrixIblockPropertyTable::getEntity()
);
$query->setSelect(array('ID', 'NAME', 'CODE', 'PROPERTY_TYPE'));
$query->setFilter(array('IBLOCK_ID' => 5));
// посмотрим, какой запрос был сформирован
echo $query->getQuery();
// выполняем запрос
$result = $query->exec();
// выводим результат
while ($row = $result->fetch()) {
    debug($row);
}
SELECT
    `iblock_property`.`ID` AS `ID`,
    `iblock_property`.`NAME` AS `NAME`,
    `iblock_property`.`CODE` AS `CODE`,
    `iblock_property`.`PROPERTY_TYPE` AS `PROPERTY_TYPE`
FROM
    `b_iblock_property` `iblock_property`
WHERE
    `iblock_property`.`IBLOCK_ID` = 5
Array
(
    [ID] => 47
    [NAME] => Автор
    [CODE] => AUTHOR
    [PROPERTY_TYPE] => S
)
Array
(
    [ID] => 48
    [NAME] => Оценка
    [CODE] => RATING
    [PROPERTY_TYPE] => L
)
Array
(
    [ID] => 49
    [NAME] => Галерея
    [CODE] => GALLERY
    [PROPERTY_TYPE] => F
)
Array
(
    [ID] => 51
    [NAME] => Примечание
    [CODE] => NOTE
    [PROPERTY_TYPE] => S
)

Поиск:
CMS • DataManager • ElementTable • IblockTable • JOIN • ORM • SQL • SectionTable • Web-разработка • getEntity • query • registerRuntimeField • setFilter • setGroup • setLimit • setOrder • setSelect • table • База данных • Битрикс • Запрос • Инфоблок • Новое ядро • Сущность • Таблица

  • О чем и для кого эта статья
  • Базовые возможности ORM в 1С-Битрикс: Управление сайтом
  • Автоматическая генерация ORM-классов
  • Работа с удаленными БД
  • Нюансы работы с разными типами БД
  • Реальный пример: использование ORM для подключения из PHP к MSSQL в Linux
  • Выбор данных из хранимых процедур вместо таблиц
  • Выводы

cookie monster

О чем и для кого эта статья

Эта статья – одна из самых популярных страниц нашего сайта. Несколько сотен человек каждый день читают ее. Мы опубликовали важнейшую для мира Битрикс-разработки справочную информацию за полгода до появления официальной документации.

Статья будет вам интересна, если Вы уже видели и слышали что-то про ORM в 1С-Битрикс: Управление сайтом, и даже пробовали разрабатывать сайты на 1С-Битрикс с собственными ORM-классами. Статья состоит из трех частей:

  • Краткое напоминание о базовых возможностях ORM в БУС;
  • Генератор ORM-классов;
  • Подробно про работу ORM c БД. Реальный случай из практики: использование ORM для подключения из linux’а к MSSQL БД и выбора данных из хранимых процедур вместо таблиц.

Согласно SRP, каждый класс в программе должен иметь только одну обязанность. Например: работа с форматом валют, работа с языковым пакетом, работа с настройками приложения.

В такой серьезной системе, как 1С-Битрикс: Управление сайтом классов и обязанностей должно быть немало. Моя IDE подсказывает, что в Интернет-магазине версии 16.0.1 объявлено 2759 классов. Я точно уверен, что самая многочисленная “группа” — классы для работы с таблицами в БД. CIBlockElement для работы с таблицей b_iblock_element (элементы инфоблоков), CSaleOrder для таблицы b_sale_order (заказы), CUser для работы с b_user (пользователи) и т.д. В БД сейчас 413 таблиц. Выходит, должно быть 413 классов для работы с каждой из них.

До недавнего времени каждый такой класс обладал уникальностью снежинки. Да, во многих выборка из таблицы выполнялась в методе с названием “GetList”, но это не более чем соглашение. Это нигде не было закреплено и никем не проверялось.

Более того, несмотря на общее название, эти методы вполне могли работать по-разному. вспомните 3 метода, имеющих одинаковое название:

  • CUser::GetList;
  • CIBlockElement::GetList;
  • CSaleOrder::GetList.

Пожалуй, только опытный программист сможет сходу назвать особенности их применения без подглядывания в справку. А ведь есть еще такие острые вопросы, как добавление в БД, обработка ошибок и т.п. И, зачастую, каждый класс решает “сам за себя”.

Но сейчас ситуация меняется. Появилось новое ядро D7, с новыми идеями, механизмами и концепциями. Поговорим об одной из них, краеугольной — ORM (Object Relation Model). Об ORM подробно рассказывают в блоге разработчиков и есть отличное руководство в документации. Постараюсь не повторяться и буду считать, что вы уже написали пару ORM-классов, ориентируясь на эти статьи.

Я выделю только основное. Если класс отвечает за доступ к таблице БД, он должен быть наследником класса BitrixMainEntityDataManager и должен переопределять только два метода:

  1. getTableName для получения имени таблицы;
  2. getMap для получения массива колонок таблицы — объектов BitrixMainEntityField

Метод getFilePath, который упоминается в блоге, больше не является обязательным!
объекты BitrixMainEntity
Хочется похвалить разработчиков за ORM. Это не просто слова и планы на будущее. На момент написания этого текста (декабрь 2015) переведено на “новые рельсы” 215 классов. То есть, больше половины всех классов для работы с БД уже имеют D7-аналоги и используются.

Выводы по базовым возможностям ORM в 1С-Битрикс: УС. У нас появился очень мощный API для работы с БД с правильной современной архитектурой. Стоит незамедлительно переводить все свои старые классы на “ORM-рельсы”. Руководство по разработке этих классов достаточно подробное и отвечает на главные вопросы.

Автоматическая генерация ORM-классов

Первое, о чем хочется рассказать в этой статье об ORM — генератор ORM классов. По неизвестной мне причине он тщательно скрыт в недрах панели управления сайтом. Когда пользуешься им, возникает ощущение, что прикасаешься к чему-то запретному :) Хотя в среде разработчиков о нем часто говорят: и на сайте идей, и в блогах.

Чтобы его использовать, нужно открыть страницу Настройки > Производительность > Таблицы и добавить GET-параметр orm=y. Адрес будет выглядеть так: /bitrix/admin/perfmon_tables.php?lang=ru&orm=y

После этого для любой таблицы в БД сайта можно автоматически создавать ORM-класс. Для примера выберем штатную таблицу шаблонов сайта b_site_template.
Автоматическое создание ORM-классов
После перезагрузки страницы имеем следующий код: код в Gist. Результат после небольших преобразований может быть сведен к настоящему классу для работы с шаблонами, расположенному по пути /bitrix/modules/main/lib/sitetemplate.php.

Важная особенность генератора: поля описываются не объектами-потомками BitrixMainEntityField, а ассоциативными массивами. Этот формат считается устаревшим, хотя поддерживается и используется во многих системных классах.

Отличное подспорье для создателей модулей: генератор позволит поставить производство классов на поток.

Работа с удаленными БД

Помимо очевидного, есть под капотом ORM и куда более интересные возможности. Например, работа с разными БД (вертикальный шардинг)! Теперь, заканчивая работу по созданию класса никто не мешает сказать “ах да, эта таблица лежит в другой БД с другим логином и паролем”.

Никаких ограничений по сравнению с “локальными” таблицами нет. По таблицам внешней БД можно точно так же делать выборки, изменять записи, группировать и т.п. Нельзя только пытаться сделать JOIN таблиц в разных БД.

Чтобы указать, в какой БД требуется искать таблицу, в ORM-классе требуется переопределить метод getConnectionName. Здесь указывается псевдоним подключения (по умолчанию “default” — главное подключение, та же БД, в которую установлены штатные таблицы).
метод getConnectionName
Само подключение должно быть вручную прописано в настройках ядра D7 (файл /bitrix/.settings.php), узел connections > value > имя подключения. узел connections > value >
Если с host, database, login, password вопросов, в принципе, нет, то первый параметр className заслуживает отдельного внимания. Для этого потребуется сделать небольшое отступление и рассказать об организации работы с БД в новом ядре.

Нюансы работы с разными типами БД

В новом ядре изменилась так же работа с БД. “Главными” по этому вопросу стали классы в пространстве имен BitrixMainDB. Конкретно за подключение к БД и выполнение всех запросов отвечают классы семейства BitrixMainDBConnection, а именно:

  • BitrixMainDBOracleConnection — подключение к Oracle-БД с использованием http://php.net/manual/en/book.oci8.php;
  • BitrixMainDBMssqlConnection — подключение к Microsoft SQL-БД с использованием http://php.net/manual/en/book.sqlsrv.php (расширение доступно только в Windows-версии PHP);
  • BitrixMainDBMysqliConnection — подключение к MySQL через http://php.net/manual/en/book.mysqli.php;
  • BitrixMainDBMysqlConnection — подключение к MySQL через http://php.net/manual/en/book.mysql.php (расширение запрещено с PHP 5.5).

Наверное, для 95% сайтов хватает этого набора (а для 90% только BitrixMainDBMysqliConnection). А что же делать, если подключение к БД очень экзотическое?

Например, у заказчика чрезвычайно устаревшая (или наоборот, слишком свежая) версия СУБД и невозможно использовать встроенный драйвер для PHP. В таких случаях на помощь спешат, конечно же, программисты.

Чтобы добавить в 1С-Битрикс: Управление сайтом поддержку нового типа БД, необходимо следовать простой инструкции:

  1. Создать класс подключения (наследник BitrixMainDBConnection). В нем определить все “базовые” операции с БД: подключение, отключение, выполнение произвольного запроса, работу с транзакциями;
  2. Создать класс SQL-хелпер (наследник BitrixMainDBSqlHelper) и возвращать его экземпляр в методе createSqlHelper. Класс предназначен для самой низкоуровневой работы с БД — он добавляет экранирование, работает с датами, предоставляет доступ к базовым SQL-функциям и т.п.;
  3. Создать класс для результата выборки (наследник BitrixMainDBResult). В нем требуется определить методы-обертки над традиционными функциями работы с результатом выборки.

BitrixMainDB
Теперь вы знаете, какие className можно указывать в /bitrix/.settings.php и как создавать собственные подключения к БД.

Реальный пример: использование ORM для подключения из PHP к MSSQL в Linux

Как уже было отмечено выше, класс BitrixMainDBMssqlConnection основан на расширении sqlsrv, которое доступно только на windows-сервере. В одном из наших проектов возникла необходимость подключиться к MSSQL с linux-сервера, то есть решение от 1C-Битрикс нам не подходило (а компиляция драйвера в linux ничем хорошим не закончилась). Помогла природная смекалка и знание ООП.

На сервер было установлено расширение mssql (http: php.net/manual/ru/book.mssql.php) и была разработана следующая архитектура: BitrixMainDBMssqlConnection
Был разработан собственный набор MSSQL-классов, многие методы были унаследованы от стандартных BitrixMainDBMssql*. Пришлось буквально в паре десятков мест произвести замены вроде sqlsrv_query => mssql_query. К этому пакету (и способу его получения) вернемся в завершении статьи.

Выбор данных из хранимых процедур вместо таблиц

ORM подходит даже для таких экзотических запросов, как выборка данных не из таблицы, а из хранимых процедур. Такие процедуры могут быть созданы в MSSQL-базе данных. Что ж, попробуем “обмануть” ORM и подсунуть ей процедуру вместо имени таблицы.

Укажем название функции в методе getTableName.
метод getTableName
Однако, сразу такой код работать не будет. Дело в том, что при использовании подключения BitrixMainDBMssqlConnection все вхождения имен таблиц проходят через экранирование. Попытка сразу выполнить такой запрос приведет к выбрасыванию исключения:

MS Sql query error: Invalid object name ‘foo_table_procedure()’. (400)

SELECT

[base].[bar] AS [BAR],

[base].[baz] AS [BAZ],

FROM [foo_table_procedure()] [base]

Увы, не получилось. На самом деле, мы в одном шаге от успеха, помешали только знаки “[“ и “]”, которыми MssqlSqlHelper защитил имя используемой “таблицы”. Проблема решается “в лоб” созданием собственного подключения (Connection) и SqlHelper’а.

На сервер было установлено расширение mssql (http: php.net/manual/ru/book.mssql.php) и была разработана следующая архитектура:
Расширение mssql
Где self::isKnownFunctionCall — метод проверки, который возвращает true, если в $identifier находится “foo_table_procedure()”.

Выводы

Новое ядро D7 уже здесь. Каждый месяц появляются все новые и новые классы, они постепенно заменяют старые. Если в Ваших проектах или модулях есть классы, чья ответственность — предоставление доступа к 1 таблице в БД (локальной или сторонней) — требуется его переписать, поставить “на рельсы» одной из ключевых фич в новом 1С-Битрикс: Управлении сайтом.

Что касается пакета классов для подключения с linux-сервера к MSSQL БД. Если Вы хотите получить этот пакет (3 класса: MssqlConnection, MssqlSqlHelper, MsssqlResult), поделитесь статьей в социальных сетях и заполните форму в конце страницы. Ссылка для скачивания файлов придёт вам на почту.

  1. Главная
  2. Сниппеты Битрикс d7

Модуль расширенного управления меню для битрикс

Сентябрь 17, 2018

Теги:
Хранение данных, События, Хайлоадблоки, ORM

Использование событий при работе с хайлоадблоками или ORM.

Данные примеры можно использовать при работе как с «чистым» ORM, так и с хайлоадблоками. При использовании для хайлоадблоков, в качестве модуля используется пустая строка — ».

Подключение обработчика событий для хайлоадблока с прямым указанием сущности «Myentity»:


namespace PartnerMyentity;
$eventManager BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('''MyentityOnAdd''PartnerMyentityOnAdd');

Подключение обработчика события хайлоадблока с получением названия сущности по идентификатору хайлоадблока (12):


namespace PartnerMyentity;
$hlblock BitrixHighloadblockHighloadBlockTable::getById(12)->fetch();

$entity BitrixHighloadblockHighloadBlockTable::compileEntity($hlblock); $eventManager BitrixMainEventManager::getInstance(); $eventManager->addEventHandler(''$entity->getName().'OnAdd''PartnerMyentityOnAdd');

Подключение обработчика события для сущности ORM «GrainForumForum» из модуля «grain.forum»:


namespace PartnerMyentity;
$eventManager BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('grain.forum''GrainForumForum::onAfterAdd''PartnerMyentityOnAfterAdd');

Получение различных данных из объекта «$event»:


$arFields $event->getParameter("fields"); // получаем список полей   

$event->setParameter("fields",$arFields); // обновляем список полей  
$entity $event->getEntity(); // получаем объект сущности 
$eventType $event->getEventType(); // получаем тип события (например, "MyentityOnUpdate") 
$moduleId $event->getModuleId(); // получаем код модуля

Использование событий «OnBeforeAdd», «OnBeforeUpdate»:


namespace PartnerMyentity;
$eventManager BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('''MyentityOnBeforeUpdate''PartnerMyentityOnBeforeAddUpdate');

$eventManager->addEventHandler('''MyentityOnBeforeAdd''PartnerMyentityOnBeforeAddUpdate');

function 

OnBeforeAddUpdate(BitrixMainEntityEvent $event)
{
    
$id $event->getParameter("id");
    if(
is_array($id))
        
$id $id["ID"];

    if(!

$id)
        return;
$entity $event->getEntity();
    
$entityDataClass $entity->GetDataClass(); $eventType $event->getEventType(); $arFields $event->getParameter("fields"); $price = ... $error = ... $result = new BitrixMainEntityEventResult();

    if(

$error
    {
        
$arErrors = Array();
        
$arErrors[] = new BitrixMainEntityFieldError($entity->getField("MYFIELD"), "Ошибка в поле MYFIELD");
        
// или $arErrors[] = new BitrixMainEntityEntityError("Общая ошибка");
        
$result->setErrors($arErrors);
    } 
    else 
    {
        
$arFields["UF_AVG_PRICE"] = $price;
        
$event->setParameter("fields",$arFields);
        
$changedFields = Array();
        
$changedFields["UF_AVG_PRICE"] = $price;
        
$result->modifyFields($changedFields);
         
//$result->unsetFields($arUnsetFields);
    
}

    return 

$result;
}

События «OnAdd», «OnUpdate» вызываются после проверки полей на правильность перед добавлением или обновлением элемента:


namespace PartnerMyentity;

$eventManager BitrixMainEventManager::getInstance(); $eventManager->addEventHandler('''MyentityOnUpdate''PartnerMyentityOnAddUpdate');
$eventManager->addEventHandler('''MyentityOnAdd''PartnerMyentityOnAddUpdate');

function 

OnAddUpdate(BitrixMainEntityEvent $event)
{
    
$id $event->getParameter("id");
    if(
is_array($id))
        
$id $id["ID"]; $entity $event->getEntity();
    
$entityDataClass $entity->GetDataClass(); $eventType $event->getEventType(); $arParameters $event->getParameters();
    
//$event->setParameters($arParameters);

    //$arFields = $event->getParameter("fields");
    //$event->setParameter("fields",$arFields);

}

События «OnAfterAdd», «OnAfterUpdate» вызываются после добавления/изменения записи (статическая переменная $bHandlerStop используется для предотвращения рекурсии при вызове Update внутри обработчика, т.к. событие при этом также будет вызываться):


namespace PartnerMyentity;
$eventManager BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('''MyentityOnAfterUpdate''PartnerMyentityOnAfterAddUpdate');

$eventManager->addEventHandler('''MyentityOnAfterAdd''PartnerMyentityOnAfterAddUpdate');

function 

OnAfterAddUpdate(BitrixMainEntityEvent $event)
{
    static 
$bHandlerStop;
    if(
$bHandlerStop===true)
        return;
$id $event->getParameter("id");
    if(
is_array($id))
        
$id $id["ID"];
    if(!
$id)
        return;
$entity $event->getEntity();
    
$entityDataClass $entity->GetDataClass(); $eventType $event->getEventType(); $arParameters $event->getParameters();
    
//$event->setParameters($arParameters);

    //$arFields = $event->getParameter("fields");
    //$event->setParameter("fields",$arFields);

$bHandlerStop true;
    
$result $entityDataClass::update($id, Array("UF_AVG_PRICE"=>2.0));
    
$bHandlerStop false;
}

Пример передачи данных между обработчиками «OnBeforeUpdate» и «OnAfterUpdate» через статическую переменную класса:


namespace Partner;
$eventManager BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('''MyentityOnBeforeUpdate''PartnerMyClass::onBeforeUpdate');

$eventManager->addEventHandler('''MyentityOnAfterUpdate''PartnerMyClass::onAfterUpdate');

class 

MyClass
{
    const 
props =  array(
        
'UF_KINDS_OF_WORK',
        
'UF_CONSTRUCTION_TYPE',
        
'UF_REGIONS',
    );

    private static 

$temp null;

             function 

onBeforeUpdate(BitrixMainEntityEvent $event)
    {
        
$id $event->getParameter("id");
        if(
is_array($id))
            
$id $id["ID"]; $entity $event->getEntity();
        
self::$temp = array(
            
'id' => $id,
            
'enumId' => self::getAllEnumId($entity,$id),
        );
    }

    function 

onAfterUpdate(BitrixMainEntityEvent $event)
    {
        
$id $event->getParameter("id");
        if(
is_array($id))
            
$id $id["ID"]; $entity $event->getEntity();

                 if(!

is_array(self::$temp) || self::$temp['id']!=$id)
            return;
$oldEnumId self::$temp['enumId'];
        
$newEnumId self::getAllEnumId($entity,$id); // ... делаем что-то имея данные, которые были до, и стали после
    
}

         function 

getAllEnumId($entity,$id)
    {
        
$entityDataClass $entity->GetDataClass();
        
$result $entityDataClass::getList(array(
            
"select" => self::props,
            
"filter" => Array("=ID"=>intval($id)),
            
"limit" => 1,
        ));
$allEnumId = array();    
        if(
$arRow $result->Fetch())
        {
            foreach(
self::props as $propCode)
                if(
is_array($arRow[$propCode]))
                    foreach(
$arRow[$propCode] as $enumId)
                        
$allEnumId[] = $enumId;
        }

                 return 

$allEnumId;
    }    
}

Событие «OnBeforeDelete», возникающее перед удалением элемента (может быть использовано для отмены удаления):


namespace PartnerMyentity;
$eventManager BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('''MyentityOnBeforeDelete''PartnerMyentityOnBeforeDelete');

function 

OnBeforeDelete(BitrixMainEntityEvent $event)
{
    
// All $event functions /bitrix/modules/main/lib/event.php $id $event->getParameter("id");
    if(
is_array($id))
        
$id $id["ID"]; $entity $event->getEntity();
    
$entityDataClass $entity->GetDataClass(); $result = new BitrixMainEntityEventResult();

    if(

$ID==15) { $arErrors = Array();
        
$arErrors[] = new BitrixMainEntityEntityError("Нельзя удалить запись с ID=".$id);
        
$result->setErrors($arErrors);

    }

    return 

$result;
}

Событие «OnDelete», возникающее перед удалением элемента (уже не может быть использовано для отмены удаления):


namespace PartnerMyentity;
$eventManager BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('''MyentityOnDelete''PartnerMyentityOnDelete');

function 

OnDelete(BitrixMainEntityEvent $event)
{
    
// All $event functions /bitrix/modules/main/lib/event.php $id $event->getParameter("id");
    if(
is_array($id))
        
$id $id["ID"]; $entity $event->getEntity();
    
$entityDataClass $entity->GetDataClass(); define("ERROR_EMAIL""my@email.ru"); 
    
SendError("Запись с ID=$id будет удаленаnn");
}

Событие «OnAfterDelete», возникающее после удаления элемента:


namespace PartnerMyentity;
$eventManager BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('''MyentityOnAfterDelete''PartnerMyentityOnAfterDelete');

function 

OnAfterDelete(BitrixMainEntityEvent $event)
{
    
// All $event functions /bitrix/modules/main/lib/event.php $id $event->getParameter("id");
    if(
is_array($id))
        
$id $id["ID"]; $entity $event->getEntity();
    
$entityDataClass $entity->GetDataClass(); define("ERROR_EMAIL""my@email.ru"); 
    
SendError("Запись с ID=$id была удаленаnn");
}

← Переход к списку

Реализация ORM в ядре D7 — очередная интересная, перспективная, но как обычно плохо документированная разработка от 1с-Битрикс :) Призвана она абстрагировать разработчика от механики работы с таблицами на уровне запросов к БД, введя понятие сущности и поля сущности. На зимней партнерской конференции Алексей Кирсанов провел мастер-класс по созданию модуля с применением ORM для выборки данных из своей таблицы. После чего я решил провести небольшое исследование на предмет возможности построения более сложных, чем в примере, запросов.

Интересовало меня как общее устройство системы и принцип постороения кода, так и частные случаи запросов. Например, можно ли с помощью ORM использовать HAVING, DISTINCT и разные типы JOIN’ов. Забегая вперед, получилось далеко не все, но в общем конструктор эффективен и относительно прост.

Для начала изучаем понятия сущности, поля сущности и датаменеджера в документации. По-простому, сущность в терминах битрикса — это таблица, поля сущности — столбцы или «ссылки» на другие сущности, а датаменеджер — система управления данными. Для каждой сущности нужно создать описание, например

class ElementTable extends EntityDataManager
{
    public static function getFilePath()
    {
        return __FILE__;
    }

    public static function getTableName()
    {
        return 'b_iblock_element';
    }

    public static function getMap()
    {
        return array(
            'ID' => array(
                'data_type' => 'integer',
                'primary' => true,
                'autocomplete' => true,
                'title' => Loc::getMessage('IBLOCK_ELEMENT_ENTITY_ID_FIELD'),
            ),
            'IBLOCK_ID' => array(
                'data_type' => 'integer',
            ),
            'IBLOCK' => array(
                'data_type' => 'Iblock',
                'reference' => array('=this.IBLOCK_ID' => 'ref.ID'),
            ),
            ...

В getMap перечислены все поля таблицы, включая описание связей с другими сущностями. В примере таким образом указано отношение столбца IBLOCK_ID текущей таблицы и столбца ID сущности Iblock. В дальнейшем по reference-полям возможно выбирать поля связанных сущностей и использовать их в фильтрах.

Лайвхак: автоматически сгенерировать класс с описанием любой таблицы можно на странице Производительность-Таблицы, добавив параметр &orm=y в адрес.

Тренироваться будем на таблицах типов инфоблока, самих инфоблоков и элементов. Для них описаны сущности TypeTable, IblockTable и ElementTable, их можно посмотреть в исходниках модуля iblock. Операции добавления, удаления и выборки данных по первичному ключу (getById) нас не интересуют, будем строить обычные getList-запросы произвольного вида через цепочку вызовов методов класса Query.

Пример #1

Выберем 10 первых элементов из инфоблока ID=1.

BitrixMainLoader::IncludeModule("iblock");
// создаем объект Query. В качестве параметра он принимает объект сущности, относительно которой мы строим запрос
$query = new BitrixMainEntityQuery(BitrixIblockElementTable::getEntity());
// можно еще так: :)
// $query = new BitrixMainEntityQuery(BitrixMainEntityBase::getInstance("BitrixIblockElementTable"));
$query
    ->setSelect(array("ID", "NAME"))
    ->setFilter(array("IBLOCK_ID" => 1))
    ->setOrder(array("ID" => "ASC"))
    ->setLimit(10);
$query->exec();
$query->dump();

Пример #2

Выберем элементы инфоблока с группировкой по названию. Стоит отметить, что битрикс автоматически добавляет в число полей для группировки все, что указано в setSelect.

$query
    ->setSelect(array("NAME"))
    ->setFilter(array("IBLOCK_ID" => 1))
    ->setGroup(array("NAME"))

Пример #3

В запросах можно использовать агрегатные функции mysql. Для это служит метод registerRuntimeField, регистрирующий новое поле на время выполнения запроса.

Подсчитаем количество элеметов с группировкой по имени.

$query
    // cnt - название поля
    ->registerRuntimeField("cnt", array(
        // тип вычисляемого поля
        "data_type" => "integer",
        // агрегатная функция (count, max, sum, avg...) и поле для подстановки
        "expression" => array("count(%s)", "NAME")
        )
    )
    ->setSelect(array("NAME", "cnt"))
    ->setFilter(array("IBLOCK_ID" => 1))
    ->setGroup(array("NAME"));

Пример #4

Допустим, нам нужно в предыдущем примере ограничить выборку только теми названиями, которые встречаются более 5 раз. Написав фильтр по агрегатному runtime-полю, битрикс автоматически переносит его в условие having! Причем в параметрах группировки не обязательно указывать поле, к которому применяется агрегатная функция.

$query
    ->registerRuntimeField("cnt", array(
        "data_type" => "integer",
        "expression" => array("count(%s)", "NAME")
        )
    )
    ->setSelect(array("NAME", "cnt"))
    ->setFilter(array("IBLOCK_ID" => 1, ">cnt" => 5));

Будет сгенерирован запрос вида:

select NAME, count(NAME) as cnt from table where IBLOCK_ID=1 group by NAME having cnt>5

Пример #5

Через сущность «элемент» можно выбирать или ставить условия на поля связанной сущности «инфоблок». Связанная таблица по умолчанию присоединяется с помощью left join. Вспомним reference-поле IBLOCK в описании ElementTable и выберем название инфоблока с ID=1 через таблицу с элементами:

$query
    ->setSelect(array("IBLOCK.NAME"))
    ->setFilter(array("IBLOCK.ID" => 1))
    // или
    //->setFilter(array("IBLOCK_ID" => 1))
    ->setLimit(1);

Запрос:

SELECT  `iblock_element_iblock`.`NAME` AS `IBLOCK_ELEMENT_IBLOCK_NAME`
FROM `b_iblock_element` `iblock_element` LEFT JOIN `b_iblock` `iblock_element_iblock` ON `iblock_element`.`IBLOCK_ID` = `iblock_element_iblock`.`ID`
WHERE `iblock_element_iblock`.`ID` = 1
LIMIT 0, 1

Пример #6

Runtime- может быть не только вычисляемое поле, но и ссылка на другую сущность. Т.е. в функции getMap мы можем не описывать связь, а сформировать ее прямо в запросе. Например, создадим объект Query для сущности IblockTable, свяжем ее с ElementTable и выберем элемент с ID=1:

// Query(IblockTable)
$query = new BitrixMainEntityQuery(BitrixIblockIblockTable::getEntity());
$query
    // поле element как ссылка на таблицу b_iblock_element
    ->registerRuntimeField("element", array(
        // тип - сущность ElementTable
        "data_type" => "BitrixIblockElementTable",
        // обратите внимание, что this.ID относится к таблице, относительно которой строится запрос
        // т.е. b_iblock.ID = b_iblock_element.IBLOCK_ID
        'reference' => array('=this.ID' => 'ref.IBLOCK_ID'),
        )
    )
    // все поля элемента и название инфоблока
    ->setSelect(array("element", "NAME"))
    ->setFilter(array("element.ID" => 1));

Небольшая особенность — в setSelect нужно обязательно указывать runtime-поле, что добавляет в select все поля связанной таблицы.

Пример #7

В определении runtime-reference-поля можно указывать тип join’a, а в фильтре использовать сложную логику как в CIblockElement::GetList():

$query = new BitrixMainEntityQuery(BitrixIblockIblockTable::getEntity());
$query
    ->registerRuntimeField("element", array(
        "data_type" => "BitrixIblockElementTable",
        'reference' => array('=this.ID' => 'ref.IBLOCK_ID'),
        'join_type' => "LEFT"
        )
    )
    ->registerRuntimeField("type", array(
        "data_type" => "BitrixIblockTypeTable",
        'reference' => array('=this.IBLOCK_TYPE_ID' => 'ref.ID'),
        'join_type' => "RIGHT"
        )
    )
    ->setSelect(array("element", "type"))
    ->setFilter(array(
            "LOGIC" => "OR",
            array("element.ID" => 1),
            array("ID" => 3)
        )
    )
    ->setLimit(2);

Пример #8

Если несколько таблиц в описании сущностей (не через runtime-поля!) связаны по цепочке, то их можно использовать в запросе через символ «.». Например, выберем ID типа инфоблока для элемента с ID=1:

$query = new BitrixMainEntityQuery(BitrixIblockElementTable::getEntity());
$query
    // b_iblock_element.IBLOCK_ID = b_iblock.TYPE_ID = b_iblock_type.ID
    ->setSelect(array("IBLOCK.TYPE.ID"))
    ->setFilter(array("ID" => 1));

К сожалению, не получилось создать запрос, на который повлиял бы метод enableDataDoubling и выяснить, что можно передавать в метод setOptions. У кого есть мысли на этот счет, прошу в комментарии.

Напоследок привет от битрикса :)

/*
/bitrix/modules/main/lib/entity/query.php
* The most magic method. Do not edit without strong need, and for sure run tests after.
*/
protected function collectExprChains(QueryChain $chain, $storages = array('hidden'))

Понравилась статья? Поделить с друзьями:
  • Bitrix mysql query error 1271 illegal mix of collations for operation union 400
  • Bitrix mysql query error 1054
  • Bitrix mysql connect error localhost 2002 no such file or directory 400
  • Bitrix main db connectionexception mysql connect error localhost
  • Bitrix main argumentexception json error 1 100