8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"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>"
|
||
}
|