Есть неприятная ошибка, которую легко принять за кеш: открываешь карточку товара, а видишь другой товар с похожим названием. Особенно странно это выглядит после импорта — обе записи есть в админке, у обеих нормальные картинки, а URL одной вдруг показывает соседнюю. Здесь не нужно начинать с очистки кеша. Главный вопрос: как доказать конфликт CODE или широкий фильтр до того, как менять данные? Цена ошибки — исправить правильную запись и получить новый конфликт.
Первое правило — не смотреть только на название. Компонент получает строку из адреса и строит по ней выборку. Если выборка возвращает несколько элементов, значение «первого» зависит от порядка и условий запроса. Если она не возвращает ничего, компонент может отдать 404 или подставить другую ветку своей логики. Поэтому нам нужны три наблюдаемых факта: что было в URL, какую переменную получил компонент и сколько записей удовлетворяют его фильтру.
Не путать симптом и причину
Похожее название не доказывает конфликт. В одном каталоге может быть несколько позиций «Classic 250 г» в разных разделах, и тогда адрес обязан содержать достаточный контекст. Наоборот, разные названия могут получить одинаковый код после нормализации. Диагностику начинаю с конкретного сломанного адреса и ID товара, который ожидали увидеть. Только потом читаю список элементов по фактическому ELEMENT_CODE.
Какие данные собрать до исправления
| Факт | Зачем он нужен | Как зафиксировать |
|---|---|---|
| Исходный URL | Показывает, что реально запросил браузер | Сохранить полный путь из адресной строки или access-лога |
| Ожидаемый ID | Не даёт спорить о том, какая запись считается правильной | Взять ID из админки или из результата импорта |
| ELEMENT_CODE | Связывает путь с данными компонента | Вывести переменную после разбора ЧПУ на тестовом стенде |
| Все записи по CODE | Отличает один результат от конфликта | Сделать ограниченный GetList в том же инфоблоке |
| Фильтр детали | Объясняет, почему часть записей исключена или выбрана | Сверить с параметрами и кодом конкретного компонента |
Контрольная выборка
Документация CIBlockElement::GetList позволяет явно задать сортировку, фильтр, ограничение и набор полей. Для диагностики беру только те поля, которые помогают отличить записи: ID, имя, CODE, основной раздел и шаблон детального URL. Запрос не должен случайно тянуть свойства всего каталога: его задача — показать размер набора и порядок элементов.
<?php
function findActiveElementsByCode($iblockId, $code)
{
$result = CIBlockElement::GetList(
array("ID" => "ASC"),
array(
"IBLOCK_ID" => (int)$iblockId,
"=CODE" => $code,
"ACTIVE" => "Y",
),
false,
array("nTopCount" => 20),
array("ID", "NAME", "CODE", "IBLOCK_SECTION_ID", "DETAIL_PAGE_URL")
);
$items = array();
while ($item = $result->GetNext()) {
$items[] = $item;
}
return $items;
}
$items = findActiveElementsByCode(12, "classic-250-g");
if (count($items) !== 1) {
throw new RuntimeException("Нужно разобрать " . count($items) . " совпадений");
}
Сортировка по ID в этом примере нужна не для выбора «правильного» товара, а для повторяемого вывода. Если там два элемента, проблема уже доказана: детальный компонент не должен случайно решать, что меньший ID важнее. Дальше либо сужаем фильтр контекстом раздела, либо исправляем один из кодов по заранее выбранному правилу.
Как отличить три разных случая
| Результат проверки | Что это значит | Следующий шаг |
|---|---|---|
| 0 совпадений | URL разобран, но элемент не проходит базовый фильтр | Проверить значение переменной, активность, инфоблок и шаблон ссылки |
| 1 совпадение, ID правильный | Данные и базовый фильтр совпали | Сравнить дополнительные условия компонента и только затем кеш |
| 1 совпадение, ID другой | Переменная из URL не соответствует ожидаемому товару | Проверить генерацию URL, шаблон и исходный CODE элемента |
| 2 и более совпадений | Фильтр недостаточно точный или коды конфликтуют | Решить, нужен ли контекст раздела, затем изменить конфликтующие данные |
Проверяем, что компонент получил из URL
Не нужно угадать имя переменной по шаблону. Комплексный компонент разбирает путь через CComponentEngine::ParseComponentPath и возвращает переменные, восстановленные из маркеров. Для проблемной ссылки полезно на тестовой копии вывести $page и $arVariables. Так видно, не потерялся ли раздел и действительно ли ELEMENT_CODE равен строке из адреса.
<?php
$templates = array(
"detail" => "#SECTION_CODE#/#ELEMENT_CODE#/",
);
$variables = array();
$page = CComponentEngine::ParseComponentPath(
"/catalog/",
$templates,
$variables,
"/catalog/kofe/classic-250-g/"
);
if ($page !== "detail") {
throw new RuntimeException("Не найден шаблон detail");
}
error_log(print_r($variables, true));
Если в массиве нет SECTION_CODE, а детальный запрос должен учитывать раздел, коды элементов могут быть вполне корректны. Ошибка будет в URL-шаблоне или в логике компонента, который не применяет восстановленную переменную. И наоборот: если переменные верны, а GetList возвращает несколько записей, искать надо в данных и условиях выборки, не в роутинге.
Исправление без потери истории
Когда конфликт подтверждён, сначала выбираю правило для нового адреса: суффикс, артикул или раздел. Затем сохраняю старый URL и список мест, которые на него ссылаются. Смена CODE меняет адрес, поэтому публикацию лучше выполнять отдельным шагом с проверкой ссылок. Метод CIBlockElement::Update возвращает результат изменения; при ошибке не пропускаем LAST_ERROR.
<?php
$element = new CIBlockElement();
$updated = $element->Update($duplicateId, array(
"CODE" => "classic-250-g-2",
));
if (!$updated) {
throw new RuntimeException($element->LAST_ERROR);
}
// После изменения снова выполняем findActiveElementsByCode().
Порядок работы в продовой задаче
- Зафиксировать URL, ожидаемый ID и время, когда ошибка наблюдалась.
- На тестовой копии получить переменные, восстановленные из того же пути.
- Сделать выборку по фактическому
CODEв нужномIBLOCK_IDи посчитать результаты. - Сравнить полученные ID с тем, что показывает детальный компонент после его дополнительных фильтров.
- Если есть конфликт, выбрать новое стабильное правило кода и проверить все старые ссылки, которые важны для проекта.
- После изменения повторить URL-проверку. Кеш и индекс обновлять только по принятому в проекте порядку, когда данные и маршрут уже доказаны.
Ограничения
Эта заметка не утверждает, что любое совпадение CODE ошибочно. В некоторых каталогах один и тот же код допустим в разных витринах или разделах, и тогда адрес и фильтр обязаны включать этот контекст. Не следует добавлять раздел в запрос автоматически: сначала нужно понять, что именно считает идентичностью текущий компонент.
Также не стоит менять десятки кодов одной SQL-командой. У Bitrix есть API изменения элемента, обработчики событий и проектные зависимости от адресов. Сначала правим один доказанный конфликт на тестовых данных, проверяем маршрут и только потом составляем отдельный план для массовой миграции.
Итог
Когда адрес открывает не тот товар, удобнее не спорить о кеше, а посчитать факты. URL даёт переменную, переменная даёт набор элементов, набор показывает — это маршрут, фильтр или конфликт данных. После такой проверки изменение CODE становится осознанной операцией, а не попыткой наугад исправить карточку.
Проверяемые источники
- Bitrix: CIBlockElement::GetList — выборка элементов по фильтрам IBLOCK_ID, CODE, ACTIVE и с заданным порядком
- Bitrix: CComponentEngine::ParseComponentPath — разбор ЧПУ-пути по шаблонам и восстановление переменных компонента
- Bitrix: CIBlockElement::Update — изменение полей существующего элемента и результат операции