8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"index": 325,
|
||
"slug": "editorial-2018-12-field-legacy-refactoring",
|
||
"title": "Bitrix: как заменить генерацию CODE в legacy-коде и не потерять исходное значение",
|
||
"excerpt": "Точечная замена обработчика Bitrix начинается с одного элемента: фиксируем исходный CODE, проверяем инфоблок, записываем только нужное поле и читаем результат обратно. Отдельно разбираем отрицательный путь и осторожный откат.",
|
||
"contentHtml": "<p>После изменения формы карточка товара открывается по старому адресу, а новая запись получает другой URL. Иногда поле <code>CODE</code> остаётся пустым. Иногда его меняет обработчик, о котором забыли. Цена ошибки — 404 для опубликованной страницы, сломанная ссылка в выгрузке и риск затереть чужое изменение при поспешном откате.</p><p>Причина часто лежит в одном legacy-обработчике. Он читает запрос, строит символьный код, обновляет элемент инфоблока и показывает сообщение формы. Успешный ответ формы не доказывает, что изменился правильный элемент. Ответ <code>true</code> от API не доказывает, что после событий в базе лежит ожидаемая строка.</p><p><strong>Тезис:</strong> первую замену ограничивают одним вызовом и одним полем. До записи подтверждают ID и <code>IBLOCK_ID</code>. После записи снова читают элемент по тому же ID. Откат маршрута и восстановление данных считают разными операциями.</p><h2>Где возникает расхождение</h2><p>Форма передаёт ID и название. Старый обработчик вызывает <code>legacyUpdateCode()</code>. Функция транслитерирует название и передаёт результат в <code>CIBlockElement::Update</code>. Рядом могут работать импорт, cron-задача и обработчик события. Они используют похожий код, но принимают разные входы.</p><p>Если сразу заменить функцию во всех местах, после сбоя неясно, что сработало: неверный ID, другой инфоблок, правило транслитерации или обработчик Bitrix. Сначала нужен контролируемый шов. Он получает выбранный ID и название, знает ожидаемый инфоблок, меняет только <code>CODE</code> и возвращает результат вызывающему коду. HTML, редирект и отправка письма остаются снаружи.</p><figure><img src=\"/assets/editorial/2018/bitrix-legacy-replacement-rollback-2018.svg\" alt=\"Схема точечной замены генерации CODE в Bitrix с проверкой элемента, повторной выборкой и раздельным откатом маршрута и данных\" /><figcaption>Сначала проверяем один элемент и сохраняем его исходный CODE. При расхождении выключаем новый маршрут. Старое значение восстанавливаем только после проверки, что его не изменил другой процесс.</figcaption></figure><h2>Контракт операции</h2><p>До рефакторинга выпишите контракт старого вызова. Входом будут положительный ID, название для расчёта, ожидаемый ID инфоблока и разрешённый контур. Результатом — исходный код, рассчитанный код и фактический код после чтения. Отказ должен прекращать текущий путь, а не превращаться в сообщение об успехе.</p><p>В учебном примере числа <code>7</code> и <code>451</code> условны. Их нельзя переносить в рабочую систему. Они показывают, что проверка должна называть конкретный инфоблок и выбранный элемент, а не принимать эти значения из формы.</p><table><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td><code>CODE</code> пуст</td><td>Пустое название или пустой результат транслитерации</td><td>Проверить строку до <code>Update</code></td><td>Остановить запись</td></tr><tr><td>Изменился не тот элемент</td><td>ID пришёл из формы без проверки</td><td>Прочитать <code>ID</code> и <code>IBLOCK_ID</code> по фильтру</td><td>Не вызывать writer</td></tr><tr><td>Код после успеха другой</td><td>Событие или другая запись изменила поле</td><td>Сделать повторную выборку</td><td>Выключить новый маршрут</td></tr><tr><td>Откат затирает новое значение</td><td>Исходный снимок устарел</td><td>Сравнить текущее поле с результатом опыта</td><td>Не восстанавливать автоматически</td></tr></tbody></table><p>Таблица задаёт порядок расследования. Нельзя начинать с восстановления старого значения, пока неизвестно, кто записал текущее. Откат маршрута влияет на следующие вызовы. Откат данных меняет сам элемент и требует проверки снимка.</p><h2>Выделяем writer с одной ответственностью</h2><p>Ниже ограниченный учебный фрагмент для legacy Bitrix API. Он не является кодом конкретного production-проекта и не утверждает, что параметры подходят вашему каталогу. Метод подключает модуль, выбирает элемент, проверяет инфоблок, записывает только <code>CODE</code> и читает элемент ещё раз.</p><pre><code><?php\nfinal class CheckedCodeWriter\n{\n private $iblockId;\n public function __construct($iblockId) { $this->iblockId = (int) $iblockId; }\n public function writeFromName($elementId, $name)\n {\n if (!CModule::IncludeModule('iblock')) throw new RuntimeException('module unavailable');\n $before = $this->find((int) $elementId);\n if (!$before || (int) $before['IBLOCK_ID'] !== $this->iblockId) {\n throw new RuntimeException('unexpected element or iblock');\n }\n $code = CUtil::translit(trim((string) $name), 'ru', array(\n 'change_case' => 'L', 'replace_space' => '-',\n 'replace_other' => '-', 'delete_repeat_replace' => true, 'max_len' => 100,\n ));\n if ($code === '') throw new InvalidArgumentException('CODE is empty');\n $element = new CIBlockElement();\n if (!$element->Update((int) $elementId, array('CODE' => $code))) {\n throw new RuntimeException($element->LAST_ERROR ?: 'Update failed');\n }\n $after = $this->find((int) $elementId);\n if (!$after || (string) $after['CODE'] !== $code) {\n throw new RuntimeException('CODE differs after Update');\n }\n return array('before' => $before['CODE'], 'after' => $after['CODE']);\n }\n private function find($elementId)\n {\n $result = CIBlockElement::GetList(array(), array('ID' => $elementId), false, false,\n array('ID', 'IBLOCK_ID', 'CODE'));\n return $result->Fetch();\n }\n}</code></pre><p>Фрагмент показывает границу, а не готовую библиотеку. Он не проверяет права, уникальность кода, SEO-правила, торговые предложения или кеш. Он также не делает атомарной запись в инфоблок и вызов внешней системы. Эти условия добавляют отдельными проверками.</p><h2>Почему нужно читать элемент после Update</h2><p>Проверка булевого ответа полезна, но недостаточна. До обновления могут выполняться обработчики. <code>OnBeforeIBlockElementUpdate</code> может изменить поля или отменить изменение. После записи могут сработать другие действия. Поэтому проверяем не только вызов, но и состояние выбранного элемента.</p><p>Повторная выборка не доказывает согласованность всего каталога. Она отвечает на узкий вопрос: у элемента с этим ID находится ожидаемый <code>CODE</code>. Если ответ отрицательный, новый путь не готов. Сначала выключаем его для следующих запросов, затем разбираем вход, события и конкурирующие записи.</p><h2>Подключаем новый путь без массовой замены</h2><p>Старый обработчик может остаться точкой входа. Переключатель имеет безопасное значение по умолчанию и ограничивает новый маршрут выбранным сценарием:</p><pre><code><?php\ndefined('USE_CHECKED_CODE_WRITER') || define('USE_CHECKED_CODE_WRITER', false);\nfunction updateCodeForScenario($elementId, $name)\n{\n if (USE_CHECKED_CODE_WRITER !== true) return legacyUpdateCode($elementId, $name);\n if ((int) $elementId !== 451) throw new RuntimeException('example ID only');\n return (new CheckedCodeWriter(7))->writeFromName($elementId, $name);\n}</code></pre><p>В рабочем проекте ограничение задают конфигурацией и правилами доступа, а не параметром URL. Если тестового элемента нет, нельзя включать новый маршрут на рабочей карточке ради быстрой проверки.</p><h2>Порядок точечной замены</h2><ol><li>Найти известные вызовы старого обработчика: форму, импорт, cron и события. Не считать список полным без поиска по проекту.</li><li>Выбрать разрешённый тестовый элемент и записать его <code>ID</code>, <code>IBLOCK_ID</code> и исходный <code>CODE</code>.</li><li>Зафиксировать правила расчёта и проверить, что результат не пустой.</li><li>Вынести проверку элемента, запись одного поля и повторную выборку в writer.</li><li>Оставить старый маршрут включённым по умолчанию.</li><li>Выполнить операцию на тестовом контуре и сравнить фактический код с ожидаемым.</li><li>При расхождении выключить новый маршрут и проверить события до восстановления данных.</li><li>Расширять охват по одному вызову, сохраняя для каждого свои входы и критерии.</li></ol><h2>Отрицательный путь и откат</h2><p>Неверный ID, чужой инфоблок, пустое название, ошибка <code>Update</code> и несовпадение после чтения должны останавливать текущую операцию. Нельзя показывать сообщение «сохранено», если writer вернул исключение. Нельзя вызывать второй <code>Update</code> в обработчике ошибки без установленной причины.</p><p>Откат состоит из двух решений. Сначала вернуть флаг нового маршрута в безопасное состояние. Затем решить, нужно ли восстанавливать поле. Сравните текущее значение с тем, которое должен был поставить опыт. Если поле отличается, его мог изменить редактор или импорт. Автоматическая запись старого снимка тогда опасна.</p><h2>Ограничения метода</h2><p>Точечный writer не превращает старый модуль в новую архитектуру и не заменяет аудит всех источников записи. Один успешный элемент не проверяет дубликаты, разные языки, спецсимволы, права, индексацию и ссылки, построенные по старому коду.</p><p>Не передавайте в <code>Update</code> лишние поля ради полноты примера. Чем шире массив изменения, тем больше поверхность побочных эффектов. Если проект передаёт свойства элемента, отдельно изучите их контракт и обработчики.</p><p>Подход не решает транзакцию между Bitrix и внешним API. Ошибка внешней отправки не отменяет автоматически уже выполненную запись. Повтор и расхождение нужно описать отдельным процессом.</p><h2>Критерий готовности</h2><p>Замену можно считать готовой для выбранного сценария, если writer принимает проверяемый ID, подтверждает инфоблок, меняет только заявленное поле, не пропускает ошибку как успех, читает элемент после <code>Update</code>, получает ожидаемый <code>CODE</code> и отключается одной понятной настройкой. Для расхождения должен существовать запрет на слепой откат.</p><p>Это локальный критерий, а не доказательство готовности всей миграции. Для следующего вызова нужны новый снимок, новый ожидаемый результат и отдельная проверка. Если условия нельзя показать на одном разрешённом элементе, расширять замену нельзя.</p><h2>Проверяемые источники</h2><ul><li><a href=\"https://www.dev.1c-bitrix.ru/api_help/iblock/classes/ciblockelement/getlist.php\" target=\"_blank\" rel=\"noopener noreferrer\">1С-Битрикс: 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 noreferrer\">1С-Битрикс: CIBlockElement::Update</a></li><li><a href=\"https://dev.1c-bitrix.ru/api_help/iblock/events/onbeforeiblockelementupdate.php\" target=\"_blank\" rel=\"noopener noreferrer\">1С-Битрикс: OnBeforeIBlockElementUpdate</a></li></ul>"
|
||
}
|