8 lines
19 KiB
JSON
8 lines
19 KiB
JSON
{
|
||
"index": 33,
|
||
"slug": "editorial-2027-02-practice-bitrix-lessons",
|
||
"title": "Bitrix legacy без догадок: сохранить вызов, поставить адаптер или заменить API",
|
||
"excerpt": "Как принять решение по старому Bitrix API: отделить наблюдаемое поведение от имени класса, проверить границу и не начинать миграцию без контракта.",
|
||
"contentHtml": "<p>После замены старого вызова Bitrix страница продолжает открываться, но новый пользователь не создаётся. В логах остаётся общий отказ. Административная форма показывает успех, хотя обработчик, который отправляет данные во внешнюю систему, не сработал. Цена ошибки — не один сломанный метод. Команде приходится восстанавливать порядок событий, формат полей и правила, которые раньше были спрятаны в legacy-коде.</p>\n<p>Возраст класса не доказывает его опасность. Имя нового API не доказывает совместимость. Безопасное решение начинается с наблюдаемого контракта: какие входы принимает код, какое состояние меняет, что возвращает, какие события запускает и как сообщает об отказе. Пока контракт не проверен, есть три действия: сохранить вызов, обернуть его адаптером или заменить после сравнения поведения.</p>\n<h2>Симптом сначала, название класса потом</h2>\n<p>Типичный симптом выглядит безобидно: после обновления вызова исчезает запись, меняется формат телефона или внешний обработчик получает пустой идентификатор. Система может не упасть. Она продолжит работать с неполным состоянием. Поэтому проверять нужно не только отсутствие исключения. Нужны чтение результата, события, права, повторный запуск и реакция на частичный отказ.</p>\n<p>Риск выше, если одна функция выполняет несколько операций. Она может привести email к нижнему регистру, создать пользователя, вызвать обработчик и вернуть ID. Замена класса меняет сразу четыре соглашения. Сравнение строк вызова этого не показывает.</p>\n<p>В Bitrix старый <code>CUser</code> и D7-класс <code>Bitrix\\\\Main\\\\UserTable</code> относятся к одной предметной области, но это не делает их взаимозаменяемыми в проекте. Нужно проверить mapping полей, способ ошибки, порядок событий и доступность модуля в конкретной установке. Документация даёт публичную поверхность API. Она не знает локальные обработчики, пользовательские поля и скрытые callers.</p>\n<figure><img src='/assets/editorial/2027/bitrix-lessons-2027-keep-wrap-replace-tree.svg' alt='Дерево решения для Bitrix legacy: проверка callers и контракта перед сохранением, адаптацией или заменой API' loading='lazy' /><figcaption>Сначала проверяют контракт. Если побочные эффекты неизвестны, ветка ведёт к остановке изменения и сбору фактов.</figcaption></figure>\n<h2>Три решения и их границы</h2>\n<p><strong>Сохранить</strong> — оставить текущий вызов и ограничить изменение вокруг него. Это подходит, когда callers мало, побочные эффекты не описаны, а задача не требует нового контракта. Сохранение не убирает технический долг. Оно не даёт неизвестному поведению разойтись дальше и оставляет обратимый шаг.</p>\n<p><strong>Обернуть</strong> — поставить одну границу между приложением и Bitrix API. Адаптер принимает поля проекта, проверяет обязательные значения, вызывает legacy-код и переводит ошибку в согласованный результат. Внешний код перестаёт зависеть от <code>PERSONAL_PHONE</code>, подключения модуля и объекта, в котором Bitrix хранит последнюю ошибку.</p>\n<p><strong>Заменить</strong> — перейти на новый контракт, а не только поменять имя класса. Сначала описывают mapping полей, порядок операций, успешный результат, ошибку, события и требования к версии. Затем запускают старый и новый путь на одинаковых входах. Если различие намеренное, его фиксируют как изменение поведения. Если случайное, замену откладывают.</p>\n<div class='table-scroll'><table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th scope='col'>Симптом</th><th scope='col'>Причина</th><th scope='col'>Проверка</th><th scope='col'>Действие</th></tr></thead><tbody><tr><td>Вызов вернул ID, но внешняя система не получила запись</td><td>событие зависит от старого порядка</td><td>журнал событий и повторное чтение записи</td><td>сохранить путь или обернуть его</td></tr><tr><td>Обновление прошло, поле стало пустым</td><td>пустое значение трактуется как очистка</td><td>сравнить отсутствие поля, null и пустую строку</td><td>задать mapping и правило пустого значения</td></tr><tr><td>Класс не найден</td><td>модуль не подключён или API недоступно в версии</td><td>проверить IncludeModule и поверхность методов</td><td>остановить замену и уточнить контракт</td></tr><tr><td>Повторный запуск создал дубль</td><td>операция не различает обработанный объект</td><td>запустить один вход дважды и сравнить ID</td><td>добавить ключ идемпотентности</td></tr><tr><td>Новый вызов работает для одного caller</td><td>caller-ы передают разные форматы</td><td>найти все места вызова и фактические входы</td><td>поставить адаптер с единым входом</td></tr></tbody></table></div>\n<p>Таблица задаёт порядок вопросов, но не доказывает причину сама. Для каждой строки нужен артефакт: лог, diff полей, результат повторного запуска, список callers или проверка подключения модуля. Если артефакта нет, решение остаётся гипотезой.</p>\n<h2>Учебный пример адаптера</h2>\n<p>Ниже приведён учебный PHP-фрагмент. Он показывает форму узкой границы и не утверждает, что конкретный проект должен обновлять пользователя именно так. Перед применением нужно сверить версию Bitrix, права, обработчики и правила хранения контактов.</p>\n<pre><code><?php\nfunction updateUserContact(int $userId, array $input): int\n{\n if ($userId < 1) {\n throw new InvalidArgumentException('userId must be positive');\n }\n\n $fields = [];\n if (array_key_exists('email', $input)) {\n $email = trim((string) $input['email']);\n if ($email === '' || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {\n throw new InvalidArgumentException('email is invalid');\n }\n $fields['EMAIL'] = strtolower($email);\n }\n\n if (array_key_exists('phone', $input)) {\n $fields['PERSONAL_PHONE'] = trim((string) $input['phone']);\n }\n if ($fields === []) {\n throw new InvalidArgumentException('no fields to update');\n }\n\n $user = new CUser();\n if ($user->Update($userId, $fields) === false) {\n throw new RuntimeException($user->LAST_ERROR);\n }\n return $userId;\n}</code></pre>\n<p>Адаптер делает три вещи. Он отличает отсутствие поля от переданного значения. Он приводит email к одному формату. Он переводит отказ Bitrix в исключение, которое может обработать caller. Он не объявляет успехом сам факт вызова метода. После обновления нужен read-back: получить пользователя, сравнить поля и проверить побочный обработчик.</p>\n<p>Отрицательный путь важнее короткого примера. Если проект разрешает очистить email пустой строкой, проверка выше неверна. Если обработчик ожидает исходный регистр, lowercase меняет контракт. Если старый метод обновляет связанные поля, узкий wrapper скрывает обязательную операцию. В этих случаях пример нельзя переносить целиком. Нужно изменить mapping, расширить контракт или оставить legacy-вызов.</p>\n<h2>Что выяснить до миграции</h2>\n<p>Начните с callers. Поиск по имени метода недостаточен: вызов может находиться в сервисе, обработчике события, шаблоне или административной форме. Для каждого caller запишите входные поля, права, ожидаемый результат и реакцию на ошибку. Если один caller передаёт пустую строку, а другой не передаёт поле, это разные операции.</p>\n<p>Затем зафиксируйте владельца состояния. Кто создаёт запись? Кто меняет её после события? Кто формирует внешний ID? Какой код считается успехом? Где находится последняя ошибка? Ответы должны подтверждаться кодом, логом или чтением данных. Фраза «Bitrix сам вызывает обработчик» не является проверкой: обработчик зависит от модуля, версии, прав и условий события.</p>\n<p>Третья граница — версия. Для старого API проверьте доступность модуля и метода в выполняемой среде. Для нового API проверьте namespace, mapping полей, типы значений и поведение исключений. Вызов <code>CModule::IncludeModule</code> должен быть частью проверки границы, а не строкой, которую добавляют после сбоя.</p>\n<pre><code><?php\nif (!CModule::IncludeModule('main')) {\n throw new RuntimeException('Bitrix main module is unavailable');\n}\n\n$user = new CUser();\n$ok = $user->Update($userId, $fields);\nif (!$ok) {\n throw new RuntimeException($user->LAST_ERROR);\n}</code></pre>\n<p>Этот второй фрагмент тоже учебный. Он проверяет подключение модуля, но не проверяет права, события, mapping или корректность данных. В конкретном проекте идентификатор модуля и способ вызова нужно сверить с установленной версией.</p>\n<h2>Действия по порядку</h2>\n<ol><li>Соберите один воспроизводимый симптом: вход, caller, версия среды, права, результат и цену отказа.</li><li>Найдите все вызовы и выпишите фактические поля, значения по умолчанию, события и обработку ошибок.</li><li>Проверьте подключение нужного модуля и доступную поверхность API в выполняемой версии.</li><li>Добавьте проверки для успеха, обязательного поля, пустого значения, ошибки и повторного запуска.</li><li>Выберите сохранить, обернуть или заменить. Если контракт неизвестен, остановите миграцию.</li><li>Для адаптера опишите canonical input/output: поля, формат, пустое состояние, ошибку, ID и владельца состояния.</li><li>Сравните старый и новый путь на одинаковых учебных входах. Проверьте read-back, события, права и отрицательный путь.</li><li>Оставьте только изменение, для которого можно назвать проверку, результат и способ отката.</li></ol>\n<h2>Ограничения и критерий готовности</h2>\n<p>Переход на D7 или другой новый слой не переносит автоматически пользовательские поля, события, права и внешние идентификаторы. Даже правильный mapping опасен, если операция выполняется внутри транзакции или участвует в импорте. Нужно понять поведение при повторе и частичном отказе.</p>\n<p>Unit-тест на чистом объекте не проверяет модуль, реальную версию, события и права. Но полная копия production-среды не обязательна для первого шага. Достаточно сузить границу, зафиксировать факты и назвать, чего учебная проверка не покрывает. Не следует объявлять замену готовой только потому, что новый метод вернул ожидаемый ID.</p>\n<p>Иногда правильный результат — не менять код. Если нет воспроизводимого входа, список callers неполный, а ошибка зависит от неизвестного обработчика, сохранение вызова снижает риск. Сначала получают недостающий контракт. Затем возвращаются к адаптеру или замене.</p>\n<p>Изменение готово, когда другой разработчик может повторить проверку по записи входа и получить тот же результат. В записи есть callers, версия и подключение модуля, mapping полей, события, успешный и отрицательный путь, read-back и способ отката. Для замены старый и новый путь дают одинаковый результат либо команда явно согласовала изменение. Если пункт неизвестен, готова не миграция, а следующая проверка.</p>\n<h2>Проверяемые источники</h2><ul><li><a href='https://dev.1c-bitrix.ru/api_help/main/reference/cuser/index.php' target='_blank' rel='noopener noreferrer'>1С-Битрикс: CUser</a> — официальный справочник класса, его полей и аналога в D7. Он не описывает локальные обработчики, права и callers проекта.</li><li><a href='https://dev.1c-bitrix.ru/api_help/main/reference/cmodule/includemodule.php' target='_blank' rel='noopener noreferrer'>1С-Битрикс: CModule::IncludeModule</a> — официальный способ проверить установку и подключение модуля перед обращением к его API. Результат зависит от среды.</li><li><a href='https://www.php.net/manual/en/function.filter-var.php' target='_blank' rel='noopener noreferrer'>PHP Manual: filter_var</a> — описание проверки значения фильтром. Она не подтверждает правила конкретного поля Bitrix и не заменяет проверку проекта.</li></ul>"
|
||
}
|