Презентую модуль для 1С-Битрикс maximaster.tools

Давно у меня назревала мысль создания модуля с полезными наработками, которые будет удобно распространять и использовать между всеми проектами. Но почему-то не находила эта идея времени на реализацию.

И вот, спустя N лет наконец, начало положено! Ура товарищи! И сразу публикую его исходники для сообщества.

Сам модуль можно скачать на github. Также модуль можно подключить через composer нехитрыми манипуляциями:

Установка

Она типична для модулей битрикс. Нужно зайти в админку в Marketplace — Установленные решения, найти в списке модуль и выполнить установку.
Можно и программно установить. Достаточно заинклюдить файл с классом инсталлятора и вызвать метод DoInstall(), примерно так.

О возможностях

Начну, пожалуй, с малого. Первое, что мне понадобилось когда-то давно на всех проектах — это автозагрузчик. К моему великому сожалению в битрикс до сих пор нет psr- совместимого автолоадера, которым можно было бы пользоваться runtime. Посему, первой фичей был добавлен именно он. Актуальным на момент написания данных строк является стандарт PSR-4, поэтому и реализовал именно его. Воспользоваться им весьма просто.
Для начала надо подключить установленный модуль:

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

Конечно, данный пример не очень показателен, но он иллюстрирует идею. Идея не нова, можно пользовать.
Кстати говоря — в данном случае лучше воспользоваться автозагрузчиком из поставки composer, делается в пару строк в composer.json:

Ну да ладно, с этим все. Тут в общем ничего нового и нет. Едем дальше

Автозагрузка обработчиков событий

Вот эта штука мне уже гораздо больше нравится. Меня порой очень напрягает регистрировать каждый хэндлер вручную. Посему был придуман класс, который сам будет тянуть обработчики, оформленные по определенным правилам. А правила, в общем-то, те же самые, что и в автозагрузчике классов 🙂 Значит так:

Вот и все! Теперь все, что нужно сделать, это создавать классы.
Каждый класс обработчик должен принадлежать пространству имен, которое имеет имя модуля, которому этот обработчик принадлежит. Например, если нужно зарегистрировать обработчик события OnPageStart, то необходимо создать класс, полное имя которого будет кончаться на Main\OnPageStart и создать в этом классе метод с любым именем.
Класс может содержать неограниченное количество методов, они будут вызываться в порядке их объявления в коде.
Вот, кстати, как выглядит дерево обработчиков событий на одном из моих проектов. Достаточно наглядно и структурировано, как мне кажется:

handlers-tree
На порядок вызова можно повлиять, правда сделать это несколько сложнее, чем в случае с обычным объявлением. В поставке модуля идет класс \Maximaster\Tools\Events\Base. Нужно унаследовать свой класс-обработчик события от него. В своем классе обработчике нужно объявить статическую переменную array $sort, которая в качестве ключа будет хранить название метода, а в качестве значения число — порядок сортировки.
Пример класса события OnPageStart с двумя обработчиками и разным порядком выполнения:

Базовый класс для обработчиков событий предоставляет также функционал, позволяющий обмениваться данными между разными обработчиками. В процессе выполнения страницы может вызываться несколько ваших обработчиков и часто появляется необходимость передать данные из одного в другой. Некоторые пытаются делать это глобалками, что совсем печально.
У данного класса есть setData($key, $value) и getData($key), который позволяют обменяться данными.

Едем далее.

Надстройка над ORM

И самое вкусное. На момент написания этих строк известно, что ORM в битриксе не умеет работать со свойствами инфоблоков. Но использовать старое API, придуманное десяток лет назад ну очень уж надоело. Тем более что некоторые задачи иногда упираются в ограничения старого API.
Не так давно я наткнулся на статью Андрея Неймана про то, что он разработал надстройку над ORM, позволяющую выбирать некоторые свойства. Мне понравилась идея, и я решил её развить. Тем более, что сам Андрей не очень то собирается видимо развивать свою библиотеку (оно и понятно, вероятно битриксоиды сами разработают должны разработать такой функционал, но я не хочу ждать).

