Польза
Урлы разделов и элементов через ORM

Урлы разделов и элементов через ORM

21 ноября 2020 года
Битрикс, ORM, Query, D7, урлы, SECTION_PAGE_URL, DETAIL_PAGE_URL.

Я при возможности работаю с ORM и базой данных через объект Query.

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

Всё, что выпускается сейчас, заточено под этот способ. Ранее были выпущены компиляторы сущностей разделов (с упаковкой в сущность пользовательских полей), в конце предыдущего, начале этого (2020) года выпущен компилятор сущности для элементов.

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

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

В целом, процедура выглядит следующим образом:

  • получаем сущность для выборки элементов;
  • дообучаем сущность специальными полями;
  • на основе сущности создаём query-объект;
  • набрасываем ему параметров;
  • делаем выборку.

1. Получаем сущность.

1.1. Если нам в выборке нужны только основные поля, получаем сущность из стандартного таблета IblockTable:

\Bitrix\Iblock\ElementTable::getEntity()

1.2. Если нам в выборке понадобятся и свойства, придётся получить сущность через компилятор (придётся — потому что компилятор для получения данных о настройках и свойствах произведёт запросы к базе данных). Если до нас на текущем хите кто-то уже компилировал сущность, повторных запросов к базе данных не будет.

\Bitrix\Iblock\IblockTable::compileEntity($iblockApiCode);

... где api-код инфоблока ($iblockApiCode) — специальное поле в настройках инфоблока:

Битрикс API-код инфоблока

2. Дообучаем сущность специальными полями

Грубо говоря, сущность, которую мы получаем (или собираем самостоятельно) — условность до того момента пока мы с её помощью не попробуем что-то получить из базы данных. Справочник, тетрадь с записями, виртуальная документация — кому как удобно представить это для понимания. В неё можно набросать какие угодно поля (даже ведущие на не существующие колонки таблиц) с абсолютно любыми конфигурациями и код не будет отображать ошибку пока мы не отдадим объект query builder-у ORM и он не составит реальный mysql-запрос.

В сущность можно добавлять свои поля. Я называю это "обучить сущность новому полю". Обычно такое выносится в класс-хелпер. Методу класса мы передаём объект сущности, метод обучает её нужному полю. Для полноты картины, такие примеры полей: путь к картинке по её коду, пользовательские поля хайлоадблока (значения свойства типа "справочник"), xml_id значения свойства типа список... Ниже представлен код, который создаёт два поля, помогающие нам с выборкой готовых урлов.

2.1. Путь из символьных кодов раздела

В сущность добавляется поле с названием "SECTION_CODE_PATH", которое принимает значение в виде склеенных через слеш символьных кодов родительских разделов вверх пяти уровней вложенности (которых может и не существовать, тогда хвост цепочки будет состоять из идущих подряд слешей /// ).

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

$elementEntity->addField(
  new ExpressionField(
      'SECTION_CODE_PATH',
      '
      CONCAT(
          COALESCE(%s,""), "/",
          COALESCE(%s,""), "/",
          COALESCE(%s,""), "/",
          COALESCE(%s,""), "/",
          COALESCE(%s,""), "/"
      )',
      [
          'IBLOCK_SECTION.PARENT_SECTION.PARENT_SECTION.PARENT_SECTION.PARENT_SECTION.CODE',
          'IBLOCK_SECTION.PARENT_SECTION.PARENT_SECTION.PARENT_SECTION.CODE',
          'IBLOCK_SECTION.PARENT_SECTION.PARENT_SECTION.CODE',
          'IBLOCK_SECTION.PARENT_SECTION.CODE',
          'IBLOCK_SECTION.CODE',
      ]
  )
);
      

2.2. Урл детальной страницы элемента

В сущность добавляется поле с названием "DETAIL_PAGE_URL", которое принимает значение, получаемое после замены запчастей в шаблоне урла детальной страницы элемента. Код больше как пример того, как можно подходить к задаче. Предполагается, что скорее всего он будет адаптирован под конкретный проект, с учётом:

  • максимального уровня вложенности разделов (где-то пятого уровня вложенности в жизни не предвидится);
  • использование #ELEMENT_ID# вместо #ID#;
  • использование #CODE# вместо #ELEMENT_CODE#;
  • ...и так далее

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

$elementEntity->addField(
    new ExpressionField(
        'DETAIL_PAGE_URL',
        '
        REPLACE(
            REPLACE(
                REPLACE(
                    REPLACE(
                        REPLACE(
                            REPLACE(
                                REPLACE(
                                    REPLACE(
                                        %s, "#ID#", %s
                                    ), "#ELEMENT_CODE#", %s
                                ), "#SECTION_CODE_PATH#", %s
                            ), "#SITE_DIR#", ""
                        ), "//", "/"
                    ), "//", "/"
                ), "//", "/"
            ), "//", "/"
        )',
        ['IBLOCK.DETAIL_PAGE_URL', 'ID', 'CODE', 'SECTION_CODE_PATH']
    )
);
      

3. Выборка

Дообученную сущность упаковываем под Query-объект, добавляем условий и в выборке используем наше поле с урлом на детальную страницу элемента:

use Bitrix\Iblock\IblockTable;
use Bitrix\Main\ORM\Query\Query;
use AlexeyGfi\OrmEntityExtenderHelper;

// Получаем сущность через основной таблоид
// (нам в выборке нужны только стандартные поля)
$elementEntity = IblockTable::getEntity();

// Дообучаем сущность нашими специальными полями
OrmEntityExtenderHelper::extendEntityWithPathFields($elementEntity);

$idList = [1, 2, 3];
$iblockId = 77;

// Делаем выборку через Query
$res = (new Query($elementEntity))
    ->setSelect(['ID', 'NAME', 'DETAIL_PAGE_URL'])
    ->where('IBLOCK_ID', $iblockId)
    ->where('ACTIVE', 'Y')
    ->whereIn('ID', $idList)
    ->exec();

while ($arr = $res->fetch()) {
    // ...
}
      

4. Разные моменты

4.1. По аналогии поступаем с другими шаблонами урлов.

4.2. Иногда имеет смысл выборку списка урлов выносить в отдельный хелпер, отбирать полным списком, кешировать этот список целиком а выдавать урлы наружу из кеша, отфильтровав по запрошенным ID элементов (или разделов). Кеш сбрасывать только если изменились шаблоны в настройках инфоблоков или внесены изменения в символьные коды. Довольно стабильный и крепкий кеш получится, иногда живущий месяцами без сброса.

Плюс экономия на компиляции сущностей: по факту, стандартные таблоиды \Bitrix\Iblock\IblockTable и \Bitrix\Iblock\SectionTable уже завезены в код и на сборку сущности по ним не нужны дополнительные запросы к базе данных.

4.3. Хелперы в автоподгрузку теперь можно регистрировать целым неймспейсом:

use Bitrix\Main\Context;
use Bitrix\Main\Loader;

$docRoot =
    Context::getCurrent()
        ->getServer()
        ->getDocumentRoot();

Loader::registerNamespace(
    "\\AlexeyGfi",
    $docRoot . '/local/lib/AlexeyGfi'
);

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

Обсуждение статьи 3

Написать
Аноним
В чем подвох с Loader::registerNamespace()?
На скрине код метода. Это не будет работать. Даже сигнатура не подходит...
Ответить
Аноним
скрин:
Ответить
Аноним
А все.. вопрос снят. Они обновили метод...
Как всегда — сам спросил, сам ответил))
Все можно удалить
Ответить
Написать