Ошибки PHP: полная классификация и подходы к обработке
Основные типы ошибок PHP
В PHP предусмотрено несколько уровней ошибок, обозначаемых константами. Фатальные ошибки (E_ERROR) прерывают выполнение скрипта. Предупреждения (E_WARNING) не останавливают работу, но сигнализируют о проблемах. Синтаксические ошибки (E_PARSE) возникают на этапе компиляции. Замечания (E_NOTICE) указывают на возможные недочеты. E_STRICT и E_DEPRECATED содержат рекомендации и информацию об устаревших функциях. Пользовательские ошибки (E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE) генерируются разработчиком.
Единая обработка через преобразование в исключения
Как добиться того, чтобы все ошибки PHP обрабатывались единообразно через механизм исключений?
Наиболее эффективный способ - установить пользовательский обработчик ошибок, который превращает каждую ошибку (кроме фатальных уровня E_ERROR и выше) в объект ErrorException. Затем фатальные ошибки (включая E_ERROR) перехватываются через блок try-catch с типом Throwable или Error.
set_error_handler(function ($level, $message, $file, $line) {
if (error_reporting() & $level) {
throw new ErrorException($message, 0, $level, $file, $line);
}
});
try {
// Пример: вызов несуществующей функции
undefinedFunction();
} catch (ErrorException $e) {
echo 'Исключение: ' . $e->getMessage();
} catch (Throwable $e) {
echo 'Неожиданная ошибка: ' . $e->getMessage();
}
типы ошибок php (типы ошибок в php)
Исключение: Call to undefined function undefinedFunction()
Такой подход позволяет централизованно обрабатывать ошибки любого уровня, используя знакомый механизм try-catch.
Типичные проблемы
- Ошибки компиляции (E_PARSE) не могут быть перехвачены таким образом, так как они происходят до выполнения кода. Для их обработки применяется проверка синтаксиса перед запуском (например, через php -l).
- Некоторые фатальные ошибки (например, нехватка памяти) могут обойти обработчик. В таких случаях используют register_shutdown_function в сочетании с error_get_last.
- Если обработчик не проверяет error_reporting(), то он будет выбрасывать исключения даже для ошибок, отключенных в конфигурации.
Вариант: Настройка уровней ошибок и их отображения
Как контролировать, какие ошибки видны на экране, а какие сохраняются в лог?
Самый простой способ - манипулировать директивами error_reporting, display_errors и log_errors через ini_set или в php.ini.
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED); // Игнорируем уведомления и устаревшие предупреждения
ini_set('display_errors', '1'); // Показывать на экране (только для разработки)
ini_set('log_errors', '1');
ini_set('error_log', '/tmp/php_errors.log');
В продакшене display_errors обычно выключают.
Проблема: Фатальные ошибки (E_ERROR) всё равно завершают скрипт, и сообщение может быть выведено до того, как сработают настройки буферизации. Для полного контроля нужны дополнительные средства.
Вариант: Перехват фатальных ошибок с помощью Error (PHP 7+)
Как предотвратить остановку скрипта при фатальной ошибке, например, вызове несуществующего метода?
Начиная с PHP 7, фатальные ошибки (кроме некоторых) можно перехватывать через catch (Error $e).
try {
$obj = new stdClass();
$obj->method(); // Call to undefined method stdClass::method()
} catch (Error $e) {
echo 'Фатальная ошибка перехвачена: ' . $e->getMessage();
}
Фатальная ошибка перехвачена: Call to undefined method stdClass::method()
Не все фатальные ошибки наследуются от Error. Например, ошибки парсинга (E_PARSE) происходят до выполнения try-catch. Также некоторые внутренние ошибки (например, превышение времени выполнения) могут не перехватываться.
Вариант: Собственный обработчик без исключений
Как обработать ошибку и продолжить выполнение скрипта?
Функция set_error_handler может вернуть true, чтобы указать, что ошибка обработана, и PHP не должен запускать стандартный обработчик. Это позволяет логировать ошибку, но не прерывать выполнение.
function myErrorHandler($level, $message, $file, $line) {
if (error_reporting() & $level) {
error_log('Ошибка [' . $level . ']: ' . $message . ' в файле ' . $file . ' на строке ' . $line);
// Не выбрасываем исключение
}
return true; // Ошибка обработана
}
set_error_handler('myErrorHandler');
echo $undefinedVariable; // Notice
echo 'Скрипт продолжает работу';
Скрипт продолжает работу
В лог будет записано уведомление.
Возврат true не отменяет фатальные ошибки - они всё равно приведут к остановке. Кроме того, такой подход не позволяет использовать исключения для управления потоком.
Вариант: Обработка фатальных ошибок через shutdown и error_get_last
Как узнать о фатальной ошибке после её возникновения и выполнить завершающие действия?
Функция register_shutdown_function регистрирует callback, который вызывается после завершения скрипта. Внутри можно проверить error_get_last() на наличие фатальной ошибки.
register_shutdown_function(function() {
$error = error_get_last();
if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
echo 'Фатальная ошибка: ' . $error['message'];
// Дополнительные действия: логирование, очистка
}
});
// Вызов несуществующей функции
nonexistent();
Фатальная ошибка: Call to undefined function nonexistent()
Этот метод не позволяет предотвратить остановку скрипта, так как callback вызывается уже после завершения. Он подходит для логирования и очистки ресурсов.
Расширенные примеры обработки ошибок PHP
Комбинированный обработчик для всех типов ошибок
Сочетание set_error_handler для нефатальных ошибок и register_shutdown_function для фатальных позволяет охватить практически все ситуации.
set_error_handler(function($level, $message, $file, $line) {
throw new ErrorException($message, 0, $level, $file, $line);
});
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
echo 'Неисправимая ошибка: ' . $error['message'] . PHP_EOL;
// Здесь можно отправить уведомление разработчику
}
});
try {
// Фатальная ошибка - вызов undefined
undefined();
} catch (Error $e) {
echo 'Перехвачено через Error: ' . $e->getMessage() . PHP_EOL;
}
Неисправимая ошибка: Call to undefined function undefined()
Подавление ошибок оператором @
Оператор @ подавляет вывод ошибок для конкретного выражения. Но он не отключает обработчик ошибок, и ошибки всё равно могут логироваться.
error_reporting(E_ALL);
ini_set('display_errors', 1);
$result = @file_get_contents('/nonexistent/file.txt');
if ($result === false) {
echo 'Файл не найден, но ошибка подавлена.';
}
// Проверим последнюю ошибку
$lastError = error_get_last();
if ($lastError) {
echo 'Тип: ' . $lastError['type'] . ', сообщение: ' . $lastError['message'];
}
Файл не найден, но ошибка подавлена. Тип: 2, сообщение: file_get_contents(/nonexistent/file.txt): Failed to open stream: No such file or directory
Использование @ не рекомендуется, так как затрудняет отладку и маскирует проблемы.
Проверка синтаксиса PHP перед выполнением
Ошибки парсинга (E_PARSE) невозможно перехватить внутри исполняемого кода. Чтобы избежать их, можно проверять синтаксис файла перед его выполнением.
$filename = 'test.php';
$output = [];
$returnVar = 0;
exec('php -l ' . $filename . ' 2>&1', $output, $returnVar);
if ($returnVar !== 0) {
echo 'Синтаксическая ошибка в ' . $filename . ':' . PHP_EOL;
echo implode(PHP_EOL, $output);
} else {
echo 'Файл ' . $filename . ' синтаксически корректен.';
}
Синтаксическая ошибка в test.php: Parse error: syntax error, unexpected '}' in test.php on line 5
Класс для централизованного управления ошибками
Можно создать класс, который инкапсулирует логику обработки ошибок.
class ErrorHandler {
public static function register() {
set_error_handler([__CLASS__, 'handleError']);
register_shutdown_function([__CLASS__, 'handleShutdown']);
}
public static function handleError($level, $message, $file, $line) {
$logMessage = '[Error] Level ' . $level . ': ' . $message . ' in ' . $file . ':' . $line;
error_log($logMessage);
if (error_reporting() & $level) {
throw new ErrorException($message, 0, $level, $file, $line);
}
return true;
}
public static function handleShutdown() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
$logMessage = '[Fatal] ' . $error['message'] . ' in ' . $error['file'] . ':' . $error['line'];
error_log($logMessage);
// Отправить уведомление
}
}
}
ErrorHandler::register();
try {
strpos(); // Warning: expects at least 2 parameters
} catch (ErrorException $e) {
echo 'Исключение: ' . $e->getMessage();
}
Исключение: strpos() expects at least 2 parameters, 0 given
Пользовательские ошибки с разными уровнями
Функция trigger_error позволяет создавать собственные ошибки с нужным уровнем.
function validateAge($age) {
if ($age < 0) {
trigger_error('Возраст не может быть отрицательным', E_USER_WARNING);
} elseif ($age > 150) {
trigger_error('Неправдоподобный возраст', E_USER_ERROR);
}
return true;
}
set_error_handler(function($level, $message) {
if ($level === E_USER_WARNING) {
echo 'Предупреждение: ' . $message . PHP_EOL;
}
if ($level === E_USER_ERROR) {
throw new Exception('Фатальная ошибка: ' . $message);
}
});
validateAge(-5);
try {
validateAge(200);
} catch (Exception $e) {
echo $e->getMessage();
}
Предупреждение: Возраст не может быть отрицательным Фатальная ошибка: Неправдоподобный возраст
Битовые маски error_reporting
Уровни ошибок комбинируются с помощью побитовых операторов. Полезно для гибкой настройки.
// Включить все ошибки, кроме уведомлений (E_NOTICE) и строгих стандартов (E_STRICT)
$level = E_ALL & ~E_NOTICE & ~E_STRICT;
error_reporting($level);
// Проверить, входит ли данный уровень в текущую маску
if (error_reporting() & E_WARNING) {
echo 'E_WARNING сейчас отображается.';
}
E_WARNING сейчас отображается.
Восстановление после ошибок в цикле
Использование try-catch внутри цикла позволяет продолжить выполнение, даже если одна итерация вызывает ошибку.
$array = ['a', 'b', null, 'd'];
foreach ($array as $key => $value) {
try {
if ($value === null) {
trigger_error('Пустое значение на позиции ' . $key, E_USER_WARNING);
}
echo 'Обработка элемента ' . $key . ': ' . strtoupper($value) . PHP_EOL;
} catch (ErrorException $e) {
echo 'Ошибка: ' . $e->getMessage() . PHP_EOL;
}
}
Обработка элемента 0: A Обработка элемента 1: B Ошибка: Пустое значение на позиции 2 Обработка элемента 3: D