👣

【Laravel】リクエストがルートに届かない(prefixで空文字を指定して全リクエストがおかしくなった話)

2023/11/06に公開

前日までできていた動作が突然できなくなっていた(半日溶けた)ので、その原因と解決策をなるべく汎用的にメモする記事です。

まず結論

仮だと思っても余計なルーティングを書いてはいけない

具体的に

''」宛のルートを複数記述していたため、いくつかのリクエストが全て認証処理が行われて「/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')を外して他ページを確認をすると正常だったことにも納得できます。

まとめ

後で楽できるようにとルーティングの雛形だけ書いていたのが過ちでした。
プログラマーにとって怠惰はいいことですが、怠惰にするのも方向性を間違えてはいけない。とはいえそういった曖昧なことを意識するのは難しいので、「動作確認できないコードは一切書かない」が教訓かなと思います。作業途中で書くならコメントアウトで!

GitHubで編集を提案
Fusic 技術ブログ

Discussion