tohokuaikiのチラシの裏

技術的ネタとか。

LaravelのControllerでValidationを行う時

メモ

Controller

validateメソッドの - 第一引数がRule - 第二引数がエラーメッセージ - 第三引数がAttribute名

<?php
use App\Rules\UserPassword;

class PasswordController extends Controller
{
    /**
     * Update the user's password.
     */
    public function update(Request $request): RedirectResponse
    {
        $validated = $request->validate(
            [
                'current_password' => ['required', 'current_password'],
                'password' => ['confirmed', new UserPassword()],
            ],
            [
                'password.confirmed' => ':attribute の入力が確認用と一致しません。',
            ],
            [
                'current_password' => '現在のパスワード',
                'password' => '新しいパスワード',
            ]
        );

        $request->user()->update([
            'password' => $validated['password'],
        ]);

        return back();
    }
}

独自Rule Validator

ここでもAttributeのPlaceholderは使える。

<?php

class UserPassword implements ValidationRule
{

    private $min_length = 8;

    /**
     * Run the validation rule.
     *
     * @param  \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $rules = [
            'required',
            'string',
            Password::min($this->min_length)
                ->mixedCase()
                ->numbers()
                ->symbols(),
        ];
        $messages = [
            ':attribute は必須です。',
            ':attribute は文字列で入力してください。',
            ':attribute は' . $this->min_length . '文字以上で、大文字・小文字・数字・記号を含める必要があります。',
        ];
        foreach ($rules as $key => $rule) {
            $validator = Validator::make(
                [$attribute => $value],
                [$attribute => [$rule]],
            );
            if ($validator->fails()) {
                $fail($messages[$key] ?? self::class . " validation error");
            }
        }
    }
}

$failでエラーメッセージを配列に積んでいける。ただし、Inertia.jsではエラーメッセージ配列の最初の1つ目だけを送るのでちょっと工夫が必要。

ということでInteria.jsでの工夫

ちょっと工夫はRequestクラスに行う。これを継承していけばOK

<?php

class InertiaRequest extends FormRequest
{
   /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
           return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            //
        ];
    }


    /**
     * Inertia.jsはValidationエラーを最初の1つしか投げないので、改行でJOINしたものにする。
     * 
     */
    protected function failedValidation(Validator $validator)
    {
        $errors = collect($validator->errors()->toArray())
            ->map(fn($messages) => implode("\n", $messages))
            ->toArray();

        // Inertiaにキャッチさせず、ここでResponseExceptionを投げて同一ページ更新
        throw new HttpResponseException(
            back()->withErrors($errors)->withInput()
        );
    }
}

何をしているかというと、改行コードでJOINしているだけ。受け取ったInertia.jsではそれをsplitして複数に見せれれば大丈夫。