Иногда символьный код в элементе правильный, а карточка всё равно отвечает 404. В другой раз тот же код работает только без раздела в адресе. Причина обычно не в транслите: путь сначала разбирает компонент, а уже потом его переменные попадают в фильтр инфоблока. Разберём один вопрос: что должно совпасть, чтобы адрес каталога действительно стал значением ELEMENT_CODE? Цена ошибки — менять данные элемента, когда проблема находится в маршруте.
Это полезно отделить в голове. Адрес /catalog/kofe/classic-250-g/ не является запросом к таблице элементов. Для комплексного компонента Bitrix сначала определяет, какой шаблон пути подошёл, и восстанавливает переменные из URL. Только затем код компонента решает, как искать элемент. Если смешать эти два шага, начинается бесконечная правка CODE, хотя ошибка сидит в шаблоне или в имени переменной.
Что делает движок ЧПУ
В документации CComponentEngine::ParseComponentPath описано, что метод получает папку ЧПУ, массив шаблонов и текущий путь. Он возвращает код найденного шаблона, а переменные из пути записывает в переданный массив. Если шаблон не найден, результат — пустая строка. Значит, до запроса к инфоблоку можно и нужно посмотреть две вещи: какой шаблон распознан и какое значение оказалось в ELEMENT_CODE.
Шаблон пишется относительно папки компонента. Например, для папки /catalog/ внутри массива нужен путь #SECTION_CODE#/#ELEMENT_CODE#/, а не полный адрес с начальным слешем. Это не вкусовщина: документация отдельно предупреждает, что лишний слеш в шаблоне меняет результат разбора.
Четыре значения, которые должны совпасть
| Участок | Пример | Как проверить |
|---|---|---|
| Папка ЧПУ | /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/ в одном месте проекта.
Последовательность от ссылки до карточки
- Взять реальный адрес, который не открывается, и сохранить его без ручной правки.
- Сверить папку и шаблон детали в параметрах вызванного компонента.
- На тестовой среде вывести код страницы и массив переменных после
ParseComponentPath. - Передать полученный
ELEMENT_CODEв короткийCIBlockElement::GetListс теми же базовыми фильтрами. - Если элемент найден, сравнить с фильтром самого компонента: раздел, активность, права и проектные свойства.
- Собрать обратную ссылку из тех же маркеров и повторить проверку после изменения шаблона.
Частые расхождения
| Симптом | Где искать | Безопасная проверка |
|---|---|---|
| Страница не определяется | Папка ЧПУ или шаблон детали | Проверить результат ParseComponentPath до обращения к инфоблоку |
| Страница определяется, код пуст | Маркер отличается от имени, которое ждёт компонент | Сравнить ключи массива переменных с параметрами компонента |
| Код есть, элемента нет | CODE, инфоблок, активность или дополнительный фильтр | Запустить GetList сначала с базовыми, затем с проектными условиями |
| Ссылка формируется иначе, чем разбирается | Два разных URL-шаблона в шаблоне и компоненте | Собрать путь через MakePathFromTemplate и разобрать его обратно |
Ограничения
Эта диагностика начинается в момент, когда PHP-компонент уже получил запрос. Если веб-сервер или правила перенаправления не передали путь в приложение, ParseComponentPath не сможет это исправить. Тогда проверять нужно предыдущий слой: фактический URI, правило маршрутизации и точку входа сайта. Не стоит менять CODE, пока не доказано, что компонент вообще получил нужную переменную.
Ещё одна ловушка — перенос чужого шаблона без понимания его маркеров. В Bitrix можно назвать переменные по-разному, но компонент и его фильтр должны читать то же имя, которое восстановлено из пути. Я бы не делал универсальную функцию для всех страниц сайта: лучше зафиксировать один шаблон рядом с конкретным каталогом и покрыть его двумя-тремя адресами из реальных данных.
Итог
ЧПУ — это не «красивый CODE в базе», а связка из папки, шаблона, восстановленных переменных и фильтра элемента. Когда ссылка ведёт в 404, сначала смотрим результат разбора URL, затем выборку. После такой проверки становится видно, нужна ли правка в данных, компоненте или маршруте.
Проверяемые источники
- Bitrix: CComponentEngine::ParseComponentPath — разбор ЧПУ-пути по шаблонам и восстановление переменных компонента
- Bitrix: CComponentEngine::MakePathFromTemplate — подстановка значений массива в маркеры URL-шаблона
- Bitrix: CIBlockElement::GetList — выборка элементов по фильтрам IBLOCK_ID, CODE, ACTIVE и с заданным порядком