Laravelで簡単なCMSを作成(フォームリクエストでバリデーション)

Laravelで簡単なCMSを作成してみます。記事登録までの機能は下記の記事を参考に。

https://qiita.com/kiyo27/items/e4bbaf96ed970be5c746

今回は新規登録する記事にバリデーション機能を追加します。Laravelのバリデーション機能には「フォームリクエスト」という独自のバリデーションクラスを作成し適用できる機能が用意されています。「フォームリクエスト」の利点は、コントローラメソッドにタイプヒンティングすることで、バリデーションロジックをコントローラメソッド内に記述する必要がなくなり、メソッドがスッキリすることです。

https://readouble.com/laravel/8.x/ja/validation.html#introduction

## フォームリクエストクラス作成

``artisan``コマンドでフォームリクエストのひな形を作成できます。
https://readouble.com/laravel/8.x/ja/validation.html#creating-form-requests

```
php artisan make:request ArticlesRequest
```

## バリデーションルール作成

フォームリクエストクラスの``rules``メソッドでバリデーションルールを定義します。
https://readouble.com/laravel/8.x/ja/validation.html#creating-form-requests

```php:ArticlesRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|unique|max255',
            'body' => 'required',
        ];
    }
}

```

### エラーメッセーのカスタマイズ

``message``メソッドをオーバーライドすることでエラーメッセージをカスタマイズできます。
https://readouble.com/laravel/8.x/ja/validation.html#customizing-the-error-messages

```php:ArticlesRequest.php
public function messages()
{
    return [
        'title.required' => 'タイトルを入力してください',
        'body.required' => '本文は入力必須です',
    ];
}
```

## バリデーションルールの実行

コントローラメソッドで作成した``ArticlesRequest``クラスをタイプヒントで指定することで、コントローラメソッドが実行される前にバリデーションが実行されます。
https://readouble.com/laravel/8.x/ja/validation.html#creating-form-requests

```php:ArticlesController.php
use App\Http\Requests\ArticlesRequest;

public function create(ArticlesRequest $request)
{
    $validated = $request->validated();
    
    $article = Article::create([
        'title' => $request->title,
        'body' => $request->body,
        'user_id' => Auth::id(),
        'slug' => $request->title,
    ]);

    // 中間テーブルにtagを登録
    $article->tags()->attach($request->tags);

    return redirect('/articles');
}  
```

## バリデーションエラーの表示

``@error``ディレクティブを使用すると、特定の属性のバリデーションエラーメッセージが存在するか確認でき、``$message``変数でエラーメッセージを取得できます。
https://readouble.com/laravel/8.x/ja/validation.html#the-at-error-directive

```html:add.blade.php
<label class="block">
    <span class="text-gray-700">Title</span>
    <input type="text" name="title" class="mt-1 block w-full @error('title') border-red-500 @enderror">
    @error('title')
    <p class="text-red-500 text-xs italic mt-3">{{ $message }}</p>
    @enderror
</label>
<label class="block">
    <span class="text-gray-700">Body</span>
    <textarea class="mt-1 block w-full @error('body') border-red-500 @enderror" rows="3" name="body"></textarea>
    @error('body')
    <p class="text-red-500 text-xs italic mt-3">{{ $message }}</p>
    @enderror
</label>
```

### フォームの再取得

バリデーションエラーがあると直前のページにリダイレクトされます。直前のフォーム画面で入力された値は、``old``メソッドまたはヘルパを使用することで取得できます。
https://readouble.com/laravel/8.x/ja/validation.html#repopulating-forms

```html:add.blade.php
<label class="block">
    <span class="text-gray-700">Title</span>
    <input type="text" name="title" class="mt-1 block w-full @error('title') border-red-500 @enderror" value="{{ old('title') }}">
    @error('title')
    <p class="text-red-500 text-xs italic mt-3">{{ $message }}</p>
    @enderror
</label>
<label class="block">
    <span class="text-gray-700">Body</span>
    <textarea class="mt-1 block w-full @error('body') border-red-500 @enderror" rows="3" name="body">{{ old('body') }}</textarea>
    @error('body')
    <p class="text-red-500 text-xs italic mt-3">{{ $message }}</p>
    @enderror
</label>
```

## カスタムバリデーションルール

独自のバリデーションルールを定義できます。
https://readouble.com/laravel/8.x/ja/validation.html#custom-validation-rules

### ルールオブジェクトを作成

``artisan``コマンドでルールオブジェクトを作成します。

```
php artisan make:rule Uppercase
```

### カスタムルール定義

LaravelのドキュメントではUpperCaseのルールを紹介しているので、これを使ってみます。

```php:UpperCase.php
<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class Uppercase implements Rule
{
    /**
     * Create a new rule instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        return strtoupper($value) === $value;
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'The :attribute must be uppercase.';
    }
}
```

### カスタムルールの適用

ルールオブジェクトをほかのバリデーションルールと同様に指定します。

```php:ArticleRequest.php
public function rules()
{
    return [
        'title' => ['required', 'unique:users', 'max:255', new Uppercase],
        'body' => 'required',
    ];
}
```

## 1回切りのカスタムルール作成

他のフォームでは使用しないルールは、ルールオブジェクトではなくクロージャを渡すことで実現できます。
https://readouble.com/laravel/8.x/ja/validation.html#using-closures

```php:ArticleRequest.php
public function rules()
{
    return [
        // 'title' => 'required|unique:users|max:255',
        'title' => [
            'required',
            'unique:users',
            'max:255',
            function ($attribute, $value, $fail) {
                if (! preg_match('/^【.+】/u', $value)) {
                    $fail('【単語】見出し の形式でタイトルを書いてください');
                }
            }
        ],
        'body' => 'required',
    ];
}
```