{ "index": 32, "slug": "editorial-2027-02-mechanism-bitrix-lessons", "title": "Bitrix API и версия: имя метода не обещает одинаковый контракт", "excerpt": "Как проверить установленную поверхность Bitrix API, сопоставить поля legacy и D7 и остановить миграцию, если среда не подтверждает совместимость.", "contentHtml": "

Ошибка начинается без падения. В документации найден метод, класс подключён, вызов возвращает ID или объект. Но на другой установке модуль не загружен, поле называется иначе, пустая строка означает другое состояние, а обработчик события меняет результат. Симптом появляется позже: форма теряет значение, импорт создаёт дубль, редкая операция падает после обновления. Цена ошибки — не только исправление PHP. Команда получает повреждённые данные, повторную загрузку и миграцию, которую уже нельзя безопасно повторить.

\n

Тезис: совместимость Bitrix проверяют не по имени класса и не по номеру версии. Нужна граница из трёх фактов: модуль подключён, нужная поверхность API доступна, а вход и выход совпадают с контрактом проекта. Только после этого выбирают legacy-вызов, D7 или адаптер между ними.

\n

Механизм ошибки

\n

У старого и нового API может быть одна предметная область, но разные правила. Документация Bitrix указывает CUser и Bitrix\\Main\\UserTable как поверхности работы с пользователями. Это не утверждение, что вызовы взаимозаменяемы в конкретном проекте. У них могут различаться способ выборки, типы полей, ошибки, события и требования к версии ядра.

\n

Сначала проверяют загрузчик. CModule::IncludeModule('iblock') или \\Bitrix\\Main\\Loader::includeModule('iblock') отвечает на вопрос «модуль установлен и подключён?». Ответ true ещё не подтверждает нужный метод и mapping полей. Ответ false закрывает путь к следующему слою: нельзя маскировать отсутствие модуля вызовом класса, который случайно доступен через другой bootstrap.

\n

Затем проверяют поверхность. Нужны точные операции: найти запись, создать, обновить, получить идентификатор и разобрать ошибку. Проверка только существования класса слишком слаба. Она не отвечает, принимает ли метод нужные поля и сохранит ли различие между отсутствующим полем и явной очисткой.

\n

Последний слой — смысл результата. Успешный ID доказывает, что операция вернула идентификатор. Он не доказывает, что значение записалось в нужный формат, обработчики отработали ожидаемо, а публичная выборка увидит запись. Поэтому контракт нужно проверять через повторное чтение и отрицательные случаи.

\n
Матрица проверки Bitrix API: модуль, поверхность, поля и семантика результата
Имя метода — только первый слой. Надёжная граница проходит через подключённый модуль, доступную операцию, mapping полей и проверенный смысл результата.
\n

Учебный пример: один контракт для двух поверхностей

\n

Ниже — учебная функция. Она не обращается к реальному серверу и не обещает поддержку перечисленных версий. Входной manifest нужно получить в конкретной среде безопасной диагностикой. Функция только разделяет отсутствие модуля, legacy-поверхность и D7-поверхность.

\n
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' }
\n

Важен порядок условий. Сначала функция останавливается при отсутствии модуля. Затем она выбирает подтверждённую операцию, а не любой похожий класс. Если обе поверхности доступны, выбор должен задавать адаптер проекта: например, установленная версия, зафиксированный набор полей и проверенная матрица регрессии. Автоматически предпочитать D7 только потому, что он новее, нельзя.

\n

Адаптер должен принимать канонический вход. Для пользователя это может быть объект с id, email, phone и явными состояниями missing и clear. Внутри адаптера поля переводятся в формат выбранного API. Так legacy-детали не расползаются по формам, импорту и обработчикам.

\n
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}
\n

Этот код показывает только mapping. Он не вызывает CUser::Update, не проверяет права и не описывает локальные события. В рабочем проекте перед вызовом нужно зафиксировать, что означает пустое поле, кто владеет нормализацией, какие ошибки возвращает API и что должен увидеть read-back. Если D7-модель хранит поле в другом представлении, адаптер должен преобразовать его обратно в тот же канонический результат.

