{ "index": 350, "slug": "editorial-2018-04-mechanism-bitrix-slugs", "title": "Bitrix ЧПУ: как путь становится ELEMENT_CODE и где ломается карточка", "excerpt": "Карточка отвечает 404, хотя CODE в инфоблоке выглядит правильно. Разбираем путь от запроса до фильтра элемента и проверяем каждый слой коротким PHP-примером.", "contentHtml": "
Карточка товара отвечает 404, хотя в инфоблоке есть элемент с нужным CODE. Иногда страница открывается по /catalog/classic-250-g/, но не по /catalog/kofe/classic-250-g/. Иногда компонент выбирает не тот товар. Цена ошибки — потерянный переход, неверная карточка и правка данных, которые не были причиной.
Главный тезис: ЧПУ не превращает строку адреса в запрос к инфоблоку автоматически. Bitrix сначала сопоставляет путь с шаблоном комплексного компонента. Затем записывает значения в массив переменных. Только после этого компонент строит фильтр. Если смешать шаги, поиск причины превращается в перебор вариантов CODE.
CComponentEngine::ParseComponentPath получает папку ЧПУ, массив шаблонов и запрошенный путь. Если шаблон подходит, метод возвращает его код и заполняет переданный массив. Шаблон #SECTION_CODE#/#ELEMENT_CODE#/ для запроса /catalog/kofe/classic-250-g/ должен дать страницу detail, раздел kofe и код classic-250-g. Если шаблон не найден, метод возвращает пустую строку.
Шаблон задают относительно папки компонента. Для SEF_FOLDER=/catalog/ нужна запись #SECTION_CODE#/#ELEMENT_CODE#/. Начальный слеш в самом шаблоне меняет результат разбора. Полный URL и шаблон нельзя сравнивать как одинаковые строки.
После разбора компонент сам выбирает поля. Он может фильтровать по IBLOCK_ID, CODE, активности, разделу, правам и свойствам. Наличие ELEMENT_CODE ещё не доказывает, что пользователь увидит элемент. Оно доказывает только восстановление части пути.
| Участок | Пример | Что проверить |
|---|---|---|
| Папка компонента | /catalog/ | Совпадает ли с SEF_FOLDER |
| Шаблон детали | #SECTION_CODE#/#ELEMENT_CODE#/ | Маркеры и отсутствие начального слеша |
| Переменная | ELEMENT_CODE=classic-250-g | Заполнен ли массив после разбора |
| Фильтр | IBLOCK_ID + CODE + ACTIVE | Не исключает ли условие элемент |
| Обратная ссылка | /catalog/kofe/classic-250-g/ | Тот же набор маркеров |
Есть и отрицательный путь. Если веб-сервер не передал запрос в точку входа Bitrix или rewrite изменил URI, компонент не получит исходный адрес. Исправление шаблона инфоблока ничего не изменит. Сначала нужно увидеть фактический URI на входе приложения.
\nНиже учебный пример с условным IBLOCK_ID=12. Он показывает порядок диагностики, а не готовый фильтр любого каталога. В рабочем компоненте фильтр нужно сверить с его реальным кодом.
<?php\n\nCModule::IncludeModule(\"iblock\");\n\n$templates = array(\n \"detail\" => \"#SECTION_CODE#/#ELEMENT_CODE#/\",\n);\n$variables = array();\n$page = CComponentEngine::ParseComponentPath(\n \"/catalog/\", $templates, $variables,\n \"/catalog/kofe/classic-250-g/\"\n);\n\nif ($page === \"\" || empty($variables[\"ELEMENT_CODE\"])) {\n throw new RuntimeException(\"URL не разобран как детальная страница\");\n}\n\n$result = CIBlockElement::GetList(\n array(),\n array(\n \"IBLOCK_ID\" => 12,\n \"=CODE\" => $variables[\"ELEMENT_CODE\"],\n \"ACTIVE\" => \"Y\",\n ),\n false, array(\"nTopCount\" => 2),\n array(\"ID\", \"NAME\", \"CODE\")\n);\n\n$elements = array();\nwhile ($element = $result->Fetch()) {\n $elements[] = $element;\n}\nif (count($elements) === 0) {\n throw new RuntimeException(\"Элемент не найден\");\n}\nif (count($elements) > 1) {\n throw new RuntimeException(\"Фильтр вернул несколько элементов\");\n}\nВызов ограничен двумя строками, чтобы обнаружить неоднозначность. Ноль результатов указывает на IBLOCK_ID, код, активность или дополнительное условие. Два результата показывают слишком широкий фильтр. Нельзя брать первый элемент сортировкой и считать проблему решённой.
Карточка может ломаться при построении ссылки. Список собирает путь с одним именем переменной, а детальный компонент ждёт другое. Соберите учебный URL через CComponentEngine::MakePathFromTemplate, а затем разберите его тем же шаблоном.
<?php\n\n$url = CComponentEngine::MakePathFromTemplate(\n \"#SECTION_CODE#/#ELEMENT_CODE#/\",\n array(\n \"SECTION_CODE\" => \"kofe\",\n \"ELEMENT_CODE\" => \"classic-250-g\",\n )\n);\n\n// $url: kofe/classic-250-g/\nФрагмент проверяет только согласованность маркеров. Он не проверяет rewrite, права и фильтры. Учебное совпадение строки не является production-результатом.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Страница не определяется | Не совпали папка или шаблон | Посмотреть результат ParseComponentPath | Сверить SEF_FOLDER и шаблон |
detail найден, код пуст | Маркер отсутствует или ключ не читается | Вывести массив переменных | Исправить шаблон или обращение к ключу |
| Код есть, карточки нет | Неверный инфоблок, код или активность | Повторить GetList с базовыми фильтрами | Исправить фильтр или данные после проверки |
| Код есть, результатов два | Фильтр широк или нарушена уникальность | Запросить ID, IBLOCK_ID и CODE | Добавить доказанный контекст или устранить конфликт |
| Ссылка строится иначе | Генератор и разбор используют разные шаблоны | Собрать и разобрать путь обратно | Оставить один набор маркеров |
| PHP получает другой путь | Rewrite меняет запрос | Сравнить URI браузера и приложения | Исправить внешний слой, не меняя CODE |
ParseComponentPath.ELEMENT_CODE отдельным GetList с известным IBLOCK_ID.Методика применима к маршруту, который уже обрабатывает PHP-компонент Bitrix в режиме ЧПУ. Она не заменяет nginx, Apache, rewrite, права на инфоблок и проверку кеша. Она также не создаёт уникальность CODE. Если коды могут повторяться, фильтру нужен подтверждённый контекст: раздел или внешний идентификатор.
Не меняйте данные как первую реакцию на 404. Сначала разделите три результата: шаблон не распознан, переменная не заполнена, элемент исключён фильтром. После этого причина привязана к конкретному слою.
\nГотовность проверяется так: каждый контрольный URL даёт страницу detail, непустой ELEMENT_CODE, ровно один ожидаемый элемент в том же фильтре и обратную ссылку, которая снова проходит разбор. Для отрицательных случаев проверка показывает понятную причину: пустой шаблон, пустой код или ноль результатов.
CODE, ACTIVE; документация отмечает изменения метода в новых версиях модуля.