Files
progcode/editorial/agent-rewrites/350.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
14 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 350,
"slug": "editorial-2018-04-mechanism-bitrix-slugs",
"title": "Bitrix ЧПУ: как путь становится ELEMENT_CODE и где ломается карточка",
"excerpt": "Карточка отвечает 404, хотя CODE в инфоблоке выглядит правильно. Разбираем путь от запроса до фильтра элемента и проверяем каждый слой коротким PHP-примером.",
"contentHtml": "<p>Карточка товара отвечает 404, хотя в инфоблоке есть элемент с нужным <code>CODE</code>. Иногда страница открывается по <code>/catalog/classic-250-g/</code>, но не по <code>/catalog/kofe/classic-250-g/</code>. Иногда компонент выбирает не тот товар. Цена ошибки — потерянный переход, неверная карточка и правка данных, которые не были причиной.</p>\n<p>Главный тезис: ЧПУ не превращает строку адреса в запрос к инфоблоку автоматически. Bitrix сначала сопоставляет путь с шаблоном комплексного компонента. Затем записывает значения в массив переменных. Только после этого компонент строит фильтр. Если смешать шаги, поиск причины превращается в перебор вариантов <code>CODE</code>.</p>\n<h2>Механизм: два независимых шага</h2>\n<p><code>CComponentEngine::ParseComponentPath</code> получает папку ЧПУ, массив шаблонов и запрошенный путь. Если шаблон подходит, метод возвращает его код и заполняет переданный массив. Шаблон <code>#SECTION_CODE#/#ELEMENT_CODE#/</code> для запроса <code>/catalog/kofe/classic-250-g/</code> должен дать страницу <code>detail</code>, раздел <code>kofe</code> и код <code>classic-250-g</code>. Если шаблон не найден, метод возвращает пустую строку.</p>\n<p>Шаблон задают относительно папки компонента. Для <code>SEF_FOLDER=/catalog/</code> нужна запись <code>#SECTION_CODE#/#ELEMENT_CODE#/</code>. Начальный слеш в самом шаблоне меняет результат разбора. Полный URL и шаблон нельзя сравнивать как одинаковые строки.</p>\n<figure><img src=\"/assets/editorial/2018/bitrix-slug-route-2018.svg\" alt=\"Путь запроса проходит через SEF-шаблон, превращается в SECTION_CODE и ELEMENT_CODE, затем попадает в фильтр инфоблока\" /><figcaption>Путь и выборка — разные операции. Между ними находится массив переменных компонента.</figcaption></figure>\n<p>После разбора компонент сам выбирает поля. Он может фильтровать по <code>IBLOCK_ID</code>, <code>CODE</code>, активности, разделу, правам и свойствам. Наличие <code>ELEMENT_CODE</code> ещё не доказывает, что пользователь увидит элемент. Оно доказывает только восстановление части пути.</p>\n<h2>Контракт адреса</h2>\n<div class=\"table-scroll\"><table><thead><tr><th scope=\"col\">Участок</th><th scope=\"col\">Пример</th><th scope=\"col\">Что проверить</th></tr></thead><tbody><tr><td>Папка компонента</td><td><code>/catalog/</code></td><td>Совпадает ли с <code>SEF_FOLDER</code></td></tr><tr><td>Шаблон детали</td><td><code>#SECTION_CODE#/#ELEMENT_CODE#/</code></td><td>Маркеры и отсутствие начального слеша</td></tr><tr><td>Переменная</td><td><code>ELEMENT_CODE=classic-250-g</code></td><td>Заполнен ли массив после разбора</td></tr><tr><td>Фильтр</td><td><code>IBLOCK_ID + CODE + ACTIVE</code></td><td>Не исключает ли условие элемент</td></tr><tr><td>Обратная ссылка</td><td><code>/catalog/kofe/classic-250-g/</code></td><td>Тот же набор маркеров</td></tr></tbody></table></div>\n<p>Есть и отрицательный путь. Если веб-сервер не передал запрос в точку входа Bitrix или rewrite изменил URI, компонент не получит исходный адрес. Исправление шаблона инфоблока ничего не изменит. Сначала нужно увидеть фактический URI на входе приложения.</p>\n<h2>Минимальная проверка на тестовой странице</h2>\n<p>Ниже учебный пример с условным <code>IBLOCK_ID=12</code>. Он показывает порядок диагностики, а не готовый фильтр любого каталога. В рабочем компоненте фильтр нужно сверить с его реальным кодом.</p>\n<pre><code>&lt;?php\n\nCModule::IncludeModule(\"iblock\");\n\n$templates = array(\n \"detail\" =&gt; \"#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\" =&gt; 12,\n \"=CODE\" =&gt; $variables[\"ELEMENT_CODE\"],\n \"ACTIVE\" =&gt; \"Y\",\n ),\n false, array(\"nTopCount\" =&gt; 2),\n array(\"ID\", \"NAME\", \"CODE\")\n);\n\n$elements = array();\nwhile ($element = $result-&gt;Fetch()) {\n $elements[] = $element;\n}\nif (count($elements) === 0) {\n throw new RuntimeException(\"Элемент не найден\");\n}\nif (count($elements) &gt; 1) {\n throw new RuntimeException(\"Фильтр вернул несколько элементов\");\n}</code></pre>\n<p>Вызов ограничен двумя строками, чтобы обнаружить неоднозначность. Ноль результатов указывает на <code>IBLOCK_ID</code>, код, активность или дополнительное условие. Два результата показывают слишком широкий фильтр. Нельзя брать первый элемент сортировкой и считать проблему решённой.</p>\n<h2>Проверка обратного направления</h2>\n<p>Карточка может ломаться при построении ссылки. Список собирает путь с одним именем переменной, а детальный компонент ждёт другое. Соберите учебный URL через <code>CComponentEngine::MakePathFromTemplate</code>, а затем разберите его тем же шаблоном.</p>\n<pre><code>&lt;?php\n\n$url = CComponentEngine::MakePathFromTemplate(\n \"#SECTION_CODE#/#ELEMENT_CODE#/\",\n array(\n \"SECTION_CODE\" =&gt; \"kofe\",\n \"ELEMENT_CODE\" =&gt; \"classic-250-g\",\n )\n);\n\n// $url: kofe/classic-250-g/</code></pre>\n<p>Фрагмент проверяет только согласованность маркеров. Он не проверяет rewrite, права и фильтры. Учебное совпадение строки не является production-результатом.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class=\"table-scroll\"><table><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Страница не определяется</td><td>Не совпали папка или шаблон</td><td>Посмотреть результат <code>ParseComponentPath</code></td><td>Сверить <code>SEF_FOLDER</code> и шаблон</td></tr><tr><td><code>detail</code> найден, код пуст</td><td>Маркер отсутствует или ключ не читается</td><td>Вывести массив переменных</td><td>Исправить шаблон или обращение к ключу</td></tr><tr><td>Код есть, карточки нет</td><td>Неверный инфоблок, код или активность</td><td>Повторить <code>GetList</code> с базовыми фильтрами</td><td>Исправить фильтр или данные после проверки</td></tr><tr><td>Код есть, результатов два</td><td>Фильтр широк или нарушена уникальность</td><td>Запросить <code>ID</code>, <code>IBLOCK_ID</code> и <code>CODE</code></td><td>Добавить доказанный контекст или устранить конфликт</td></tr><tr><td>Ссылка строится иначе</td><td>Генератор и разбор используют разные шаблоны</td><td>Собрать и разобрать путь обратно</td><td>Оставить один набор маркеров</td></tr><tr><td>PHP получает другой путь</td><td>Rewrite меняет запрос</td><td>Сравнить URI браузера и приложения</td><td>Исправить внешний слой, не меняя <code>CODE</code></td></tr></tbody></table></div>\n<h2>Порядок действий</h2>\n<ol><li>Сохранить точный URL. Не удалять раздел и завершающий слеш.</li><li>Проверить, дошёл ли тот же URI до PHP-приложения.</li><li>Сверить папку ЧПУ и шаблон детали конкретного компонента.</li><li>Вывести код шаблона и массив после <code>ParseComponentPath</code>.</li><li>Проверить <code>ELEMENT_CODE</code> отдельным <code>GetList</code> с известным <code>IBLOCK_ID</code>.</li><li>Добавлять фильтры раздела, прав и свойств по одному.</li><li>Собрать обратный URL теми же маркерами и повторить разбор.</li></ol>\n<h2>Ограничения и готовность</h2>\n<p>Методика применима к маршруту, который уже обрабатывает PHP-компонент Bitrix в режиме ЧПУ. Она не заменяет nginx, Apache, rewrite, права на инфоблок и проверку кеша. Она также не создаёт уникальность <code>CODE</code>. Если коды могут повторяться, фильтру нужен подтверждённый контекст: раздел или внешний идентификатор.</p>\n<p>Не меняйте данные как первую реакцию на 404. Сначала разделите три результата: шаблон не распознан, переменная не заполнена, элемент исключён фильтром. После этого причина привязана к конкретному слою.</p>\n<p>Готовность проверяется так: каждый контрольный URL даёт страницу <code>detail</code>, непустой <code>ELEMENT_CODE</code>, ровно один ожидаемый элемент в том же фильтре и обратную ссылку, которая снова проходит разбор. Для отрицательных случаев проверка показывает понятную причину: пустой шаблон, пустой код или ноль результатов.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://dev.1c-bitrix.ru/api_help/main/reference/ccomponentengine/parsecomponentpath.php\" target=\"_blank\" rel=\"noopener\">Bitrix: CComponentEngine::ParseComponentPath</a> — параметры SEF-папки, шаблонов, массива переменных и результат поиска шаблона.</li><li><a href=\"https://dev.1c-bitrix.ru/api_help/main/reference/ccomponentengine/makepathfromtemplate.php\" target=\"_blank\" rel=\"noopener\">Bitrix: CComponentEngine::MakePathFromTemplate</a> — подстановка значений массива в маркеры шаблона пути.</li><li><a href=\"https://dev.1c-bitrix.ru/api_help/iblock/classes/ciblockelement/getlist.php?print=Y\" target=\"_blank\" rel=\"noopener\">Bitrix: CIBlockElement::GetList</a> — выборка элементов по фильтру и поля <code>CODE</code>, <code>ACTIVE</code>; документация отмечает изменения метода в новых версиях модуля.</li></ul>"
}