Fatal error в PHP — это ошибка, после которой интерпретатор останавливает выполнение скрипта: код после проблемной строки не выполняется, а пользователь получает оборванную страницу или ответ 500. Остальные сообщения — Warning, Notice, Deprecated — скрипт не останавливают: PHP сообщает о проблеме и идет дальше, часто уже с неверными данными.
Содержание
- Мини-словарь: ошибка, исключение, Throwable
- Как читать сообщение Fatal error
- Частые Fatal error и как их исправить
- Warning, Notice, Deprecated: скрипт работает дальше
- Error и Exception: что ловить в try/catch
- Что не ловится try/catch
- display_errors и log_errors: разработка и продакшен
- Шаблон обработки ошибок для веб-приложения
- Если не получилось: диагностика по симптому
- Выводы
- Где применяется / связь с практикой
- FAQ
Ниже — как читать Fatal error и чинить частые случаи, чем уровни ошибок отличаются от исключений Error/Exception и как настроить display_errors и log_errors для разработки и продакшена. Примеры и тексты сообщений получены прогоном в PHP 8.4.25 (CLI) 24.09.2026.
Мини-словарь: ошибка, исключение, Throwable
В PHP 8 «ошибками» называют два разных механизма:
- Уровень ошибки (
E_WARNING,E_NOTICE,E_DEPRECATED,E_ERRORи др.) — сообщение движка.try/catchего не ловит, нефатальные уровни перехватываетset_error_handler(). - Исключение — объект, который бросается (
throw) и ловитсяtry/catch; все исключения реализуют интерфейс Throwable. Ветка Error — ошибки движка (несуществующая функция, неверный тип,intdiv()на ноль), ветка Exception — ошибки логики, которые бросает ваш код или библиотека.
В PHP 8 большинство фатальных ошибок выглядит как «Fatal error: Uncaught …» — это непойманный Error или Exception. Фатальные уровни без объекта (нехватка памяти, превышение времени, синтаксис) встречаются реже, о них ниже.
Как читать сообщение Fatal error
Минимальный пример: функцию вызвали, но не объявили и не подключили.
<?php
echo "start\n";
$total = calculate_total([10, 20]);
echo "never\n";
start
Fatal error: Uncaught Error: Call to undefined function calculate_total() in /app/f1.php:3
Stack trace:
#0 {main}
thrown in /app/f1.php on line 3
Строку start PHP вывел, never — уже нет. Разбор сообщения:
Uncaught Error— брошено и не поймано исключение классаError. Класс подсказывает природу проблемы:TypeError— типы,DivisionByZeroError— деление на ноль.Call to undefined function calculate_total()— суть, ее и ищут в документации./app/f1.php:3— файл и строка.Stack trace— цепочка вызовов;{main}внизу — основной код файла, выше — функции, через которые прошли.
Исправление здесь — объявить функцию или подключить файл, где она описана (обычно через автозагрузку Composer):
<?php
function calculate_total(array $prices): int
{
return array_sum($prices);
}
echo calculate_total([10, 20]), "\n"; // 30
В журнале ошибок у сообщения есть префикс PHP Fatal error:, на экране — просто Fatal error:; в логах ищите по обоим вариантам. В PHP 8.5 стек вызовов добавляется и к фатальным ошибкам без объекта (директива fatal_error_backtraces, по умолчанию On).
Частые Fatal error и как их исправить
Сообщения получены прогоном на PHP 8.4; в PHP 7 часть текстов выглядела иначе.
| Сообщение (фрагмент) | Причина | Что сделать |
|---|---|---|
Call to undefined function calculate_total() |
Функция не объявлена или файл не подключен | Проверить имя, require/автозагрузку, namespace |
Class "User" not found |
Класс не подключен, неверный namespace или путь в автозагрузке | use, composer dump-autoload, соответствие PSR-4 |
Call to a member function getName() on null |
Переменная оказалась null, а не объектом |
Проверить, что вернул поиск; ?-> или явная проверка |
Failed opening required 'vendor/autoload.php' |
Нет файла для require |
Выполнить composer install, строить путь от __DIR__ |
must be of type int, string given (TypeError) |
Аргумент не того типа | Привести и проверить вход до вызова |
Cannot redeclare function helper() |
Функция объявлена дважды, часто из-за двойного include |
require_once или автозагрузка |
Allowed memory size of 67108864 bytes exhausted |
Скрипт превысил memory_limit |
Обрабатывать данные порциями (генераторы, построчное чтение) |
Maximum execution time of 1 second exceeded |
Превышен max_execution_time (значение из set_time_limit(1)) |
Искать бесконечный цикл или выносить долгую задачу в очередь |
Parse error: Unclosed '{' on line 3 |
Синтаксическая ошибка | Закрыть скобку, проверить php -l file.php |
Поднимать memory_limit имеет смысл, только если объем данных такой по задаче: если скрипт читает большой файл целиком, лимит лишь отодвинет падение. Файл с Parse error не выполняется совсем, поэтому ini_set('display_errors', '1') внутри него не поможет — включайте показ в php.ini или конфиге пула PHP-FPM, а файл проверяйте php -l.
Warning, Notice, Deprecated: скрипт работает дальше
Нефатальные уровни не останавливают выполнение, и в этом их опасность: код продолжает работать с пустыми или неверными данными.
<?php
$price = $_GET['price']; // ключа нет
echo "Цена: $price\n";
include 'config.local.php'; // файла нет
echo end(explode(',', 'a,b,c')), "\n"; // Notice
echo "5 apples" + 1, "\n";
echo "дошли до конца\n";
Warning: Undefined array key "price" in /app/w1.php on line 2
Цена:
Warning: include(config.local.php): Failed to open stream: No such file or directory in /app/w1.php on line 4
Warning: include(): Failed opening 'config.local.php' for inclusion (include_path='.:/usr/local/lib/php') in /app/w1.php on line 4
Notice: Only variables should be passed by reference in /app/w1.php on line 5
c
Warning: A non-numeric value encountered in /app/w1.php on line 6
6
дошли до конца
Скрипт дошел до конца, но с пустой ценой и без конфига. Исправленный вариант:
<?php
$price = $_GET['price'] ?? null;
if ($price === null) {
echo "Цена не передана\n";
}
$local = __DIR__ . '/config.local.php';
if (is_file($local)) {
require $local;
}
$parts = explode(',', 'a,b,c');
echo end($parts), "\n";
Цена не передана
c
Что важно знать про уровни в PHP 8:
- Обращение к неопределенной переменной или ключу массива в PHP 8 —
Warning, а неNotice, как в PHP 7. - Часть бывших предупреждений стала исключениями: арифметика со строкой без числа (
"abc" + 0) бросаетTypeError: Unsupported operand types, обращение к неопределенной константе —Error: Undefined constant. Deprecated— конструкция объявлена устаревшей и может быть удалена в будущих версиях. В PHP 8.4 так помечены неявно nullable-параметр (string $name = nullвместо?string $name = null), константаE_STRICT(ее сообщения еще в PHP 7 перевели в другие уровни) иtrigger_error()сE_USER_ERROR— вместо него бросайте исключение.
Error и Exception: что ловить в try/catch
Частая ошибка — ловить только Exception, ожидая, что это «все ошибки». Неверный вариант:
<?php
try {
echo intdiv(100, 0);
} catch (Exception $e) {
echo "Exception: ", $e->getMessage(), "\n";
}
echo "after\n";
Fatal error: Uncaught DivisionByZeroError: Division by zero in /app/c1.php:3
Это первая строка вывода, дальше идет стек. DivisionByZeroError — потомок Error, а не Exception, поэтому catch его пропустил, и after не вывелся. Исправление и иерархия:
<?php
$cases = [
fn() => throw new RuntimeException('Товар A-1 не найден'), // Exception
fn() => intdiv(100, 0), // Error
];
foreach ($cases as $case) {
try {
$case();
} catch (Exception $e) {
echo 'Exception ', $e::class, ': ', $e->getMessage(), "\n";
} catch (Error $e) {
echo 'Error ', $e::class, ': ', $e->getMessage(), "\n";
}
}
echo "скрипт продолжает работу\n";
Exception RuntimeException: Товар A-1 не найден
Error DivisionByZeroError: Division by zero
скрипт продолжает работу
| Ветка | Кто бросает | Примеры классов | Как обращаться |
|---|---|---|---|
Exception |
Ваш код и библиотеки | RuntimeException, InvalidArgumentException, PDOException |
Ловить там, где понятно, что делать дальше |
Error |
Движок PHP | TypeError, ValueError, DivisionByZeroError, ArgumentCountError |
Обычно это баг: исправлять код, а не глушить |
Throwable |
Общий интерфейс обеих веток | — | Ловить на верхнем уровне: залогировать и ответить 500 |
Ловите конкретный класс там, где знаете, как восстановиться, а Throwable — один раз на верхнем уровне, не в каждой функции: иначе баги скрываются. Проверку лучше делать до опасной операции: if ($count === 0) throw new InvalidArgumentException(...) понятнее, чем ловить DivisionByZeroError после.
Что не ловится try/catch
Три вида фатальных ошибок не являются объектами: нехватка памяти, превышение времени выполнения и Parse error в самом файле. try/catch их не перехватит. Оператор @ в PHP 8 тоже не подавляет фатальные ошибки. Последнее, что можно сделать, — функция завершения:
<?php
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/tmp/app-error.log');
register_shutdown_function(function (): void {
$err = error_get_last();
$fatal = E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR;
if ($err !== null && ($err['type'] & $fatal)) {
echo "Сервис временно недоступен\n";
}
});
$rows = [];
while (true) {
$rows[] = str_repeat('x', 1024 * 1024);
}
С memory_limit=32M пользователь увидит строку «Сервис временно недоступен», а в журнале появится запись:
[24-Sep-2026 11:10:05 UTC] PHP Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 1052672 bytes) in /app/c4.php on line 16
Функция завершения не продолжает скрипт: она лишь позволяет вернуть аккуратный ответ.
display_errors и log_errors: разработка и продакшен
Две директивы решают разные задачи: display_errors — показывать ли ошибку в ответе, log_errors — писать ли ее в журнал. error_reporting задает, о каких уровнях сообщать вообще.
| Директива | Разработка (php.ini-development) |
Продакшен (php.ini-production) |
|---|---|---|
error_reporting |
E_ALL |
E_ALL & ~E_DEPRECATED |
display_errors |
On |
Off |
display_startup_errors |
On |
Off |
log_errors |
On |
On |
error_log |
не задан | не задан: указать путь или syslog |
Значения — из шаблонов PHP 8.4. Без error_log запись уходит в обработчик SAPI: в CLI — stderr, у PHP-FPM и Apache — их журналы. Многие команды и на продакшене оставляют E_ALL, чтобы видеть Deprecated до обновления версии.
На проде display_errors выключают: сообщение раскрывает пути, имена функций и фрагменты данных. Проверка на встроенном сервере PHP 8.4, скрипт падает на вызове несуществующей функции:
display_errors=1—200 OKс текстомFatal errorи путем к файлу в HTML.display_errors=0, до ошибки ничего не выведено —500 Internal Server Errorс пустым телом.display_errors=0, но часть страницы уже выведена —200 OKс оборванной страницей: заголовки отправлены.
Отсюда практический вывод: код ответа при ошибке надо ставить явно, а не рассчитывать на поведение движка.
Шаблон обработки ошибок для веб-приложения
Файл src/bootstrap.php подключается первым строкой в public/index.php. Журнал лежит в var/log/, вне document root, так что при запуске php -S 0.0.0.0:8000 -t public его нельзя скачать по URL.
<?php
declare(strict_types=1);
$isDev = getenv('APP_ENV') === 'dev';
error_reporting(E_ALL); // сообщать обо всем
ini_set('display_errors', $isDev ? '1' : '0'); // показывать - только в разработке
ini_set('log_errors', '1'); // писать в лог - всегда
ini_set('error_log', dirname(__DIR__) . '/var/log/php-error.log');
// Warning/Notice/Deprecated -> ErrorException, чтобы их не пропускать
set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
if (!(error_reporting() & $severity)) {
return false;
}
throw new ErrorException($message, 0, $severity, $file, $line);
});
// Последний рубеж для непойманных Exception и Error
set_exception_handler(function (Throwable $e) use ($isDev): void {
error_log(sprintf('%s: %s in %s:%d', $e::class, $e->getMessage(), $e->getFile(), $e->getLine()));
if (!headers_sent()) {
http_response_code(500);
}
echo $isDev ? htmlspecialchars((string) $e) : 'Внутренняя ошибка сервера. Попробуйте позже.';
});
<?php
require dirname(__DIR__) . '/src/bootstrap.php';
$prices = ['A-1' => 1200];
$sku = $_GET['sku'] ?? 'B-2';
echo "Цена: " . $prices[$sku] . "\n";
Проверено запросами: ?sku=A-1 возвращает Цена: 1200, ?sku=B-2 — код 500 и «Внутренняя ошибка сервера», а в var/log/php-error.log появляется ErrorException: Undefined array key "B-2" in /app/public/index.php:6. Режим по умолчанию — продакшен; стек в ответе виден только при явном APP_ENV=dev. Текст исключения в dev-режиме экранируется htmlspecialchars: в сообщение попадает ключ из запроса, и без экранирования ?sku=<script>... исполнился бы в браузере.
Границы шаблона: фатальные уровни он не перехватывает (для них — register_shutdown_function) и не заменяет php.ini: Parse error в самом bootstrap случится раньше ini_set. Журнал нужно ротировать, пароли и персональные данные в него не писать.
Если не получилось: диагностика по симптому
- Белый экран или 500 без текста. Фатальная ошибка при
display_errors=Off, причина — в журнале PHP-FPM или веб-сервера. Если журнал пуст, проверьте, что каталог изerror_logсуществует и доступен на запись пользователю PHP. Показ ошибок включайте только на локальной копии. - После обновления PHP посыпались Deprecated. Это не поломка, а список того, что перестанет работать в будущих версиях; исправляйте по одному, начиная с самых частых.
Выводы
- Fatal error останавливает скрипт; Warning, Notice и Deprecated — нет, но приводят к работе с неверными данными.
- В PHP 8 большинство «Fatal error: Uncaught …» — непойманные исключения
ErrorилиException; они ловятсяtry/catch, аThrowableпокрывает обе ветки. - Нехватку памяти, превышение времени и Parse error
try/catchне ловит: их видно только в журнале и черезregister_shutdown_function. - На продакшене
display_errors=Offиlog_errors=On, код 500 ставить явно; в разработке показывать все, включаяE_DEPRECATED.
Где применяется / связь с практикой
Освойте тему на практике
Найти причину по сообщению и стеку, исправить код и настроить окружение так, чтобы следующая ошибка попала в журнал, а не в браузер, — повседневная работа бэкенд-разработчика. Системно PHP 8, обработку исключений, отладку и логирование в веб-приложениях разбирают на курсе «PHP-разработчик. Продвинутый уровень». Отдельные темы можно посмотреть на открытых уроках Otus.
FAQ
Чем Fatal error отличается от Parse error?
Parse error возникает при разборе файла, до выполнения, поэтому не выполняется ни одна его строка. Fatal error возникает во время выполнения, и код до проблемной строки успевает отработать.
Можно ли просто выключить вывод ошибок через error_reporting(0)?
Можно, но тогда ошибки не попадут и в журнал, а скрипт продолжит работать с неверными данными. На продакшене выключают показ (display_errors), а не сообщение об ошибках.
Можно ли поймать Parse error из подключаемого файла?
Да: синтаксическая ошибка в файле из include/require или в eval() бросается как исключение ParseError и ловится catch (ParseError $e). Не ловится только Parse error в файле, который запущен сам.



