8 lines
19 KiB
JSON
8 lines
19 KiB
JSON
{
|
||
"index": 349,
|
||
"slug": "editorial-2018-04-field-bitrix-slugs",
|
||
"title": "Bitrix: как доказать конфликт символьных кодов и исправить адрес товара",
|
||
"excerpt": "Карточка товара открывает соседний элемент или даёт нестабильный результат? Разбираем путь от URL до фильтра CIBlockElement, считаем совпадения и меняем CODE только после проверки маршрута.",
|
||
"contentHtml": "<p>Карточка товара открывает не тот элемент: в адресе стоит <code>classic-250-g</code>, а на странице показан другой товар. Иногда ошибка появляется только после импорта. Обе записи видны в админке, картинки на месте, а один адрес ведёт на соседнюю карточку. Очистка кеша в такой ситуации не доказывает причину.</p>\n<p>Цена ошибки — не только одна неверная страница. Клиент видит чужую цену или остаток. Поисковик получает дублирующиеся адреса. Ссылки из писем и рекламы ведут к непредсказуемой записи. Если менять поля наугад, можно потерять старый адрес и усложнить восстановление.</p>\n<p>Тезис простой: сначала нужно посчитать, сколько элементов подходит под фактический <code>CODE</code>, и проверить, какую переменную компонент получил из URL. Только после этого становится ясно, где ошибка: в маршруте, в фильтре или в данных инфоблока.</p>\n<h2>Как возникает неправильная карточка</h2>\n<p>Браузер отправляет путь, например <code>/catalog/kofe/classic-250-g/</code>. Комплексный компонент разбирает этот путь по SEF-шаблону. SEF (Search Engine Friendly) — человекочитаемый адрес, который компонент сопоставляет с маркерами вроде <code>#SECTION_CODE#</code> и <code>#ELEMENT_CODE#</code>. Результат разбора попадает в массив переменных.</p>\n<p>Затем код компонента строит выборку по инфоблоку. Обычно в неё входят <code>IBLOCK_ID</code>, символьный код и признак активности. Проект может добавить раздел, права, витрину или свойство. URL сам по себе не выбирает строку базы. Между адресом и элементом стоит программный фильтр.</p>\n<figure><img src=\"/assets/editorial/2018/bitrix-slug-conflict-2018.svg\" alt=\"Диагностика неправильной карточки: URL, переменная ELEMENT_CODE, число совпадений GetList и действие\" /><figcaption>Разделяйте четыре шага: путь, переменная, выборка и выбранный элемент. Кеш проверяют после них.</figcaption></figure>\n<h2>Сначала зафиксируйте наблюдаемый симптом</h2>\n<p>Возьмите полный URL и ID товара, который должен открыться. Не переписывайте путь вручную. Запишите время запроса, инфоблок и фактический ID, который показала страница. Если ошибка плавающая, сохраните несколько ответов: порядок результатов может скрывать конфликт.</p>\n<p>Название товара не подходит для диагностики. Два товара могут называться «Classic 250 г». Разные названия могут получить один код после транслитерации и нормализации. Нужны значения, которыми реально пользуется компонент: <code>CODE</code>, <code>IBLOCK_ID</code>, раздел и дополнительные условия.</p>\n<h2>Контрольная выборка по CODE</h2>\n<p>Для первой проверки ограничьте запрос одним инфоблоком и активными элементами. Выведите ID, имя, код и раздел. Ограничение <code>nTopCount</code> защищает от чтения всего каталога, но не заменяет подсчёт: значение 20 означает «совпадений не меньше 20», а не точное число.</p>\n<pre><code><?php\n\nfunction findActiveElementsByCode($iblockId, $code)\n{\n $result = CIBlockElement::GetList(\n array(\"ID\" => \"ASC\"),\n array(\n \"IBLOCK_ID\" => (int)$iblockId,\n \"=CODE\" => $code,\n \"ACTIVE\" => \"Y\",\n ),\n false,\n array(\"nTopCount\" => 20),\n array(\"ID\", \"NAME\", \"CODE\", \"IBLOCK_SECTION_ID\", \"DETAIL_PAGE_URL\")\n );\n\n $items = array();\n while ($item = $result->GetNext()) {\n $items[] = $item;\n }\n\n return $items;\n}\n\n$items = findActiveElementsByCode(12, \"classic-250-g\");\nif (count($items) !== 1) {\n throw new RuntimeException(\"Найдено совпадений: \" . count($items));\n}</code></pre>\n<p>Сортировка по ID делает вывод повторяемым. Она не выбирает правильный товар. Если запрос вернул два элемента, меньший ID не становится владельцем адреса. Компонент не должен случайно решать спор порядком строк.</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>0 совпадений</td><td>Неверный код, инфоблок, активность или фильтр</td><td>Вывести <code>ELEMENT_CODE</code> и поочерёдно убрать проектные условия</td><td>Исправить источник URL или условие выборки после сравнения с данными</td></tr><tr><td>1 совпадение, ID ожидаемый</td><td>Базовые данные верны</td><td>Сверить дополнительные фильтры компонента и кеширование</td><td>Не менять <code>CODE</code>; искать расхождение после базовой выборки</td></tr><tr><td>1 совпадение, ID другой</td><td>В URL попал не тот код или ожидаемый ID неверен</td><td>Сопоставить URL, массив переменных и ссылку-источник</td><td>Исправить генерацию ссылки или исходную запись</td></tr><tr><td>2 и более совпадений</td><td>Конфликт кодов или слишком широкий фильтр</td><td>Вывести все ID, разделы и полный фильтр детали</td><td>Выбрать уникальное правило кода или добавить доказанный контекст</td></tr><tr><td>Результат меняется между запросами</td><td>Нет явной сортировки, кеш или несколько слоёв маршрута</td><td>Повторить запрос с сортировкой и сравнить логи до кеша</td><td>Устранить неоднозначность, затем отдельно проверить кеш</td></tr></tbody></table></div>\n<h2>Проверьте, что пришло из URL</h2>\n<p>Если контрольная выборка выглядит правильно, проверьте разбор пути. Метод <code>CComponentEngine::ParseComponentPath</code> получает папку, шаблоны и текущий путь. Он возвращает имя найденного шаблона, а переменные записывает в переданный массив. Это позволяет увидеть ошибку до обращения к инфоблоку.</p>\n<pre><code><?php\n\n$templates = array(\n \"detail\" => \"#SECTION_CODE#/#ELEMENT_CODE#/\",\n);\n$variables = array();\n$page = CComponentEngine::ParseComponentPath(\n \"/catalog/\",\n $templates,\n $variables,\n \"/catalog/kofe/classic-250-g/\"\n);\n\nif ($page !== \"detail\") {\n throw new RuntimeException(\"Не найден шаблон detail\");\n}\n\nif (empty($variables[\"ELEMENT_CODE\"])) {\n throw new RuntimeException(\"Пустой ELEMENT_CODE\");\n}\n\nerror_log(print_r($variables, true));</code></pre>\n<p>В учебном примере ожидается <code>SECTION_CODE=kofe</code> и <code>ELEMENT_CODE=classic-250-g</code>. Это пример для проверки на тестовой странице, а не утверждение о значениях в вашем проекте. В рабочем компоненте имена маркеров и папка могут отличаться.</p>\n<p>Если <code>$page</code> пуст, запрос не совпал с шаблоном. Не меняйте код элемента: сначала проверьте SEF-папку, начальный слеш и правила веб-сервера. Если шаблон найден, но <code>ELEMENT_CODE</code> пуст, сравните имя маркера с ключом, который читает компонент.</p>\n<p>Если переменная верна, а <code>GetList</code> возвращает несколько записей, проблема находится в данных или фильтре. Если запрос возвращает одну запись, но компонент показывает другую, добавьте к диагностике условия компонента: раздел, права, активность, дату публикации и свойства витрины.</p>\n<h2>Проверьте обратное направление</h2>\n<p>Ссылка и разбор должны использовать одну форму шаблона. Вызов <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/\".\n// Папку /catalog/ добавляет отдельный слой маршрута.</code></pre>\n<p>Если генератор ссылки использует <code>#CODE#</code>, а детальная страница ждёт <code>#ELEMENT_CODE#</code>, данные могут быть безупречны, но маршрут будет читать не то поле. Сверьте шаблон ссылки и шаблон компонента на конкретной странице.</p>\n<h2>Исправление подтверждённого конфликта</h2>\n<p>Не исправляйте несколько элементов одной массовой операцией до проверки одного случая. Сначала определите правило уникальности. Для каталога это может быть артикул, раздел, производитель или безопасный суффикс. Правило должно быть стабильным: название товара может измениться, а адрес не должен зависеть от случайного порядка импорта.</p>\n<p>Смена <code>CODE</code> меняет URL. До изменения сохраните старое значение, новый адрес, ID элемента и места, где старый URL формируется или хранится. Решите, нужен ли редирект со старого адреса. Проверьте ссылки из шаблонов, фидов, sitemap и интеграций, которые входят в область вашего проекта.</p>\n<pre><code><?php\n\n$element = new CIBlockElement();\n$updated = $element->Update($duplicateId, array(\n \"CODE\" => \"classic-250-g-2\",\n));\n\nif (!$updated) {\n throw new RuntimeException($element->LAST_ERROR);\n}\n\n// После изменения снова выполняем выборку по новому CODE.</code></pre>\n<p>Значение <code>classic-250-g-2</code> — учебный пример. Подставьте правило своего каталога и заранее проверьте, что новый код свободен в нужном инфоблоке. Нельзя считать суффикс защитой, если следующий импорт снова создаёт тот же код.</p>\n<h2>Порядок действий</h2>\n<ol><li>Сохраните проблемный URL, ожидаемый ID и фактический результат страницы.</li><li>Определите папку ЧПУ, шаблон детали и вызванный компонент.</li><li>На тестовой копии выведите результат <code>ParseComponentPath</code> и значение <code>ELEMENT_CODE</code>.</li><li>Выполните ограниченную выборку в том же <code>IBLOCK_ID</code> с теми же базовыми условиями и явной сортировкой.</li><li>Сравните все найденные ID с фильтром детального компонента и исключите вариант «ID выбирается первым».</li><li>Если конфликт доказан, выберите новое уникальное правило и проверьте его на данных элемента.</li><li>Измените один элемент через API Bitrix и сохраните ошибку <code>LAST_ERROR</code>, если операция не прошла.</li><li>Повторите выборку, разбор URL, открытие карточки и проверку старых ссылок.</li><li>Обновите кеш или индекс только после проверки данных и маршрута по принятому в проекте порядку.</li></ol>\n<h2>Ограничения и отрицательный путь</h2>\n<p>Одинаковый <code>CODE</code> не всегда ошибка. Некоторые проекты разделяют витрины или каталоги контекстом. Тогда уникальность задаёт не один код, а комбинация <code>IBLOCK_ID</code>, раздела и дополнительных условий. Не добавляйте раздел в фильтр автоматически. Сначала установите, какой набор полей проект считает идентичностью товара.</p>\n<p>Если совпадений нет, не делайте вывод о конфликте. Причиной может быть отключённый элемент, другой инфоблок, неверная транслитерация или фильтр по правам. Если URL не разбирается, исправление <code>CODE</code> не поможет: запрос не дошёл до выборки.</p>\n<p>Кеш может скрыть исправление, но он не объясняет два элемента в контрольной выборке. И наоборот, очистка кеша не исправит неверный SEF-шаблон. Диагностируйте слои в порядке прохождения запроса: URI, переменные, фильтр, данные, затем кеш.</p>\n<h2>Критерий готовности</h2>\n<p>Работу можно считать завершённой, когда один и тот же тестовый URL на чистом запросе разбирается в ожидаемый <code>ELEMENT_CODE</code>, контрольная выборка возвращает ровно один допустимый ID, детальный компонент показывает этот ID, а старый адрес либо ведёт на согласованный редирект, либо явно исключён из поддержки. Повторите проверку после прогрева кеша и с URL из реального источника. Результат должен оставаться тем же.</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/iblock/classes/ciblockelement/getlist.php?print=Y\" target=\"_blank\" rel=\"noopener\">Bitrix: CIBlockElement::GetList</a> — выборка элементов с фильтром, сортировкой, ограничением и набором полей.</li><li><a href=\"https://dev.1c-bitrix.ru/api_help/iblock/classes/ciblockelement/update.php?print=Y\" target=\"_blank\" rel=\"noopener\">Bitrix: CIBlockElement::Update</a> — изменение полей элемента и получение результата операции.</li></ul>"
|
||
}
|