В PHP есть и исключения, и «старые» ошибки уровня E_WARNING. На собесе проверяют иерархию Throwable, работу finally и стратегию обработки ошибок в приложении.
Иерархия
Throwable
├── Error: TypeError, ValueError, ArithmeticError (DivisionByZeroError), UnhandledMatchError, ...
└── Exception: ErrorException, JsonException, RuntimeException, LogicException (InvalidArgumentException, DomainException), ...
С PHP 7 ошибки движка (вызов метода у null, неверный тип аргумента) стали Error. catch (Exception $e) их не ловит, для всего вместе catch (Throwable $e), но только на верхнем уровне.
try, catch, finally
try {
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException | ValueError $e) { // несколько типов (7.1)
throw new ImportFailed('Битый файл', 0, $e); // previous сохраняет причину
} finally {
fclose($handle);
}
finally выполняется всегда, даже после return. Если в finally есть свой return, он перекрывает результат и даже глотает исключение, так делать не надо. С PHP 8.0 переменную можно не писать: catch (JsonException), а throw стал выражением: $x ?? throw new Exception().
Ошибки, которые не исключения
Многие функции сообщают о проблеме предупреждением и возвращают false: file_get_contents, fopen. try/catch их не поймает. Решение во всех фреймворках:
set_error_handler(function (int $severity, string $message, string $file, int $line) {
throw new ErrorException($message, 0, $severity, $file, $line);
});
Оператор @ только подавляет вывод, ошибку он не исправляет.
Свои исключения
class BillingException extends RuntimeException {}
final class InsufficientFunds extends BillingException
{
public static function forAmount(int $amount): self
{
return new self("Недостаточно средств для списания $amount");
}
}
Иерархия по модулям позволяет ловить «всё из биллинга» или конкретный случай.
Глобальная обработка
set_exception_handler: для непойманных исключений, после него скрипт завершается.register_shutdown_function+error_get_last(): для фатальных ошибок вроде нехватки памяти.- Во фреймворках:
Handlerв Laravel, событиеkernel.exceptionв Symfony. Они логируют, отправляют в Sentry и рендерят ответ.
В продакшене display_errors=Off, log_errors=On: стек и пути не должны попадать к пользователю.
Ожидаемые ошибки
Валидация и бизнес-отказы это не баги. Их возвращают как ошибки валидации (422) или доменные исключения, которые обработчик превращает в понятный ответ. Неожиданные ошибки дают 500 и алерт.
Частые ошибки
catch (Exception)и удивление, чтоTypeErrorпролетел.- Пустой
catch, проглатывающий проблему. - Потеря причины без
$previous. returnвfinally.json_decodeбезJSON_THROW_ON_ERRORи проверки результата.
Что спросят на собесе
- Чем
Errorотличается отException? - Как работает
finallyсreturn? - Как превратить предупреждения в исключения?
- Как вы организуете обработку ошибок в приложении?
- Как поймать фатальную ошибку?