Laravel『Model Not Found(ModelNotFoundException)』の原因と対処法
- 作成日 2025.12.22
- その他
Laravelの「Model Not Found」は、多くの場合 ModelNotFoundException(Illuminate\Database\Eloquent\ModelNotFoundException)として発生し、findOrFail / firstOrFail / route model binding など「見つからなかったら例外にする」系の処理で、対象レコードが存在しないときに投げられる。IDの不一致、ルートパラメータの取り違え、SoftDeletesで論理削除されている、グローバルスコープで検索対象から外れている、テナント条件(会社IDなど)で絞られている、接続先DBが違う、などが典型原因。例外そのものは仕様どおりでも、「404にしたいのか」「見つからない原因を潰したいのか」で対処が変わる。
エラーの出方と発生条件
典型メッセージ。
Illuminate\Database\Eloquent\ModelNotFoundException
No query results for model [App\Models\User] 123発生条件の典型:
・findOrFail($id) / firstOrFail() を使っていて、対象が無い
・Route Model Binding(/users/{user})で、該当モデルが見つからない
・SoftDeletesで削除済みのレコードを通常検索している
・where条件(例:company_id)を付けた結果、ヒットしない
・環境差(DB接続先/シードデータ不足)でローカルだけ/本番だけ発生
原因1:IDや検索条件が間違っている(パラメータ取り違え)
URLやフォームから来た値が「本当にそのモデルの主キーか」を確認する。
よくある事故:
・/posts/{post} のつもりが user_id を渡している
・ルート定義の順番やパラメータ名がズレて別の値が入っている
// 例: posts.show なのに user_id を渡してしまう
return redirect()->route('posts.show', ['post' => $request->input('user_id')]);
対処:
・パラメータ名を明確にする(post_id, user_id)
・ルート生成時に渡すキーを確認する
・ログで受け取っているIDを確認する
logger()->info('post id', ['id' => $id]);
$post = Post::findOrFail($id);
原因2:Route Model Bindingで見つからない(ルートとモデルの紐づけ)
Route Model Binding を使うと、Laravelが自動でモデルを探して見つからなければ例外(結果的に404)になる。
// routes/web.php
Route::get('/users/{user}', [UserController::class, 'show']);
// Controller
public function show(\App\Models\User $user)
{
return view('users.show', compact('user'));
}発生条件:
・/users/9999 にアクセスしたが存在しない
・主キーではなく slug で探したいのにデフォルトのまま
対処:slug運用なら binding のキーを変える。
// app/Models/Post.php
public function getRouteKeyName()
{
return 'slug';
}これで /posts/{post} は slug で解決され、存在しないslugなら同様に見つからない扱いになる。
原因3:SoftDeletesで削除されている(論理削除が見えない)
SoftDeletesを使っているモデルは、通常クエリでは deleted_at がある行は検索対象外。
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes;
}発生条件:
・実データはあるが deleted_at が入っているため findOrFail で落ちる
対処:用途に応じて withTrashed / onlyTrashed を使う。
// 削除済みも含めて探す
$post = Post::withTrashed()->findOrFail($id);
// 削除済みだけから探す
$post = Post::onlyTrashed()->findOrFail($id);削除済みを見せたくないなら、例外=404は仕様として扱い、管理画面だけ withTrashed にする設計が多い。
原因4:グローバルスコープやテナント制約で弾かれている
global scope(例:is_active=1、company_id固定)を使っていると、存在してもスコープで除外されて見つからない。
発生条件:
・管理者は見えるべきなのに、スコープにより findOrFail が落ちる
対処:スコープを外して取得する(必要箇所だけ)。
// 例: 特定のグローバルスコープを外す(スコープ名は実装に依存)
$post = Post::withoutGlobalScope('active')->findOrFail($id);
// 全グローバルスコープを外す
$post = Post::withoutGlobalScopes()->findOrFail($id);
テナント制約(company_id)を手動で付けている場合も同様で、「そのIDがその会社に属しているか」がズレると必ず落ちる。
$post = Post::where('company_id', auth()->user()->company_id)->findOrFail($id);
この場合は「IDは存在するが権限的に見せない」=404として正しい設計になっていることも多い。
原因5:接続先DB・環境差(ローカル/本番でデータが無い)
ローカルはシード済みで通るが、本番・ステージングでは未投入で落ちるパターン。
確認ポイント:
・DB_DATABASE/DB_HOST/DB_USERNAME が想定どおりか
・migrateは当たっているか、対象テーブルにレコードがあるか
・キャッシュ(config:cache)で古い .env が使われていないか
php artisan config:clear
php artisan cache:clear
php artisan migrate:status
本番での確認は運用ポリシーに合わせる(不用意にクリアコマンドを常用しない)。
対処パターン別:404で返す/任意メッセージで返す/例外を握る
ModelNotFoundExceptionは「見つからない」なので、HTTP的には404が自然。
・通常のWeb画面:404でOK(Laravel標準)
・API:JSONで404を返したい
・業務要件:ユーザーに分かりやすい画面に戻したい
APIでJSONにしたい例(例外ハンドラ)。
// app/Exceptions/Handler.php
use Illuminate\Database\Eloquent\ModelNotFoundException;
public function render($request, Throwable $e)
{
if ($e instanceof ModelNotFoundException && $request->expectsJson()) {
return response()->json([
'message' => 'Resource not found',
], 404);
}
return parent::render($request, $e);
}「例外を投げずに分岐したい」なら find / first を使う。
$post = Post::find($id);
if (!$post) {
return redirect()->route('posts.index')->with('error', '対象が見つかりませんでした');
}サンプル:安全な取得(テナント + SoftDeletes + 404)
管理画面では削除済みも対象、一般画面では削除済み除外、など現実的な構成。
// 例: 管理者は削除済みも含めて取得、一般は除外
$query = Post::query()
->where('company_id', auth()->user()->company_id);
if (auth()->user()->is_admin) {
$query->withTrashed();
}
$post = $query->findOrFail($id);チェックリスト(上から順に確認する)
1) 例外メッセージの model名 と ID(No query results for model …)が想定どおりか
2) findOrFail/firstOrFail/Route Model Binding のどれで落ちているか
3) 受け取っているID(ルート/フォーム)が本当にそのモデルの主キー(またはslug等)か
4) SoftDeletesで deleted_at が入っていないか(管理画面だけ withTrashed が必要ではないか)
5) グローバルスコープや company_id 等の条件で除外されていないか(withoutGlobalScopeが必要か)
6) 接続先DBが正しいか、環境差でデータ不足になっていないか(.env/設定キャッシュ)
7) 仕様として404でよいか、UI/APIとして別の返し方が必要か(HandlerでJSON化など)
-
前の記事
Laravel『Syntax Error or Access Violation』の原因と対処法 2025.12.20
-
次の記事
Laravel『UnauthorizedHttpException』の原因と対処法 2025.12.23
コメントを書く