完全なエラー戦略を組み立てましょう。連鎖させた業務例外、制御されたfinally、そして最後の防衛線としてのグローバルハンドラーです。
このレッスンを Kodokon で開くPHP 7以降、投げられるものはすべてThrowableを実装し、二つの枝に分かれます。Error(TypeErrorのような、エンジンや型付けの失敗)と、Exception(アプリケーションの失敗)です。Errorはプログラムの境界でだけ捕まえます。アプリケーションの側では、SPLがすぐに使える階層を提供します。LogicExceptionは修正すべき開発者のバグを、RuntimeExceptionは対処すべき実行時の不測の事態を知らせます。プロの反射はこうです。業務例外をこれらの土台から派生させ、見分けのつく状況ごとに一つ作るのです。
<?php
declare(strict_types=1);
abstract class AppException extends RuntimeException
{
}
final class UserNotFound extends AppException
{
public static function withEmail(
string $email
): self {
return new self("No user for $email");
}
}
try {
throw UserNotFound::withEmail('lea@example.com');
} catch (UserNotFound $e) {
echo $e->getMessage();
}try/catchは、そのエラーをどう扱えばよいかわかっているときにだけ意味があります。さもなければ、そのまま上へ伝わらせましょう。三つの道具が制御を洗練させます。マルチキャッチのcatch (JsonException | ValueError $e)は同じ扱いをまとめます。previousパラメーターは、技術的な例外を業務例外に翻訳するときに、元の原因を連鎖させます。finallyはどんな場合でも実行されます。成功、例外、早期のreturnのときでさえも。リソースを解放するのに理想的です。
<?php
declare(strict_types=1);
final class PayloadInvalid extends RuntimeException
{
}
function decode(string $json): mixed
{
try {
return json_decode(
$json,
true,
flags: JSON_THROW_ON_ERROR,
);
} catch (JsonException $e) {
throw new PayloadInvalid(
'Invalid JSON payload',
previous: $e,
);
} finally {
echo "decode() finished\n";
}
}
try {
decode('{broken');
} catch (PayloadInvalid $e) {
echo $e->getPrevious()?->getMessage();
}連鎖の終わりでは、二つのグローバルハンドラーが仕上げを担います。set_exception_handlerは、処理されなかったすべてを捕まえます。構造化されたログ、汎用の500レスポンス、それもメッセージやトレースをユーザーに一切さらすことなく、です。set_error_handlerは、古い形式のエラー(警告や通知)をErrorExceptionに変換し、二つの世界を統一します。すべてが例外になり、すべてが同じ処理の道筋をたどります。
<?php
declare(strict_types=1);
set_error_handler(function (
int $severity,
string $message,
string $file,
int $line,
): bool {
throw new ErrorException(
$message,
0,
$severity,
$file,
$line,
);
});
set_exception_handler(function (Throwable $e): void {
error_log($e->getMessage());
http_response_code(500);
echo 'A technical error occurred.';
});
trigger_error('Legacy warning', E_USER_WARNING);
echo 'Never reached';ErrorとExceptionに共通するものは何ですか?ErrorはExceptionを継承しているThrowableインターフェースを実装しているfinallyブロックはいつ実行されますか?previousパラメーターは何のためのものですか?