Files
progcode/editorial/agent-rewrites/032.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
17 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 32,
"slug": "editorial-2027-02-mechanism-bitrix-lessons",
"title": "Bitrix API и версия: имя метода не обещает одинаковый контракт",
"excerpt": "Как проверить установленную поверхность Bitrix API, сопоставить поля legacy и D7 и остановить миграцию, если среда не подтверждает совместимость.",
"contentHtml": "<p>Ошибка начинается без падения. В документации найден метод, класс подключён, вызов возвращает ID или объект. Но на другой установке модуль не загружен, поле называется иначе, пустая строка означает другое состояние, а обработчик события меняет результат. Симптом появляется позже: форма теряет значение, импорт создаёт дубль, редкая операция падает после обновления. Цена ошибки — не только исправление PHP. Команда получает повреждённые данные, повторную загрузку и миграцию, которую уже нельзя безопасно повторить.</p>\n<p><strong>Тезис:</strong> совместимость Bitrix проверяют не по имени класса и не по номеру версии. Нужна граница из трёх фактов: модуль подключён, нужная поверхность API доступна, а вход и выход совпадают с контрактом проекта. Только после этого выбирают legacy-вызов, D7 или адаптер между ними.</p>\n<h2>Механизм ошибки</h2>\n<p>У старого и нового API может быть одна предметная область, но разные правила. Документация Bitrix указывает <code>CUser</code> и <code>Bitrix\\Main\\UserTable</code> как поверхности работы с пользователями. Это не утверждение, что вызовы взаимозаменяемы в конкретном проекте. У них могут различаться способ выборки, типы полей, ошибки, события и требования к версии ядра.</p>\n<p>Сначала проверяют загрузчик. <code>CModule::IncludeModule('iblock')</code> или <code>\\Bitrix\\Main\\Loader::includeModule('iblock')</code> отвечает на вопрос «модуль установлен и подключён?». Ответ <code>true</code> ещё не подтверждает нужный метод и mapping полей. Ответ <code>false</code> закрывает путь к следующему слою: нельзя маскировать отсутствие модуля вызовом класса, который случайно доступен через другой bootstrap.</p>\n<p>Затем проверяют поверхность. Нужны точные операции: найти запись, создать, обновить, получить идентификатор и разобрать ошибку. Проверка только существования класса слишком слаба. Она не отвечает, принимает ли метод нужные поля и сохранит ли различие между отсутствующим полем и явной очисткой.</p>\n<p>Последний слой — смысл результата. Успешный ID доказывает, что операция вернула идентификатор. Он не доказывает, что значение записалось в нужный формат, обработчики отработали ожидаемо, а публичная выборка увидит запись. Поэтому контракт нужно проверять через повторное чтение и отрицательные случаи.</p>\n<figure><img src='/assets/editorial/2027/bitrix-lessons-2027-version-boundary-matrix.svg' alt='Матрица проверки Bitrix API: модуль, поверхность, поля и семантика результата' loading='lazy' /><figcaption>Имя метода — только первый слой. Надёжная граница проходит через подключённый модуль, доступную операцию, mapping полей и проверенный смысл результата.</figcaption></figure>\n<h2>Учебный пример: один контракт для двух поверхностей</h2>\n<p>Ниже — учебная функция. Она не обращается к реальному серверу и не обещает поддержку перечисленных версий. Входной <code>manifest</code> нужно получить в конкретной среде безопасной диагностикой. Функция только разделяет отсутствие модуля, legacy-поверхность и D7-поверхность.</p>\n<pre><code>function chooseUserSurface(manifest) {\n if (!manifest.moduleLoaded) {\n return { kind: 'stop', reason: 'module-not-loaded' };\n }\n\n if (manifest.methods.includes('CUser::Update')) {\n return { kind: 'legacy', operation: 'update-user' };\n }\n\n if (manifest.methods.includes('Bitrix\\\\Main\\\\UserTable')) {\n return { kind: 'd7', operation: 'update-user' };\n }\n\n return { kind: 'stop', reason: 'operation-not-confirmed' };\n}\n\n// Учебные данные. Это не результат работы production-установки.\nconst surface = chooseUserSurface({\n moduleLoaded: true,\n methods: ['CUser::Update'],\n});\n\nconsole.log(surface);\n// { kind: 'legacy', operation: 'update-user' }</code></pre>\n<p>Важен порядок условий. Сначала функция останавливается при отсутствии модуля. Затем она выбирает подтверждённую операцию, а не любой похожий класс. Если обе поверхности доступны, выбор должен задавать адаптер проекта: например, установленная версия, зафиксированный набор полей и проверенная матрица регрессии. Автоматически предпочитать D7 только потому, что он новее, нельзя.</p>\n<p>Адаптер должен принимать канонический вход. Для пользователя это может быть объект с <code>id</code>, <code>email</code>, <code>phone</code> и явными состояниями <code>missing</code> и <code>clear</code>. Внутри адаптера поля переводятся в формат выбранного API. Так legacy-детали не расползаются по формам, импорту и обработчикам.</p>\n<pre><code>function toLegacyFields(user) {\n const fields = {};\n\n if (user.email.state === 'value') {\n fields.EMAIL = user.email.value;\n } else if (user.email.state === 'clear') {\n fields.EMAIL = '';\n }\n\n if (user.phone.state === 'value') {\n fields.PERSONAL_PHONE = user.phone.value.trim();\n }\n\n return fields;\n}</code></pre>\n<p>Этот код показывает только mapping. Он не вызывает <code>CUser::Update</code>, не проверяет права и не описывает локальные события. В рабочем проекте перед вызовом нужно зафиксировать, что означает пустое поле, кто владеет нормализацией, какие ошибки возвращает API и что должен увидеть read-back. Если D7-модель хранит поле в другом представлении, адаптер должен преобразовать его обратно в тот же канонический результат.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class='table-scroll'><table><caption>Диагностика границы Bitrix API</caption><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>Модуль не установлен или не подключён в этом bootstrap</td><td>Проверить <code>IncludeModule</code> в том же окружении и записать результат</td><td>Остановить операцию с диагностикой либо подключить модуль явно</td></tr><tr><td>Одинаковое имя поля даёт разный результат</td><td>Различаются тип, формат или пользовательское поле</td><td>Сравнить mapping, тип значения, <code>missing</code> и <code>clear</code></td><td>Оставить преобразование в адаптере и добавить read-back</td></tr><tr><td>Update вернул успех, но данные не видны</td><td>Проверяется только код ответа; фильтр или событие меняет выборку</td><td>Прочитать запись по ID и выполнить контрольный запрос с условиями каталога</td><td>Разделить факт записи и публичную видимость</td></tr><tr><td>После обновления появился неизвестный метод</td><td>Документация описывает другую версию или другую поверхность</td><td>Сверить версию ядра, модуль и фактический manifest операций</td><td>Вернуть адаптер к подтверждённой операции или ограничить поддержку</td></tr><tr><td>Повторный импорт создаёт новые записи</td><td>В контракте нет стабильного ключа и идемпотентного поиска</td><td>Повторить тот же вход и сравнить внешний ключ и результат чтения</td><td>Найти существующую запись по согласованному ключу до создания</td></tr><tr><td>Миграция проходит на тесте, но меняет production-смысл</td><td>Тест проверяет ID, но не события, права и пустые состояния</td><td>Добавить characterization-тесты для успеха, ошибки, retry и очистки</td><td>Не заменять поверхность до закрытия отрицательного пути</td></tr></tbody></table></div>\n<h2>Порядок действий</h2>\n<ol><li>Записать предметную операцию: что создаём, ищем или обновляем, какой результат считаем успехом и какие данные нельзя изменить.</li><li>Зафиксировать версию ядра, идентификатор модуля, bootstrap и официальный источник документации для выбранного вызова.</li><li>Собрать в целевой среде минимальный manifest: модуль подключён, класс или таблица доступны, нужные операции найдены.</li><li>Составить таблицу mapping для каждого поля. Отдельно описать значение, отсутствие, явную очистку, нормализацию и внешний ключ.</li><li>Проверить legacy и D7 на одном наборе учебных входов. Сравнить не только ID, но и read-back, коды ошибок и побочные события.</li><li>Спрятать выбранную поверхность за узким адаптером. Наружу вернуть канонический результат и классифицированную ошибку.</li><li>Проверить повторный запуск и отрицательный путь: отсутствующий модуль, неизвестная операция, плохое поле, отказ прав и частичный результат.</li><li>Если хотя бы один обязательный слой не подтверждён, остановить миграцию и оставить текущий вызов. Сначала закрыть неизвестность, затем менять API.</li></ol>\n<h2>Ограничения</h2>\n<p>Документация Bitrix описывает публичную поверхность, но не знает локальные обработчики, права, переопределения в <code>/local</code>, структуру инфоблока и фактический bootstrap. Две установки с одним номером версии могут иметь разные модули и данные. Поэтому ссылка на страницу API не является доказательством совместимости проекта.</p>\n<p>Manifest тоже не равен полному тесту. Он подтверждает доступность слоя, но не доказывает корректность SQL, событий и бизнес-правил. Не следует печатать в диагностике пользовательские данные. Достаточно версии, имени модуля и названий операций. Секреты и значения полей в такой вывод не входят.</p>\n<p>Иногда правильное решение — не мигрировать. Если legacy-вызов покрыт тестами, выполняет нужную операцию и новый API не даёт проверяемого выигрыша, адаптер может сохранить старую поверхность. Если новый метод доступен, но его mapping или события не доказаны, переход откладывают. Остановка с причиной безопаснее частичной миграции и ручного восстановления данных.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Граница готова, когда повторяемая проверка показывает: нужный модуль подключается; операция существует в каждой обязательной среде; поля имеют записанный mapping; значение, отсутствие и очистка дают ожидаемый read-back; ошибка и отказ прав не превращаются в успех; повторный запуск не создаёт дубль; а сборка и тесты проходят без ручного вмешательства. Для каждой версии нужен сохранённый результат проверки. Если нет хотя бы одного из этих доказательств, готов только план проверки, а не миграция.</p>\n<h2>Проверяемые источники</h2><ul><li><a href='https://dev.1c-bitrix.ru/api_help/main/reference/cmodule/includemodule.php' target='_blank' rel='noopener noreferrer'>Документация Bitrix: CModule::IncludeModule</a> — проверяет установку и подключение модуля; это не проверка конкретного метода или mapping.</li><li><a href='https://dev.1c-bitrix.ru/api_d7/bitrix/main/loader/includemodule.php' target='_blank' rel='noopener noreferrer'>Документация Bitrix D7: Loader::includeModule</a> — описывает D7-эквивалент загрузки модуля и результат подключения; локальная конфигурация проекта остаётся отдельной проверкой.</li><li><a href='https://dev.1c-bitrix.ru/api_d7/bitrix/main/usertable/index.php?print=Y' target='_blank' rel='noopener noreferrer'>Документация Bitrix D7: UserTable</a> — описывает класс для работы с пользователями и его ORM-поверхность; равенство с legacy-операциями нужно подтверждать тестами проекта.</li></ul>"
}