firstOrFailの例外処理でハマった話

Laravel 8においてDBにデータが存在するかどうかをチェックして、存在したらそのデータを、存在しなかったら定数を返却するような処理を書く際に一時間近くハマったので備忘録として残しておく。

環境

  • OS: Ubuntu 20.04 server
  • Laravel 8.26.1
  • PHP 8.0.12

やりたかったこと

  1. 特定の条件に当てはまるデータが存在した場合、firstOrFail()関数を用いてそのデータの特定カラム(foo)の値を返却する。
  2. 条件に当てはまらない場合、スローされた例外をキャッチして定数(-1)を返却する。

これを実現するため、以下のようなコードを作成。

try{
      $user= User::where([
           ['user_id', '=', $userId],
           他の条件…,
      ])
      ->firstOrFail();
      return $user->foo;
}catch(Exception $e){
      return -1;
}

実際にこの処理を行うAPIをブラウザから叩くと、Laravel標準の 「404 | Not Found」ページが表示された。

原因

findOrFailメソッドとfirstOrFailメソッドは、クエリの最初の結果を取得します。ただし、結果が見つからない場合は、Illuminate\Database\Eloquent\ModelNotFoundExceptionを投げます。
(中略)
ModelNotFoundExceptionをキャッチしない場合は、404 HTTPレスポンスをクライアントへ自動的に返送します。

引用: Eloquentの準備 8.x Laravel

どうやらModelNotFoundExceptionがキャッチされない限り、firstOrFailメソッドは例外発生時にApp::abort(404)と同様の動作をするらしい。

解決

きちんとModelNotFoundExceptionをキャッチするようにしたところ、想定していた通りの動作をしてくれた。

use Illuminate\Database\Eloquent\ModelNotFoundException;
...
try{
      $user= User::where([
           ['user_id', '=', $userId],
           他の条件…,
      ])
      ->firstOrFail();
      return $user->foo;
}catch(ModelNotFoundException $e){
      return -1;
}