【Laravel】リクエストがルートに届かない(prefixで空文字を指定して全リクエストがおかしくなった話)
前日までできていた動作が突然できなくなっていた(半日溶けた)ので、その原因と解決策をなるべく汎用的にメモする記事です。
まず結論
仮だと思っても余計なルーティングを書いてはいけない
具体的に
「''
」宛のルートを複数記述していたため、いくつかのリクエストが全て認証処理が行われて「/login
」にリダイレクトしていました。
記事を書く理由
最近PHPを触るようになり、慣れないためにデバッグが難しいなとつくづく感じています。数をこなすうちにデバッグの勘を掴めてくるのだろうな〜と思いますが、一度経験したことや知り得たことは忘れたくない。
今回、どこで処理が行き詰まっているのか検証しました。エラーが起きていたわけではなくログ出力では分からない状況。
最終的に解決できたので、手順のメモと同じ過ちを繰り返さないための備忘録+原因の考察を残しておきます。
環境
- Laravel 10.28.0 on Docker
- Laravel/Breeze(認証機能)
起きていたこと
前日までできていたユーザー登録ができない
前提のコード
以下の通り、registerという名前のルートにルーティングしたい。
<form method="POST" action="{{ route('register') }}">
ルーティングはこちら。routes/web.php
ではなくroutes/auth.php
に記載しています。
Route::middleware('guest')->group(function () {
Route::get('register', [RegisteredUserController::class, 'create'])
->name('register');
Route::post('register', [RegisteredUserController::class, 'store']);
// 略
Breezeにより生成されたコードからほとんど変えていない状態です。
正しい動作
フォーム入力後リクエストを送信すると、usersテーブルにデータ登録+認証済みユーザー向けトップページが表示される
起きていた状況
リクエスト送信後、userテーブルにデータが登録されない+ログインページ(「/login
」にリダイレクト(未認証ユーザー向けのリダイレクト先)
ログ出力はなし(処理が行われないまま、おかしいタイミングでリダイレクトしている?)
まず原因を簡単に
「''
」宛のルートがweb.phpに複数あったため。
この記述をした理由:作業がひと段落ついたところで他機能のルーティングだけ先に仮に書いておこうとルートを増やしたため。
↓こんなコードが複数あった。(ミドルウェアでの認証チェックのauth,guestのどちらの中にも)
Route::prefix('')->name('xxx.')->controller(CommentController::class)->group(function() {
Route::post('{xxx}', 'store')->name('store');
Route::get('{xxx}/edit', 'edit')->whereNumber('xxx')->name('edit');
Route::put('{xxx}', 'update')->whereNumber('xxx')->name('update');
Route::delete('{xxx}', 'destroy')->whereNumber('xxx')->name('destroy');
});
検証したこと
この項目で話すのは、結果的に全て原因とは無関係の検証です。
実際の原因についてはこちらから。
まず検証ツール
正しいURIにPOSTリクエストは送られていて、ペイロードに入力データは乗っている
→リクエスト送信はうまくいっているっぽい。ビューに問題はなさそう?
しかしDBに登録はされていない。
続いて、後ろから行きます。
コントローラー
(※これは結果的に正しいコードです。)
public function store(Request $request) :RedirectResponse
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'is_admin' => $is_admin_default_value,
]);
event(new Registered($user));
Auth::login($user);
return redirect(RouteServiceProvider::HOME);
}
dd();
で処理結果を出力していきます。
-
returnでリダイレクトしない
return dd($user);
変わらず、「
/login
」にリダイレクト。(この時点でかなりおかしい状況だとうっすら気づく) -
event()
が実行されているか確認$result = event(new Registered($user)); dd($result);
動きなし(変わらずリダイレクト)
-
$user
の確認$user = User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), 'is_admin' => $is_admin_default_value, ]); dd($user);
動きなし(変わらずリダイレクト)
-
バリデーションの確認
バリデーションを丸ごと外してみる→変わらず。
そもそも送信元ページにリダイレクトするはず -
$request
の確認
そもそもリクエストがコントローラーに渡っていないのでは?という確認public function store(Request $request) :RedirectResponse { dd($request); $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class], 'password' => ['required', 'confirmed', Rules\Password::defaults()], ]);
結果:
dd($request)
が実行されない
[分かったこと]処理がコントローラーに届いていない
ルーティング
Route::middleware('guest')->group(function () {
Route::get('register', [RegisteredUserController::class, 'create'])
->name('register');
Route::post('register', [RegisteredUserController::class, 'store'])->name('register_post');
// 略
});
- ミドルウェアが悪さしているのでは?と思って外してみるも変わらず
- postに名前をつけていないな…と思いregisterにしてみたりregister_postにしてみたり(bladeのデフォルトで名前付きではない)
[分かったこと]正しいルートにリクエストが送られていない気がする
ビュー
一応見ましたが、リクエストが送信されていることを検証ツールで確認していたのでここではないよなあ…となる
状況を整理
- リクエストがコントローラーに渡っていない+正しいURIに入力データが送信・POSTされている
- →ルーティングがうまくいっていない
原因を見つけたきっかけ
状況を一言でいうと、「謎のリダイレクトが起きている」でした。
会員登録に限らないのかも?ということで、まずトップページ(「/
」)にGETリクエストを送ると正しい「/
」ではなく「/login
」を返す=同じ状況が起きているように見える
では他のページではどうだろう?とその他の適当なURI(「/
」以外のパラメータ)にGETリクエストを送ると正しく表示されました。
「/
」へのルートがおかしいな〜とこの周辺を検証開始。会員登録の処理とはやや離れた箇所(「/
」を通らないため)ですが、とりあえず探ってみる。
…と、以下のように「''
」宛のルートが過剰に存在していました。(上記(リンク貼る:#まず原因)の原因コード)
Route::prefix('')->name('xxx.')->controller(CommentController::class)->group(function() {
Route::post('{xxx}', 'store')->name('store');
Route::get('{xxx}/edit', 'edit')->whereNumber('xxx')->name('edit');
Route::put('{xxx}', 'update')->whereNumber('xxx')->name('update');
Route::delete('{xxx}', 'destroy')->whereNumber('xxx')->name('destroy');
});
prefix('')
をprefix('xxx')
に変えると正しく動作しました!
原因の考察
routes/web.php
に以下のように記述していました。
新規登録処理はroutes/auth.php
に記述しており一番最後に読み込まれています。
<?php
// 前略
Route::middleware('guest')->group(function() {
Route::get('/', function () {
return view('index');})->name('index');
});
Route::middleware('auth')->group(function () {
// これが問題😱
Route::prefix('')->name('xxx.')->controller(CommentController::class)->group(function() {
Route::post('{xxx}', 'store')->name('store');
Route::get('{xxx}/edit', 'edit')->whereNumber('xxx')->name('edit');
Route::put('{xxx}', 'update')->whereNumber('xxx')->name('update');
Route::delete('{xxx}', 'destroy')->whereNumber('xxx')->name('destroy');
// 中略
});
});
require __DIR__.'/auth.php';
憶測レベルですが、'/register'
宛のPOSTリクエストがRoute::prefix('')
の対象として処理されて(「''
」ということは全リクエストがここを通ることになる)、middleware('auth')
を通り未認証ユーザーとして/login
にリダイレクトされたのだと思います。そのため新規登録処理正しいルートに届かず(当然コントローラーにも届かず)ミドルウェアによる認証処理が行われていました。
こう考えると、middleware('auth')
を外して他ページを確認をすると正常だったことにも納得できます。
まとめ
後で楽できるようにとルーティングの雛形だけ書いていたのが過ちでした。
プログラマーにとって怠惰はいいことですが、怠惰にするのも方向性を間違えてはいけない。とはいえそういった曖昧なことを意識するのは難しいので、「動作確認できないコードは一切書かない」が教訓かなと思います。作業途中で書くならコメントアウトで!
Discussion