+7 499 938 8452 пн.-пт. 10:00 – 17:00

Какие ошибки чаще всего ломают кастомизации Битрикс24 после перехода на PHP 8

Обновление платформы почти всегда выявляет скрытые проблемы в кастомном коде. Чаще всего портал падает не из-за самой новой версии PHP или ORM, а из-за старых привычек разработки: неявных типов, неаккуратных выборок, тяжелых запросов и кода, который годами жил на допущениях.

В этой статье мы собрали практические сценарии, которые регулярно встречаются в проектах на коробочной Битрикс24. Материал будет полезен тем, кто поддерживает CRM-порталы, дорабатывает компоненты, пишет свои таблицы ORM и отвечает за стабильность сайта после обновлений.

Почему проблемы проявляются именно после обновления

Когда проект долго работает на одной версии ядра и PHP, многие слабые места остаются незаметными. PHP 8 стал строже относиться к типам данных, а ORM — к структуре запросов и связей между сущностями. В результате код, который раньше «терпел» некорректные данные, теперь начинает падать фатально.

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

ORM: что чаще всего идет не так

Новый ORM в Битрикс24 удобнее старых процедурных методов, но и требования к нему выше. Разработчики часто переносят старые привычки в новый API, а именно здесь и начинаются проблемы: лишние поля, неправильные join-условия, попытки вставить SQL-выражения в массивы и обработка огромных выборок в память.

1. Слишком широкая выборка

Самая частая ошибка — не задавать явный список полей. В результате ORM возвращает больше данных, чем реально нужно, а это особенно дорого для таблиц с текстовыми полями, JSON, служебными колонками и множеством связей.

// Плохо: вытягиваем все поля
$result = \Bitrix\Main\UserTable::getList([
    'filter' => ['ACTIVE' => 'Y']
]);

// Лучше: берем только то, что используется
$result = \Bitrix\Main\UserTable::query()
    ->setSelect(['ID', 'LOGIN', 'NAME', 'LAST_NAME'])
    ->where('ACTIVE', 'Y')
    ->exec();

На небольшом списке разница почти незаметна, но на реальных порталах она быстро превращается в лишнюю нагрузку на БД и память PHP. Если запрос используется в списке, карточке или AJAX-обработчике, сначала подумайте, какие поля действительно нужны.

2. Перегрузка памяти через fetchAll

Еще одна типовая ошибка — сразу забирать весь результат в массив через fetchAll(). Это удобно, но опасно: при больших выборках память процесса расходуется очень быстро, а потом появляются ошибки вида Allowed memory size exhausted.

// Плохо: все строки сразу в память
$rows = \Bitrix\Crm\DealTable::getList([
    'select' => ['ID', 'TITLE', 'OPPORTUNITY'],
    'filter' => ['=STAGE_ID' => 'NEW']
])->fetchAll();

// Лучше: построчная обработка
$result = \Bitrix\Crm\DealTable::query()
    ->setSelect(['ID', 'TITLE', 'OPPORTUNITY'])
    ->where('STAGE_ID', 'NEW')
    ->exec();

while ($row = $result->fetch()) {
    processDeal($row);
}

Если данных очень много, лучше читать их батчами: по 500–1000 строк, с сортировкой и контролем последнего ID. Такой подход стабильнее и проще для поддержки.

3. Неправильные JOIN и runtime-поля

Сложные связи между таблицами часто ломаются из-за неявных условий. ORM требует точного описания join-логики, а попытка «прикрутить» константу прямо в reference-условие нередко заканчивается ошибкой или неверным SQL.

use Bitrix\Main\ORM\Query\Join;
use Bitrix\Main\Entity\ReferenceField;
use Bitrix\Main\DB\SqlExpression;

$query = \Bitrix\Iblock\ElementTable::query();

$query->registerRuntimeField(
    new ReferenceField(
        'PROP_BRAND',
        \Bitrix\Iblock\ElementPropertyTable::class,
        Join::on('this.ID', 'ref.IBLOCK_ELEMENT_ID')
            ->where('ref.IBLOCK_PROPERTY_ID', new SqlExpression('?i', 42))
    )
);

Такой вариант предсказуемее, чем попытки встроить числовые значения в описание связи напрямую. Если запрос стал слишком сложным, полезно посмотреть итоговый SQL через методы диагностики и проверить, как ORM реально интерпретировал условия.

4. Удаление не тем способом

В простых таблицах можно удалить запись по одному ID, но в системных сущностях это работает не всегда. Причина в том, что часть таблиц использует составной первичный ключ, и тогда простой вызов delete($id) уже не подходит.

// Для таблиц с одиночным PK
SomeTable::delete(123);

// Для составного ключа
\Bitrix\Main\TaskOperationTable::delete([
    'TASK_ID' => 456,
    'OPERATION_ID' => 7,
]);

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

