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

Call to undefined function - причины по убыванию частоты

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

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

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

Окно терминала
grep -i "call to undefined" /var/log/php-fpm/error.log | tail -5
tail -30 $DOCROOT/bitrix/modules/error.log # журнал платформы, если он включён
# журнал платформы включают в .settings.php, в разделе обработки исключений

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

Выясняем, чья это функция:

Окно терминала
grep -rn "function ИМЯ_ФУНКЦИИ" bitrix/modules/ local/ | head -3
php -m | grep -iE 'mbstring|curl|gd|mysqli|zip' # расширения PHP на месте?
# консольный PHP и PHP веб-сервера бывают разными сборками с разным набором

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

Проверяем подключение модуля перед вызовом:

if (!\Bitrix\Main\Loader::includeModule('sale')) {
throw new \RuntimeException('модуль продаж не установлен или отключён');
}
// без этой строки классов и функций модуля в скрипте просто нет
// проверять результат обязательно: в младших редакциях модуля может не быть

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

Запускаем свой скрипт с прологом:

$_SERVER['DOCUMENT_ROOT'] = realpath(__DIR__ . '/../..');
define('NO_KEEP_STATISTIC', true);
define('NOT_CHECK_PERMISSIONS', true);
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';
// без пролога доступен только чистый PHP: ни ядра, ни автозагрузки классов

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

Проверяем автозагрузку своего класса:

var_dump(class_exists(\Vendor\Shop\Helper::class)); // класс виден автозагрузке?
\Bitrix\Main\Loader::registerAutoLoadClasses('vendor.shop', [
'\\Vendor\\Shop\\Helper' => 'lib/helper.php', // путь считается от папки модуля
]);
// имя файла и регистр букв обязаны совпадать с записью автозагрузки

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

Причины

  1. Модуль не подключён перед вызовом примерно 30% случаев

    ПризнакИмя функции или класса принадлежит модулю: продажи, каталог, инфоблоки.

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

    Что делатьПодключаем модуль и проверяем результат подключения, а не полагаемся на то, что его подключил кто-то раньше.

  2. Скрипт запущен без пролога платформы примерно 25% случаев

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

    ПроверкаПроверяем, подключается ли пролог ядра и задан ли корень сайта до его подключения.

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

  3. Расширение PHP отсутствует или отключено примерно 20% случаев

    ПризнакИмя функции начинается с известной приставки расширения: работа со строками, запросы, картинки.

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

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

  4. Функция удалена в новой версии языка примерно 15% случаев

    ПризнакОшибка появилась ровно после перехода на новую версию PHP, до этого код работал годами.

    ПроверкаИщем вызов в своём коде и сверяем функцию со списком удалённого в этой версии языка.

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

  5. Свой класс или файл не загрузился примерно 10% случаев

    ПризнакНе найден метод или функция собственного кода проекта, а файл при этом на месте.

    ПроверкаСверяем имя класса с путём файла и проверяем, регистрируется ли автозагрузка своего модуля.

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

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

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

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

Сбрасываем кэш байт-кода после выкладки. Устаревший скомпилированный файл переживает замену исходника, и тогда ошибка указывает на строку, которой в файле давно нет.

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

Функция есть в документации, но на сайте её нет - почему?

Модуль, которому она принадлежит, не установлен в этой редакции или не подключён в текущем скрипте. Проверяют оба условия: наличие модуля в списке установленных и его подключение перед вызовом.

Что нужно скрипту по расписанию, чтобы функции ядра работали?

Подключение пролога платформы и заданный корень сайта до него. Без пролога доступен только чистый PHP: ни функций старого ядра, ни автозагрузки классов в таком скрипте нет.

Ошибка появилась сразу после перехода на новый PHP.

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

Сообщение говорит про метод, а не про функцию.

Класс найден, а метода в нём нет: обычно это код, написанный под другую версию продукта или модуля. Смотрят версию модуля и историю изменений его класса.

Как быстро понять, чья это функция?

Поиском по каталогу модулей и по своему коду, а затем по списку расширений языка. Три этих места покрывают почти все случаи, а четвёртое - стороннее решение из Маркетплейса.

Смежное

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