DarkRiDDeR10 мин

Bitrix API. Как адрес каталога превращается в ELEMENT_CODE

BitrixPHPЧПУ

Иногда символьный код в элементе правильный, а карточка всё равно отвечает 404. В другой раз тот же код работает только без раздела в адресе. Причина обычно не в транслите: путь сначала разбирает компонент, а уже потом его переменные попадают в фильтр инфоблока. Разберём один вопрос: что должно совпасть, чтобы адрес каталога действительно стал значением ELEMENT_CODE? Цена ошибки — менять данные элемента, когда проблема находится в маршруте.

Это полезно отделить в голове. Адрес /catalog/kofe/classic-250-g/ не является запросом к таблице элементов. Для комплексного компонента Bitrix сначала определяет, какой шаблон пути подошёл, и восстанавливает переменные из URL. Только затем код компонента решает, как искать элемент. Если смешать эти два шага, начинается бесконечная правка CODE, хотя ошибка сидит в шаблоне или в имени переменной.

Что делает движок ЧПУ

В документации CComponentEngine::ParseComponentPath описано, что метод получает папку ЧПУ, массив шаблонов и текущий путь. Он возвращает код найденного шаблона, а переменные из пути записывает в переданный массив. Если шаблон не найден, результат — пустая строка. Значит, до запроса к инфоблоку можно и нужно посмотреть две вещи: какой шаблон распознан и какое значение оказалось в ELEMENT_CODE.

Шаблон пишется относительно папки компонента. Например, для папки /catalog/ внутри массива нужен путь #SECTION_CODE#/#ELEMENT_CODE#/, а не полный адрес с начальным слешем. Это не вкусовщина: документация отдельно предупреждает, что лишний слеш в шаблоне меняет результат разбора.

Схема: запрос браузера разбирается SEF-шаблоном, превращается в SECTION_CODE и ELEMENT_CODE, затем используется в выборке
Переменная из URL и элемент инфоблока живут на разных шагах. Между ними стоит проектный фильтр компонента.

Четыре значения, которые должны совпасть

УчастокПримерКак проверить
Папка ЧПУ/catalog/Сравнить с SEF_FOLDER вызванного компонента
Шаблон детали#SECTION_CODE#/#ELEMENT_CODE#/Проверить отсутствие лишнего начального слеша и нужные маркеры
ПеременнаяELEMENT_CODE = classic-250-gВывести массив, полученный после разбора, на тестовой среде
ВыборкаIBLOCK_ID + CODE + ACTIVEСравнить фильтр компонента с контрольным GetList
Ссылка в шаблонеТот же набор маркеровСобрать URL из значений и открыть его вручную

Минимальный воспроизводимый разбор

Ниже не готовый комплексный компонент, а короткая проверка его основания. Запускаю её на тестовой странице с известным путём. Если $page не равен detail, до запроса к инфоблоку дело вообще не дошло. Если код страницы найден, но ELEMENT_CODE пуст, виноват шаблон или сам адрес.

<?php

CModule::IncludeModule("iblock");

$arUrlTemplates = array(
    "detail" => "#SECTION_CODE#/#ELEMENT_CODE#/",
);
$arVariables = array();

$page = CComponentEngine::ParseComponentPath(
    "/catalog/",
    $arUrlTemplates,
    $arVariables,
    "/catalog/kofe/classic-250-g/"
);

if ($page !== "detail" || empty($arVariables["ELEMENT_CODE"])) {
    throw new RuntimeException("URL не разобран как детальная страница");
}

$result = CIBlockElement::GetList(
    array(),
    array(
        "IBLOCK_ID" => 12,
        "=CODE" => $arVariables["ELEMENT_CODE"],
        "ACTIVE" => "Y",
    ),
    false,
    array("nTopCount" => 1),
    array("ID", "NAME", "CODE")
);

$element = $result->GetNext();
if (!$element) {
    throw new RuntimeException("URL разобран, но элемент не найден");
}

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

Почему генерация и разбор должны пользоваться одной формой адреса

Метод CComponentEngine::MakePathFromTemplate подставляет значения массива в маркеры шаблона. Это удобная точка для проверки обратного направления: у нас есть SECTION_CODE и ELEMENT_CODE, собираем путь и затем разбираем его тем же шаблоном. Если после такого круга переменная изменилась или пропала, в коде сайта уже есть расхождение.

<?php

$url = CComponentEngine::MakePathFromTemplate(
    "#SECTION_CODE#/#ELEMENT_CODE#/",
    array(
        "SECTION_CODE" => "kofe",
        "ELEMENT_CODE" => "classic-250-g",
    )
);

// $url: kofe/classic-250-g/
// Для ссылки добавляем папку /catalog/ в одном месте проекта.

Последовательность от ссылки до карточки

  1. Взять реальный адрес, который не открывается, и сохранить его без ручной правки.
  2. Сверить папку и шаблон детали в параметрах вызванного компонента.
  3. На тестовой среде вывести код страницы и массив переменных после ParseComponentPath.
  4. Передать полученный ELEMENT_CODE в короткий CIBlockElement::GetList с теми же базовыми фильтрами.
  5. Если элемент найден, сравнить с фильтром самого компонента: раздел, активность, права и проектные свойства.
  6. Собрать обратную ссылку из тех же маркеров и повторить проверку после изменения шаблона.

Частые расхождения

СимптомГде искатьБезопасная проверка
Страница не определяетсяПапка ЧПУ или шаблон деталиПроверить результат ParseComponentPath до обращения к инфоблоку
Страница определяется, код пустМаркер отличается от имени, которое ждёт компонентСравнить ключи массива переменных с параметрами компонента
Код есть, элемента нетCODE, инфоблок, активность или дополнительный фильтрЗапустить GetList сначала с базовыми, затем с проектными условиями
Ссылка формируется иначе, чем разбираетсяДва разных URL-шаблона в шаблоне и компонентеСобрать путь через MakePathFromTemplate и разобрать его обратно

Ограничения

Эта диагностика начинается в момент, когда PHP-компонент уже получил запрос. Если веб-сервер или правила перенаправления не передали путь в приложение, ParseComponentPath не сможет это исправить. Тогда проверять нужно предыдущий слой: фактический URI, правило маршрутизации и точку входа сайта. Не стоит менять CODE, пока не доказано, что компонент вообще получил нужную переменную.

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

Итог

ЧПУ — это не «красивый CODE в базе», а связка из папки, шаблона, восстановленных переменных и фильтра элемента. Когда ссылка ведёт в 404, сначала смотрим результат разбора URL, затем выборку. После такой проверки становится видно, нужна ли правка в данных, компоненте или маршруте.

Проверяемые источники