Перейти к содержимому

Call to a member function on null - причины по убыванию частоты

Страница падает с сообщением об обращении к методу у пустого значения. Разбираем причины в порядке убывания частоты.

Как проверить

Читаем сообщение целиком:

Окно терминала
grep -i "on null" /var/log/php-fpm/error.log | tail -5
# имя метода сразу называет виновника: IncludeComponent - это $APPLICATION,
# IsAuthorized и GetUserGroupArray - $USER, getBasket - объект заказа

Сообщение называет метод, файл и строку, и метод здесь важнее всего. По его имени понятно, какой именно объект оказался пустым, а дальше остаётся ответить, почему его не существует в этом месте.

Объявляем глобальные объекты во включаемом файле:

global $APPLICATION, $USER; // во включаемом файле их нет по умолчанию
$APPLICATION->IncludeComponent('bitrix:news.list', '', []);
// это самая частая причина: файл из /include/ подключается внутри функции
// в шаблоне компонента объявлять их не нужно: платформа делает это сама

Включаемые файлы подключаются внутри функции ядра, а не в глобальной области. Переменные $APPLICATION и $USER там не видны, пока их не объявили глобальными явно - одной строкой в начале файла.

Переносим ранний код в событие:

// в init.php объекта пользователя ещё не существует: он создаётся позже
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'main', 'OnPageStart', ['\\Vendor\\Init', 'onPageStart']);
// в обработчике этого события глобальные объекты ядра уже на месте
// создавать объект пользователя руками не стоит: методы вернут пустоту

Файл начальной настройки выполняется раньше, чем создаётся объект пользователя. Обращение к нему прямо в этом файле падает всегда, и лечится это переносом кода в событие начала страницы, а не созданием объекта руками.

Проверяем, что объект вообще нашёлся:

$order = \Bitrix\Sale\Order::load($orderId);
if ($order === null) {
return; // заказа нет: дальше идти нельзя
}
$element = \Bitrix\Iblock\ElementTable::getById($id)->fetchObject();
// выборка возвращает пустоту молча, а падает уже следующая строка с методом
if (!$element) {
throw new \RuntimeException("элемент {$id} не найден"); // понятное сообщение
}

Выборка, не нашедшая записи, не бросает исключений. Она возвращает пустое значение, и ошибка появляется строкой ниже, в первом же обращении к методу - на чужой, совершенно исправной строке кода.

Причины

  1. Глобальные объекты не объявлены во включаемом файле примерно 30% случаев

    ПризнакПадает файл из включаемых областей, а метод в сообщении принадлежит приложению или пользователю.

    ПроверкаСмотрим начало файла: есть ли объявление нужных глобальных переменных.

    Что делатьОбъявляем их глобальными в первой строке файла; в шаблонах компонентов это делает сама платформа.

  2. Код выполняется до создания объектов ядра примерно 25% случаев

    ПризнакОшибка приходит из файла начальной настройки или из обработчика самого раннего события.

    ПроверкаПроверяем, на каком этапе выполняется код и существует ли к этому моменту нужный объект.

    Что делатьПереносим обращение в событие начала страницы, где объекты ядра уже созданы платформой.

  3. Результат выборки не проверен примерно 20% случаев

    ПризнакМетод в сообщении принадлежит заказу, элементу или корзине, а ошибка появляется не на всех страницах.

    ПроверкаСмотрим строку выше по коду: есть ли проверка того, что запись нашлась.

    Что делатьДобавляем проверку результата и осмысленный ответ на его отсутствие вместо продолжения работы.

  4. Ядро платформы не подключено вовсе примерно 15% случаев

    ПризнакФайл открывается по прямой ссылке или запускается из консоли, а падает на первой же строке.

    ПроверкаПроверяем, подключается ли пролог платформы до обращения к её объектам.

    Что делатьПодключаем пролог в начале файла либо переносим код туда, где ядро уже поднято.

  5. Файлы разошлись после переноса или обновления примерно 10% случаев

    ПризнакОшибка появилась разом на всём сайте сразу после переноса, копирования или обновления.

    ПроверкаСравниваем состав файлов с исходной копией и смотрим журнал обновления системы.

    Что делатьВозвращаем недостающие файлы из резервной копии; частичное копирование каталога ядра всегда заканчивается так.

Если ничего не помогло

Смотрим трассировку снизу вверх, а не сверху вниз. Верхняя строка называет место падения, а причину обычно видно в нижних: там указан файл проекта, который вызвал упавший код из неподходящего места.

Проверяем совпадение символьных кодов свойств с именами полей. В новом ядре свойство с кодом вроде даты активности перекрывает одноимённое поле, и обращение к нему возвращает пустоту вместо значения.

Ищем ошибку в почтовом шаблоне. Тело письма выполняется отдельно от страницы, и вставленный туда вызов компонента падает именно с этим сообщением, заваливая журнал одинаковыми записями.

Частые вопросы

Почему $APPLICATION пустой во включаемом файле?

Файл подключается внутри функции ядра, а не в глобальной области, поэтому глобальные переменные там не видны. Их объявляют глобальными в первой строке файла - в шаблонах компонентов это делается за вас.

Как обратиться к пользователю в файле начальной настройки?

Никак: объект пользователя создаётся позже этого файла. Код переносят в обработчик события начала страницы, где объект уже существует и работает штатно.

Создавать объект пользователя вручную - нормальное решение?

Это известный обходной путь, но он маскирует ошибку, а не решает её: методы такого объекта возвращают пустоту. Правильный путь - выполнять код тогда, когда платформа объект уже создала.

Ошибка появляется только на некоторых страницах.

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

После переноса сайта ошибка на всех страницах сразу.

Скорее всего, каталог ядра скопирован не полностью: часть файлов не доехала. Копируют его целиком или разворачивают из штатной резервной копии, а не через выборочную загрузку по FTP.

Смежное

Первоисточник