8 lines
14 KiB
JSON
8 lines
14 KiB
JSON
{
|
||
"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><?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}</code></pre>\n<p>Вызов ограничен двумя строками, чтобы обнаружить неоднозначность. Ноль результатов указывает на <code>IBLOCK_ID</code>, код, активность или дополнительное условие. Два результата показывают слишком широкий фильтр. Нельзя брать первый элемент сортировкой и считать проблему решённой.</p>\n<h2>Проверка обратного направления</h2>\n<p>Карточка может ломаться при построении ссылки. Список собирает путь с одним именем переменной, а детальный компонент ждёт другое. Соберите учебный URL через <code>CComponentEngine::MakePathFromTemplate</code>, а затем разберите его тем же шаблоном.</p>\n<pre><code><?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/</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>"
|
||
}
|