LaravelのAPI開発をする中で、詰まったエラーについてまとめています。コンフリクト解消直後は、若干処理なども変わってしまうため、それまでのテストが通らないという状況に見舞われました。認証ばかりに気を取られてしまい、エラー原因になかなか辿り付かなかったので、自戒の意味も込めて記事に残しておきます。
1. エラー概要
API開発を行う中で、他の人と同じ箇所を開発していたためコンフリクトが発生。コンフリクト前までは、実行できていたテストがコンフリクト解消後実行できなくなってしまうという事態が起きた。エラー内容としては、以下。
├ Expected response status code [200] but received 302.
├ Failed asserting that 302 is identical to 200.
├
├ The following errors occurred during the last request:
├
├ The org id field is required.
「org_idフィールドは必須です
」と書かれており、org_idがリクエストの値として渡されていないと言われている。
302は、認証のせいでリダイレクトしてるのかな。
2. 原因の分析
<エラーの発生原因>
**org_idがリクエストデータに含まれていないため、バリデーションエラーが発生したことが原因。**このエラーに関連しているのが今回行ったコンフリクトの解消です。コンフリクト前までは、クエリパラメータを使用する形で処理を書いていたのですが、変更後パスパラメータにしたためエラーが発生。
直前まで手元でテストが通ることを確認していましたが、他の人のコード変更を加えたことにより、発生原因特定に時間がかかりました。
また他メンバーが認証処理を加えていたので、認証によるものだろうと無意識に思ってしまい、それを前提に調査を進めたのも痛かった…泣
<原因を特定したプロセス>
特定したプロセスとしては、以下となります。
- テスト側の問題なのか、Controller側の問題なのか問題の切り分け
- テスト実行して、どこまでの処理が通っているのか、どこまで値を受け取れているのかをデバック
- 認証が悪さをしているなら、元のコードに戻した場合と現在とで挙動差異がどこにあるのか確認
- 各クラスの処理も確認
3. 試行錯誤の記録
<試した方法と結果>
原因の追求までにやったことをおさらいしていきます。
まずやったのがテスト側の問題なのか、実装した処理の問題なのかで切り分けました。
Route::prefix('piyopiyo')
->middleware('auth:sanctum')
->name('hogehoge.')
->group(function () {
Route::get('/{org_id}', [OrderInfoController::class, 'index'])->name('index');
});
こんな感じで、APIの設定をしていました。
まず考えたのが、routeを呼び出せていないんじゃないか?と考えたので、テストの処理の中でrouteの呼び出しを確認しました。
dd(route('hogehoge.index', ['org_id' => 1]));
"http://localhost/piyopiyo/hogehoge/1"
ちゃんと呼び出せることを確認。
次に、Controller側に値が入っているのかを確認。
public function index(GethogeInfoRequest $request, GethogeInfoService $hogeInfoService)
{
dd("Contoller呼び出せたよ");
$orgId = (int) $request->validated()['org_id'];
}
どうやら、この文言を呼び出す前にエラーとなるので、引数で使用しているRequestクラスとServiceクラスに問題がありそうです。
class GethogeInfoRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'org_id' => 'required|integer',
];
}
}
class GethogeInfoService
{
static function gethogeInfo(int $org_id)
{
return hogeInfo::where('org_id', $org_id)->get();
}
}
何も問題なさそう。
という事で、ここからかなり苦戦します。
なぜなら「何も問題がないという問題」が起きていることに注視せず、他の問題に気を取られてしまったからです。
現状が最適解であると勝手に決めつけてしまったのですね。とりあえず、ChatGPT先生にこれまでの経緯を報告。
4. 解決策
<具体的な解決方法>
細かく調べたことを報告したら、どうやらリクエストのバリデーションエラーが原因ではないかと提案してくれました。という事で、再度rulesの処理を確認。デバックしながら、処理が走っていることを確認しました。
またリクエストの内容がどうなっているかもここで確認。
dd($this->all());
これにより、リクエストの中身が見れます。(そもそも、デバッカーの設定をしておけばテスト実行の際の値の確認もできたので、やっておけばよかったと反省)
リクエストの中身は空。何なかった。
どういう事か調査していくと2つのことがわかりました。
- ルート定義で
org_id
はURLパスパラメータとして渡しているが({org_id}
)、Laravelのバリデーション対象データに含まれないこと。 - バリデーションでチェックするデータ(
$this→all()
)にURLパスパラメータが自動的に含まれるわけではないこと
どうやら、FormRequet
バリデーションはリクエストボディやクエリパラメーターからデータを取得するがパスパラメータはデフォルトではバリデーション対象($this→all()
)に含まれないらしい。
つまり、バリデーション前にデータ加工を行うことで、今回のエラーは解消されそうです。ということで、以下をrules()
の下にprepareForValidation
を追加。
バリデーションを行う前やサニタイズする前に入れてねって書いてありました。
protected function prepareForValidation()
{
$this->merge([
'org_id' => $this->route('org_id'),
]);
}
これでリクエストデータにorg_id
を含めることができ、rules()
も機能してくれるというわけです。
ちなみに調べていくと302のエラー原因を教えてくれるサイトがありました。rules()
のバリデーションが通らない場合はリダイレクトするみたいです。
5. まとめ
普段いかに雑にリクエストデータを扱っていたかを知る良い教訓となったエラーの学びとなりました。API開発一つとっても、まだまだ知らないことだらけなので、こういう基本的な事を積み重ねていこうと思います。
参考記事