PHP 8: где чаще всего рвется старый код

После перехода на PHP 8 типовые проблемы обычно повторяются из проекта в проект. Это не «каприз версии», а следствие того, что язык перестал молча закрывать глаза на невалидные значения и несовместимые сигнатуры.

1. Функции массива на null

Одна из самых массовых причин падения — передача null в функции, которые ожидают массив. Раньше это часто ограничивалось warning, а теперь легко превращается в фатальную ошибку.

// Потенциальная проблема
$key = array_search('active', $arParams['STATUS_LIST']);

// Безопасный вариант
$statusList = $arParams['STATUS_LIST'] ?? [];
$key = is_array($statusList) ? array_search('active', $statusList) : false;

Аналогично стоит проверять count(), in_array(), usort(), foreach и любые другие операции, завязанные на массив. Если переменная приходит извне, нужно считать, что она может быть пустой, null или иметь неожиданный формат.

2. foreach по не-массиву

Очень часто шаблон компонента работает с данными, которые на одном объекте — массив, а на другом — null. В PHP 8 это уже не «мелкое отклонение», а повод для ошибки.

$items = (array)($arResult['ITEMS'] ?? []);

foreach ($items as $item) {
    $photos = (array)($item['PROPERTIES']['PHOTOS']['VALUE'] ?? []);
    $tags = (array)($item['PROPERTIES']['TAGS']['VALUE'] ?? []);
}

Приведение к массиву — не идеальная архитектура, но на этапе миграции это часто лучший способ быстро стабилизировать код. Важно не просто «погасить» ошибку, а потом вернуться к нормальной валидации данных на входе.

3. Несовпадение сигнатур

Еще одна зона риска — наследование классов и реализация интерфейсов. Старые библиотеки нередко написаны без строгих типов, а PHP 8 требует аккуратного совпадения аргументов, возвращаемых значений и nullable-типов.

class MyIterator implements \SeekableIterator
{
    public function seek(int $offset): void
    {
        // ...
    }
}

Если в старом модуле сигнатуры не совпадают с требованиями интерфейса или родительского класса, ошибка может проявиться только после обновления. Поэтому при миграции нужно отдельно проверять все переопределенные методы, особенно в библиотеках, написанных несколько лет назад.

4. Нестатический вызов как статический

В legacy-коде встречается вызов обычного метода через двойное двоеточие. Раньше это могло работать или давать предупреждение, но в новой версии PHP такие конструкции лучше считать ошибкой проектирования.

// Плохо
CMyLegacyModule::doSomething();

// Лучше
$module = new CMyLegacyModule();
$module->doSomething();

Если метод не должен быть статическим, не пытайтесь подгонять вызов под старый стиль. Гораздо безопаснее привести код к нормальной объектной модели, чем наращивать еще один слой совместимости.

Практика безопасной миграции

Чтобы обновление не превратилось в серию аварийных исправлений, миграцию нужно проводить поэтапно. Сначала выявляются самые опасные места: шаблоны, AJAX-обработчики, ORM-запросы, старые модули и участки с массивами, которые приходят извне.

Затем код переводится в более строгий режим: явные select-поля, защитные проверки is_array(), осторожная работа с fetchAll(), контроль сигнатур и отказ от неочевидных допущений. Такой подход дает не только совместимость, но и более предсказуемую поддержку проекта в будущем.

Полезные приемы

  • Используйте явные поля в ORM-запросах, а не выборку «всего подряд».
  • Обрабатывайте большие наборы данных потоково, а не одним массивом.
  • Проверяйте все входные данные, которые могут оказаться null или не массивом.
  • Сверяйте сигнатуры методов при наследовании и реализации интерфейсов.
  • Перед обновлением PHP обязательно прогоняйте критичные сценарии на тестовой копии портала.

Что стоит проверить отдельно

Особенно внимательно нужно смотреть на кастомные компоненты, которые работают с каталогом, CRM, инфоблоками и пользовательскими свойствами. Именно там обычно накапливается больше всего «тихих» технических долгов: неявные массивы, устаревшие методы и выборки, рассчитанные на старую версию ядра.

Если в проекте есть свой ORM-слой, отдельные события, кеширование коллекций или модули интеграции с внешними сервисами, их тоже стоит проверить до запуска обновления. В таких местах проблемы часто проявляются не сразу, а через нагрузку, очереди и фоновые агенты.

Вывод для команды

Переход на ORM и PHP 8 — это не просто техническое обновление, а проверка качества всего кода проекта. Чем раньше вы начнете относиться к данным строго, тем меньше будет аварий после деплоя.

Для поддержки живого портала важнее не «красивый код» сам по себе, а код, который одинаково стабилен на реальных данных, под нагрузкой и после очередного обновления платформы.



Назад в раздел

Подписаться на новые материалы раздела:












CAPTCHA