Кому из нормальных разработчиков нравится битрикс? Правильный ответ: никому (ну или почти никому). К великому сожалению этот прекрасный продукт маркетологов под капотом — гнилой насквозь. К еще большему сожалению этим продуктом пользуются очень и очень многие. Он невероятно популярен именно из-за прекрасной работы отдела маркетинга компании 1С. Если бы перед принятием решения о том какую CMS применить при построении нового сайта сначала спрашивали независимых технических специалистов, то битрикс был бы уже давно скорее мертв, чем жив. Однако в русскоязычном секторе обычно принято спрашивать не тех кто разбирается, а скорее соседей, которые уже клюнули на удочку красивого маркетинга.

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

Репозиторий

Сам публичный репозиторий находится здесь. Там же на корявом английском я описал основной принцип работы и описал методы. Теперь попробую вкратце продублировать это здесь уже на русском.

Установка

Все просто:

composer require polyspirit/bitrix-builder

Те кто до сих пор не знаком с composer (в каком веке вы живете?) могут скачать код в виде архива и расположить основные классы из папки src где угодно на сайте, например в local/php_interface, откуда уже их и подключать куда угодно.

Использование

use \polyspirit\Bitrix\Builder\IBlock;

// в качестве первого параметра указываем ID инфоблока
$iBlockById = new IBlock(12);

// или можем указать его CODE
$iBlockByCode = new IBlock('news');

// в качестве второго параметра опционально можно указать ID сайта, который обычно лежит в константе SITE_ID
$iBlockByCodeAndSiteID = new IBlock('news', 's1');

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

Далее забираем нужный элемент или элементы:

$arResult = $iBlock->getElements();

// в этом методе просто будет возвращен первый элемент из коллекции
$arResultDetail = $iBlock->filter(['ID' => 42])->getElement();

И уже в шаблоне это все показываем:

// выводим например NAME
echo $arResultDetail['NAME'];

// для коллекции это будет как-то так
foreach ($arResult as $item) {
    echo $item['NAME'];
}

Что внутри?

Внутри каждого элемента коллекции всегда будет 2 свойства: PROPS и PICTURE_SRC. PROPS содержат в себе все свойства инфоблока, а PICTURE_SRC путь до картинки, который будет по умолчанию взят из DETAIL_PICTURE, а если это поле не указано или не выбрано, то из PREVIEW_PICTURE.

Для разделов, которые точно таким же образом работают через класс ISection, унаследованный от IBlock, правила немного другие. Свойства надо выбирать самостоятельно и указывать в fields (об этом ниже), а в PICTURE_SRC будет путь по умолчанию до DETAIL_PICTURE, а если это поле не указано, то до PICTURE.

Свойства и сеттеры

Если просто выбрать элементы, то будут выбраны все и отсортированы таким образом:

['sort' => 'ASC', 'date_active_from' => 'DESC', 'created_date' => 'DESC']

Чтобы сбросить сортировку по умолчанию, применяйте метод sortReset().

Список сеттеров: filter(), sort(), fields(), navs() и sizes(). Так же их все можно указать в массиве и закинуть в метод params(). Вот пример обоих подходов:

(new IBlock('news'))->filter(['>=TIMESTAMP_X' => date('Y-m-d h:i:s', 'yesterday')])
        ->sort(['NAME' => 'ASC']) // добавляем к сортировке по умолчанию
        ->fields(['NAME', 'CODE', 'PICTURE_PREVIEW'])
        ->sizes(['width' => 1280, 'height' => 720]) // размеры получаемой картинки
        ->navs(['nPageSize' => 4, 'iNumPage' => 1])
        ->getElements();

// ИЛИ

$params = [
    'sort' => ['NAME' => 'ASC'],
    'filter' => ['>=TIMESTAMP_X' => date('Y-m-d h:i:s', 'yesterday')],
    'fields' => ['NAME', 'CODE', 'PICTURE_PREVIEW'],
    'sizes' => ['width' => 1280, 'height' => 720],
    'navs' => ['nPageSize' => 4, 'iNumPage' => 1]
];
(new IBlock('news'))->params($params)->getElements();

Так же чтобы выбирать только активные элементы существует метод active().

$arResult = $iBlock->active()->getElements();

Функция-обработчик

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

Для методов getElement() и getElements() можно в качестве первого и единственного параметра передать объект типа Closure.

$handler = function (&$element) {
    $element['ID_CODE'] = $element['ID'] . '|' . $element['CODE'];
};
$arResult = $iBlock->filter(['>=ID' => 42])
                   ->fields(['CODE'])
                   ->getElements($handler);

foreach ($arResult as $element) {
    echo $element['ID_CODE']; // id_элемента|код_элемента
}

Добавление и редактирование

Так же можно добавлять элементы с помощью add(), обновлять с помощью update() и удалять через delete(). Синтаксис такой:

public IBlock::add(array $fields, array $props = []): int
public IBlock::update(string|int $id, array $fields, array $props = []): bool

// если $id = null, то будет попытка удалить последний добавленный или обновленный элемент
public IBlock::delete(string|int|null $id = null): bool

Пример:

$iBLock->add(
    ['NAME' => 'Some', 'PREVIEW_TEXT' => 'Some text'], 
    ['SOME_PROPERTY_CODE' => 42]
);

$iBLock->update(
    ['NAME' => 'Updated some'], 
    ['SOME_PROPERTY_CODE' => 24]
);

$iBLock->delete();

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

Разделы (секции)

Класс ISection отвечает за работу с разделами, наследуется от IBlock и имеет идентичные методы, разве что внутри они реализованы с небольшими отличиями.

У элементов коллекции ISection не будет поля PROPS, вместо этого все свойства разделов надо указывать в fields() и доставать их напрямую из элемента.

Как использовать?

Вы можете подключать классы где угодно и использовать как угодно. Я обычно использую билдер вместе с 2мя простенькими компонентами, на которых и строю почти всю работу в битрикс. Их я нашел в одном из проектов, с которым я когда-то работал, немного почистил, чуть переделал и готово. К сожалению их изначальный автор неизвестен, поэтому я просто их выложил у себя на github. Вот они: blocks и controller.

Blocks — это максимально простой компонент, который просто занимается кэшированием, ну а в довесок предоставляет стандартную архитектуру битрикса, где в result_modifier.php мы получаем и обрабатываем данные, а в template.php уже выводим. Часто я пишу для него какой-нибудь универсальный шаблон вроде items.list и подключаю везде где-только можно. Если нужна какая-то особенная логика, то создаю отдельный шаблон, например news.list.

Controller — уже посложнее. Он дает возможность создавать все необходимые страницы для сущности. То есть страницы для списка, для секции, для детальной информации, редактирования и какие угодно другие. Вся логика как всегда пишется в result_modifier.php, а в качестве файлов шаблонов выступают template.php для страницы входа (обычно это список) и любые другие файлы, которые вы укажете при подключении компонента.

Подробности работы с компонентами так же читайте в readme репозиториев, ссылки я указал выше.

Заключение

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

Ну а если у вас, как и у меня, просто нет выхода, то надеюсь подобные надстройки над кривой системой хоть как-то помогут вам писать хороший код. Удачи!