Для начала — об ограничениях и недостатках текущей системы:

  1. Она пока не оптимальна. Генерация сущности под запрос занимает 0.01 — 0.03 сек на не самом медленном железе. Все это, конечно, субъективно и зависит от количества свойств, мощности. Но в целом при работающем кешировании пользоваться вполне себе можно.
  2. При выполнении запросов к двум и более инфоблокам она еще менее оптимальна 🙂
  3. При выполнении запросов к двум и более инфоблокам, при наличии в этих инфоблоках свойств с одинаковыми кодами, система будет вести себя не так, как ожидается.
  4. Система пока не в полной мере поддерживает множественные свойства. Точнее — чистые значения вы всегда можете получить в виде массива, однако ссылок на связанные элементы таким образом пока не получить.
  5. Используется только для выборки данных.

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

Схема работы

Идея заключается в том, что мы подменяем встроенный класс \Bitrix\Iblock\ElementTable своим собственным классом \Maximaster\Tools\Orm\Iblock\ElementTable, который предоставляет более широкие возможности. Класс этот можно использовать только для выборки данных (по крайней мере — пока).
Самый частый вариант использования — это выполнение запроса к конкретному инфоблоку. Чтобы это сделать, нужно сначала заставить работать класс в контексте конкретного инфоблока. Сделать это можно несколькими способами:

  1. Просто строим запрос с помощью этого класса, где в фильтре указываем одиночный IBLOCK_ID. Это не самый лучший способ, потому-что в таком случае сущности к запросу будут подцепляться при построении каждого такого запроса на странице, поэтому я данный способ не рекомендую. Но тем не менее — он есть 🙂
  2. Можно и нужно создать наследника от класса \Maximaster\Tools\Orm\Iblock\ElementTable и определить статичный метод getIblockId(), который вернет целочисленный идентификатор инфоблока. Класс должен именоваться по правилам DataManager’а, (т.е.  оканчиваться на Table).

    В данном случае при построении запроса уже не надо указывать IBLOCK_ID. Также класс будет инициализирован единожды на страницу и сохранен в DataManager, поэтому обращение к этой сущности не будет больше занимать времени.
  3. Сущность можно скомпиллировать, как HL-блок.

Работа со свойствами

Класс ElementTable при инициализации по конкретному инфоблоку располагает набором сущностей. На данный момент перечень сущностей следующий

  • PROPERTY_{CODE}_VALUE — в этой сущности будет находиться значение свойства или референс, если значение свойства является связанным элементом/разделом/элементом списка. Если свойство множественное, то в нем будет массив из значений этого свойства.
  • PROPERTY_{CODE}_DESCRIPTION — в этой сущности будет храниться описание значения свойства, если в настройках свойства указана такая возможностьь
  • PROPERTY_{CODE} — референс на таблицу свойства. В случае с инфоблоками 1.0 — это общая таблица всех свойств (можно обратиться к колонкам свойства), а в случае инфоблоков 2.0 — это таблица всех свойств инфоблока. Эту сущность имеет смысл использовать, если нужно обратиться к ID значения свойства
  • Существует также системный референс, который я не буду описывать, кому надо, тот найдет. Этот референс является ссылкой на таблицу свойств инфоблоков 2.0. Именно по нему строятся запросы на выборку. А референсы с кодом  PROPERTY_{CODE} для инфоблоков 2.0 существуют исключительно для общности API.

Эти сущности можно использовать и в select и в order и в group. Т.е. у вас есть возможность отсортировать список элементов по значению описания свойства. Круто, правда?

Цепочки вызовов

Работают пока только для свойств типа «Привязка к элементу» и только для одиночных свойств. Существует возможность построить бесконечную цепочку связей по свойствам типа привязка. Т.е. можно отсортировать набор элементов одного инфоблока по признаку сортировки элемента 3го инфоблока, связанного через 2й инфоблок )  Например:

Ничего не мешает обратиться и к другим сущностям элемента инфоблока — к названию, дате начала активности, или к связанному инфоблоку, и построить цепочку дальше при необходимости.
В ближайшее время будут добавлены подобные финты и для разделов, связей с HL-блоками.

Запрос к нескольким инфоблокам

Просто пример

На этом пока все. Потихоньку планирую развивать модуль, т.к. по изначальным задумкам еще не все реализовано. Ну и есть еще другие планы, другой функционал, который хотелось бы вынести в этот модуль.