エラーハンドリング
エラー操作の重要性
たまに頭の悪い上流の人間が、
『わざわざ処理が止まるコードは書かないでね』
声を大にして言いたい。 意図しない動きをシステムがしたときは即座に停止させろっ!! です。 例えばある処理をしている際に、本来は必ず取得できるレコードが取得できなかった場合どうしますか?
この状態で後続の処理が実行され、運良く(悪く?)システムが落ちずに正常終了したとします。
これ本当に正常終了ですかね? あなたの知らない所、例えば「請求書の金額」とか「発注書の数量」に影響しているかも知れません。
システムは正常終了しているので誰も気づきませんが、請求書はゼロ円なので発行はされない。
発注書は逆に不要な品物まで納品している可能性もあります。 そして数ヵ月後、「最近入金が少ないなぁ」「倉庫に余計な物多くない?」と、徐々に明るみになり、
ようやくシステム障害が発生している事に気がつくのです。
しかも処理自体は正常終了しているため、何処に原因があるかが即座には判断がつきません。 こうなると組織としては疑心暗鬼ですよね。
果たして障害の影響は「請求書」「発注書」だけなのか?と、当然なります。
つまり原因が解明できるまで、最悪システムの停止となり数億単位の損害になる可能性もあります。 対して例外を[throw]していた場合はどうでしょう。
処理が止まりログに原因を記述している事で『秒』で障害発生から原因の特定までできます。
これであれば被害は最小限に抑えられますよね。 システムはどんなに堅牢でセキュアなフレームワークを使っても、設計思想においては当然人間が担います。
それなのに『テスト面倒くさい』『本番で処理止まったら信用がぁ』などの後ろ向き思考や、
顧客からの要望を右から左に伝えるだけの小学生でもできる伝言ゲーマー。
こんな人間がIT部門の上流にいる組織は不憫でなりません。 何処からかデスマーチという名の足音が聞こえてきませんか?
独自のエラークラスを作成する
作成する場所は「~\app\Exceptions\MyException.php」とします。
<?php
namespace App\Exceptions;
use Exception;
class MyException extends Exception {
// コンストラクタ
public function __construct($error = []) {
$this->code = $error['code'] ?? 0;
parent::__construct($error['message'] ?? '内部エラーが発生しました。');
}
}
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Configuration\Exceptions;
class ExceptionHandler {
public static function handle(Exceptions $exceptions) {
$exceptions->renderable(function (\Throwable $e, $request) {
// エラー種別判定
var_dump("OK");
});
}
}
use App\Exceptions\ExceptionHandler;
~ 省略 ~
->withExceptions(function (Exceptions $exceptions): void {
ExceptionHandler::handle($exceptions);
})->create();
まずは任意のコントローラに以下コードを記述してエラーにしてみましょう。
$hoge = 10/0;
どうでしたか?『string(2) "OK" 』と表示されましたか? もしLaravelのエラー画面が表示された場合は、PHPのバージョンが[7]以下となります。
期待通りの場合は[Ver.8]以降となります。 次に以下のコードでエラーにしてみましょう。
~ 省略 ~
throw new MyException(['code'=>'aaa', 'message'=>'bbb']);
ExceptionHandlerの詳細設定
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Configuration\Exceptions;
use App\Exceptions\MyException;
use Illuminate\Validation\ValidationException;
class ExceptionHandler {
public static function handle(Exceptions $exceptions) {
$exceptions->renderable(function (\Throwable $e, $request) {
$errors = [
'code' => $e->getCode(),
'path' => $e->getFile(),
'line' => $e->getLine(),
];
// バリデーションエラーは除外
if ($e instanceof ValidationException) {
return;
}
// エラー種別判定
if ($e instanceof MyException) {
$errors['title'] = 'MyException';
$errors['message'] = $e->getMessage();
} else {
$errors['title'] = $e->getMessage();
$errors['message'] = '内部エラーが発生しました。';
}
return response()->view("errors.500", ['errors'=> $errors]);
});
}
}
<!DOCTYPE HTML>
<HEAD>
<TITLE>SeverError</TITLE>
<link rel="stylesheet" href="{{ asset('css/base_css.css') }}" />
</HEAD>
<BODY>
{{ $errors['title'] }}
<BR />
{{ $errors['message'] }}
<BR />
{{ $errors['code'] }}
<BR />
{{ $errors['path'] }}
<BR />
{{ $errors['line'] }}
</BODY>
</HTML>
$e->getFile()
$e->getLine()
$e->getMessage()
[500.blade.php]は、実はLaravelが500エラーを感知した際に自動的にレンダリングされるファイルでもあります。
今回はエラーハンドリングをしているため意識手にレンダリングしていますが、
例えば「~\errors\404.blade.php」なんてのもあります。
試しに作成して存在しないURLを叩いてみて下さい。
※ただしエラーハンドリングをしている場合は、今回の設定通り500エラーとなります。





エラークラスの基底となるクラスは『Throwable』となります。
この基底クラスから『Exception』『Error』クラスの2種類が枝分かれしています。
[PHP Ver.8]は両方のクラスを例外として[throw]しますが、[PHP Ver.7]は「Exception」のみを[throw]します。
先ほどのコード【$hoge=1/0;】のエラーは「Error」クラス起因ですので補足できないのです。
また「Throwable」であれば[PHP Ver.7]でも補足はできるのですが、このバージョンでのゼロ除算の扱いは「Warning」扱いのため【例外】ですらありません。