\n

Симптом → причина → проверка → действие

\n
Диагностика границы Bitrix API
СимптомПричинаПроверкаДействие
Класс найден, вызов падает на части серверовМодуль не установлен или не подключён в этом bootstrapПроверить IncludeModule в том же окружении и записать результатОстановить операцию с диагностикой либо подключить модуль явно
Одинаковое имя поля даёт разный результатРазличаются тип, формат или пользовательское полеСравнить mapping, тип значения, missing и clearОставить преобразование в адаптере и добавить read-back
Update вернул успех, но данные не видныПроверяется только код ответа; фильтр или событие меняет выборкуПрочитать запись по ID и выполнить контрольный запрос с условиями каталогаРазделить факт записи и публичную видимость
После обновления появился неизвестный методДокументация описывает другую версию или другую поверхностьСверить версию ядра, модуль и фактический manifest операцийВернуть адаптер к подтверждённой операции или ограничить поддержку
Повторный импорт создаёт новые записиВ контракте нет стабильного ключа и идемпотентного поискаПовторить тот же вход и сравнить внешний ключ и результат чтенияНайти существующую запись по согласованному ключу до создания
Миграция проходит на тесте, но меняет production-смыслТест проверяет ID, но не события, права и пустые состоянияДобавить characterization-тесты для успеха, ошибки, retry и очисткиНе заменять поверхность до закрытия отрицательного пути
\n

Порядок действий

\n
  1. Записать предметную операцию: что создаём, ищем или обновляем, какой результат считаем успехом и какие данные нельзя изменить.
  2. Зафиксировать версию ядра, идентификатор модуля, bootstrap и официальный источник документации для выбранного вызова.
  3. Собрать в целевой среде минимальный manifest: модуль подключён, класс или таблица доступны, нужные операции найдены.
  4. Составить таблицу mapping для каждого поля. Отдельно описать значение, отсутствие, явную очистку, нормализацию и внешний ключ.
  5. Проверить legacy и D7 на одном наборе учебных входов. Сравнить не только ID, но и read-back, коды ошибок и побочные события.
  6. Спрятать выбранную поверхность за узким адаптером. Наружу вернуть канонический результат и классифицированную ошибку.
  7. Проверить повторный запуск и отрицательный путь: отсутствующий модуль, неизвестная операция, плохое поле, отказ прав и частичный результат.
  8. Если хотя бы один обязательный слой не подтверждён, остановить миграцию и оставить текущий вызов. Сначала закрыть неизвестность, затем менять API.
\n

Ограничения

\n

Документация Bitrix описывает публичную поверхность, но не знает локальные обработчики, права, переопределения в /local, структуру инфоблока и фактический bootstrap. Две установки с одним номером версии могут иметь разные модули и данные. Поэтому ссылка на страницу API не является доказательством совместимости проекта.

\n

Manifest тоже не равен полному тесту. Он подтверждает доступность слоя, но не доказывает корректность SQL, событий и бизнес-правил. Не следует печатать в диагностике пользовательские данные. Достаточно версии, имени модуля и названий операций. Секреты и значения полей в такой вывод не входят.

\n

Иногда правильное решение — не мигрировать. Если legacy-вызов покрыт тестами, выполняет нужную операцию и новый API не даёт проверяемого выигрыша, адаптер может сохранить старую поверхность. Если новый метод доступен, но его mapping или события не доказаны, переход откладывают. Остановка с причиной безопаснее частичной миграции и ручного восстановления данных.

\n

Проверяемый критерий готовности

\n

Граница готова, когда повторяемая проверка показывает: нужный модуль подключается; операция существует в каждой обязательной среде; поля имеют записанный mapping; значение, отсутствие и очистка дают ожидаемый read-back; ошибка и отказ прав не превращаются в успех; повторный запуск не создаёт дубль; а сборка и тесты проходят без ручного вмешательства. Для каждой версии нужен сохранённый результат проверки. Если нет хотя бы одного из этих доказательств, готов только план проверки, а не миграция.

\n

Проверяемые источники

" }