Undefined array key "tag_ids"
このエラーが出たとき、
記事の投稿時に、チェックボックスでタグを選択できるような機能を実装していました。
以下はコントローラとビューの実装です。
public function store(ArticleStoreRequest $request) {
$validated = $request->validated();
/** @var User $user */
$user = Auth::user();
$article = $user->articles()->create($validated);
$tag_ids = array_filter($validated['tag_ids']); //この行でエラー
$article->tags()->attach($tag_ids);
return to_route('articles.index');
}
<form method="POST" action="{{ route('articles.store') }}">
@csrf
<fieldset class="fieldset bg-base-200 border-base-300 rounded-box w-l border p-4">
<legend class="fieldset-legend">記事投稿</legend>
<label for="title" class="label">{{ __('Title') }}</label>
<input type="text" class="input w-lg" placeholder="タイトル" name="title" value="{{ old('title') }}">
<label for="content" class="label">{{ __('Content') }}</label>
<textarea name="content" id="content" class="textarea w-lg size-100">{{ old('content') }}</textarea>
<label for="tag_div">タグ</label>
<div id="tag_div">
@foreach ($tags as $tag)
<input
type="checkbox"
aria-label="{{ $tag->name }}"
class="btn btn-xs mr-2 mb-2"
value="{{ $tag->id }}"
id="tag_{{ $tag->id }}"
name="tag_ids[]"
{{ is_array(old('tag_ids')) && in_array($tag->id, old("tag_ids")) ? 'checked' : '' }}
/>
@endforeach
</div>
<button type="submit" class="btn btn-neutral">{{ __('Submit') }}</button>
</fieldset>
</form>
問題の所在はFormRequest
クラスにあった!
「$tag_ids
というタグの配列は、ちゃんとフロントから渡しているはずなのに…」と思っていたのですが、AIに相談したところ、どうやら問題はFormRequest
クラスでのバリデーションの書き方にあったようです。
FormRequest
の基本を知りたい方はこちらをどうぞ
今回は、以下で説明する、FormRequest
の二つの仕様を理解していなかったために、問題が起こっていたようです。
①コントローラの$request->validated()
には、rules
に定義したプロパティしか渡ってこない
そのときの私は、以下のように、各要素へのバリデーションしかしていませんでした。
public function rules(): array
{
return [
'tag_ids.*' => ['nullable', 'numeric', Rule::exists('tags', 'id')],
];
}
FormRequest
クラスでバリデーションを行う場合、コントローラでは$request->validated()
という形で、バリデーション済みの値を配列で取得して使います。
その配列の中には、rules()
内に定義したプロパティだけが含まれます。
つまり、私はtag_ids
という配列自体をrules
の中に記載していなかったので、コントローラで$request->validated()
したときに、その配列の中にtag_ids
が含まれていなかったのです。
対策
配列にバリデーションを実施する際は、
- 配列自体へのバリデーション
- 配列の各要素へのバリデーション
これらを合わせて書くようにしましょう。
よって、このように修正しました。
public function rules(): array
{
return [
'tag_ids' => ['nullable', 'array'],
'tag_ids.*' => ['numeric', Rule::exists('tags', 'id')],
];
}
以上が、今回の一番大きな問題でした。
しかしもうひとつポイントがあります。
②null
なプロパティは$request->validated()
に含まれない
今回のチェックボックスは、一つも選択されていなくてもOKとしています。
よってrules
でも、tag_ids
にはnullable
を設定しました。
このように配列自体をnullable
にしている場合には、少し注意が必要です。
FormRequest
では、バリデーション時にnull
だったプロパティについては、コントローラまで届かない仕様になっています。$request->validated()
で取得できる配列に、含まれなくなるということです。
チェックボックスがどれも未選択の場合、そもそもブラウザからtag_ids
というname
の値は送信されてきません。つまり、FormRequest
でのバリデーション時には、tag_ids
はnull
ということになります。
すると、コントローラで$request->validated()['tag_ids']
としても、何も取得することができません。
Undefined array key "tag_ids"
というエラーが起こってしまいます。
対策
prepareForValidation
という、バリデーションの前にデータを加工するメソッドを使いましょう。
データの正規化や加工に便利なメソッドです。
今回はこの中で、null
の場合に空の配列を入れるようにしておきます。
public function rules(): array
{
return [
'tag_ids' => ['nullable', 'array'],
'tag_ids.*' => ['numeric', Rule::exists('tags', 'id')],
];
}
protected function prepareForValidation()
{
$this->merge([
'tag_ids' => $this->input('tag_ids', []),
]);
}
input
メソッドでは、第二引数にデフォルト値を設定できますので、ここに空の配列を入れます。
$this->merge
は、引数に渡した内容を、リクエストデータに反映するためのメソッドです。
これで、tag_ids
がnull
の場合には空の配列をtag_ids
に代入してから、バリデーションへ進むことができます。
すると、空の配列がコントローラへ渡るため、Undefined array key "tag_ids
というエラーは起こらなくなります。
今回は以上2点を押さえることで、FormRequestクラスを適切な形にできました。
参考サイト