11 вопросов
В PHP 7+ иерархия ошибок:
Throwable (interface)
├── Error (внутренние ошибки PHP)
│ ├── TypeError
│ ├── ValueError
│ ├── ArithmeticError
│ ├── DivisionByZeroError
│ └── ...
└── Exception (пользовательские исключения)
├── RuntimeException
├── LogicException
├── InvalidArgumentException
└── ...Ошибки (Error) - критические проблемы: вызов несуществующего метода, неправильный тип аргумента. Исключения (Exception) - предсказуемые ситуации: файл не найден, невалидные данные.
Оба ловятся через try/catch (Throwable). Но Error обычно сигнализирует о баге в коде, Exception - о предсказуемой проблеме.
E_ERROR (1) - фатальная, скрипт останавливаетсяE_WARNING (2) - предупреждение, скрипт продолжаетсяE_NOTICE (8) - замечание (неопределенная переменная)E_STRICT (2048) - рекомендации (удален в PHP 8)E_DEPRECATED (8192) - устаревший функционалE_PARSE (4) - синтаксическая ошибкаE_RECOVERABLE_ERROR (4096) - перехватываемая фатальная ошибкаРекомендация: error_reporting(E_ALL) на разработке. На проде - логирование всех ошибок, display_errors = Off.
До PHP 7 фатальные ошибки нельзя было перехватить. С PHP 7 введен интерфейс Throwable:
try {
$result = call_to_undefined_function();
} catch (Error $e) {
echo "Ошибка PHP: " . $e->getMessage();
} catch (Exception $e) {
echo "Исключение: " . $e->getMessage();
} catch (Throwable $e) {
echo "Любая ошибка: " . $e->getMessage();
}Нельзя создать класс, реализующий Throwable напрямую - нужно наследовать Error или Exception.
try {
$conn = new PDO($dsn);
$result = $conn->query($sql);
} catch (PDOException $e) {
// Обработка ошибки БД
log($e->getMessage());
throw new DatabaseException('Query failed', 0, $e); // re-throw
} catch (Exception $e) {
// Любое другое исключение
log($e->getMessage());
} finally {
// Выполняется ВСЕГДА (даже после return или throw)
$conn = null; // закрываем соединение
}Блоки catch проверяются по порядку - ставьте более конкретные исключения первыми. finally используется для очистки ресурсов.
Если исключение не поймано в try/catch, оно всплывает по стеку вызовов до глобального обработчика. Если обработчика нет - PHP выдает Fatal Error: "Uncaught Exception" и скрипт завершается.
// Установка глобального обработчика
set_exception_handler(function (Throwable $e) {
error_log($e->getMessage());
http_response_code(500);
echo 'Internal Server Error';
});Фреймворки (Laravel, Symfony) имеют свои exception handler-ы, которые логируют ошибку и возвращают подходящий HTTP-ответ.
// Базовое исключение приложения
class AppException extends RuntimeException {}
// Специализированные исключения
class NotFoundException extends AppException {
public static function forEntity(string $entity, mixed $id): self {
return new self("$entity with ID $id not found");
}
}
class ValidationException extends AppException {
public function __construct(
private array $errors,
string $message = 'Validation failed',
) {
parent::__construct($message);
}
public function getErrors(): array { return $this->errors; }
}
// Использование
throw NotFoundException::forEntity('User', 42);Создавайте иерархию исключений для разных типов ошибок - это позволяет ловить их группами.
Да, наследуя Exception или RuntimeException/LogicException:
class InsufficientFundsException extends RuntimeException {
public function __construct(
public readonly float $balance,
public readonly float $amount,
) {
parent::__construct(
sprintf('Insufficient funds: balance %.2f, requested %.2f', $balance, $amount)
);
}
}
try {
throw new InsufficientFundsException(100.0, 150.0);
} catch (InsufficientFundsException $e) {
echo "Balance: {$e->balance}, needed: {$e->amount}";
}Рекомендации: наследуйте RuntimeException для ошибок в runtime, LogicException для ошибок в логике кода.
// Пользовательский обработчик ошибок (warnings, notices)
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
// Глобальный обработчик исключений (если не поймали в try/catch)
set_exception_handler(function (Throwable $e) {
error_log(sprintf("[%s] %s in %s:%d", get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()));
if (php_sapi_name() !== 'cli') {
http_response_code(500);
echo 'Internal Server Error';
}
});
// Для фатальных ошибок (E_ERROR, E_PARSE) - register_shutdown_function
register_shutdown_function(function () {
$error = error_get_last();
if ($error && ($error['type'] & (E_ERROR | E_PARSE | E_COMPILE_ERROR))) {
error_log("Fatal: {$error['message']}");
}
});SPL предоставляет иерархию стандартных исключений:
LogicException (ошибки в логике, могут быть найдены при code review)
├── BadFunctionCallException
├── BadMethodCallException
├── DomainException // нарушение бизнес-правила
├── InvalidArgumentException // неверный аргумент
├── LengthException
├── OutOfRangeException // индекс вне диапазона (compile-time)
└── OverflowException
RuntimeException (ошибки, обнаруживаемые только в runtime)
├── OutOfBoundsException // индекс вне диапазона (runtime)
├── RangeException
├── UnderflowException
└── UnexpectedValueExceptionИспользуйте эти классы (или наследуйте от них) вместо голого Exception - это позволяет ловить ошибки по категориям.
Исключения предпочтительнее в PHP:
Коды ошибок (return values) допустимы когда:
// Плохо: null как код ошибки
function find(int $id): ?User { return null; }
// Лучше: Result type или исключение
function findOrFail(int $id): User {
return $this->repo->find($id) ?? throw new NotFoundException();
}finally выполняется всегда, независимо от того, было ли исключение, был ли return или throw в try/catch:
function readFile(string $path): string {
$file = fopen($path, 'r');
try {
$content = fread($file, filesize($path));
return $content;
} catch (Exception $e) {
return '';
} finally {
fclose($file); // выполнится даже после return!
}
}Используется для: закрытия файлов/соединений, освобождения блокировок, логирования. Если finally содержит return, он перезаписывает return из try/catch (избегайте этого).