Laravel5.7: バリデーション(users)

# 親記事
[Laravel 5.7で基本的なCRUDを作る - Qiita](https://qiita.com/sutara79/items/ef30fcdfb7afcb2188ea)


# User用のフォームリクエストを作る
[前回の記事](https://qiita.com/sutara79/items/867a9dc7de50df8fd53c)とほぼ同じです。


```text:PowerShell
# User用のフォームリクエストを生成
> php artisan make:request StoreUser
```

```php:app/Http/Requests/StoreUser.php
    public function authorize()
    {
        // 認可は別の箇所で行うので、ここでは素通りさせる
        return true;
    }

    public function rules()
    {
        return [
            'name' => 'required|string|max:191',
            'email' => 'required|string|email|max:191|unique:users',
            'password' => 'required|string|min:6|max:191|confirmed',
        ];
    }
```

```diff:app/Http/Controllers/UserController.php
 // 忘れずにインポートすること!!!!
+use App\Http\Requests\StoreUser;
 
 class UserController extends Controller
 {
     /**
      * Store a newly created resource in storage.
      *
-     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Http\Requests\StoreUser $request
      * @return \Illuminate\Http\Response
      */
-    public function store(Request $request)
+    public function store(StoreUser $request)
     {

(中略)

     /**
      * Update the specified resource in storage.
      *
      * @param  \Illuminate\Http\Request  $request
      * @param  \App\User $user
      * @return \Illuminate\Http\Response
      */
     public function update(Request $request, User $user)
     {
         $this->authorize('edit', $user);
+
+        // name欄だけを検査するため、元のStoreUserクラス内のバリデーション・ルールからname欄のルールだけを取り出す。
+        $request->validate([
+            'name' => (new StoreUser())->rules()['name']
+        ]);
```

上のコントローラのupdateアクションでは、引数の型を`StoreUser`ではなく`Request`のままにしています。
`StoreUser`にすると、name欄、email欄、password欄の全てでバリデーション検査が実行されます。
しかし、現在の編集画面ではname欄しか変更できません。
name欄のみの入力値をバリデーションへ渡すと、email欄やpassword欄は空白であるとみなされ、必ず検査に失敗します。
そのため、フォームリクエスト・バリデーションに頼らず、`$request->validate`メソッドを使わなければなりません。
しかし、ルールを直に書くと管理が面倒なので、`StoreUser`のルールを流用しています。

# ビューを修正する
```diff:resources/views/users/create.blade.php
         <div class="form-group">
             <label for="name">{{ __('Name') }}</label>
-            <input id="name" type="text" class="form-control" name="name" required autofocus>
+            <input id="name" type="text" class="form-control @if ($errors->has('name')) is-invalid @endif" name="name" value="{{ old('name') }}" required autofocus>
+            @if ($errors->has('name'))
+                <span class="invalid-feedback" role="alert">
+                    {{ $errors->first('name') }}
+                </span>
+            @endif
         </div>
         <div class="form-group">
             <label for="email">{{ __('E-Mail Address') }}</label>
-            <input id="email" type="email" class="form-control" name="email" required>
+            <input id="email" type="email" class="form-control @if ($errors->has('email')) is-invalid @endif" name="email" value="{{ old('email') }}" required>
+            @if ($errors->has('email'))
+                <span class="invalid-feedback" role="alert">
+                    {{ $errors->first('email') }}
+                </span>
+            @endif
         </div>
         <div class="form-group">
             <label for="password">{{ __('Password') }}</label>
-            <input id="password" type="password" class="form-control" name="password" required>
+            <input id="password" type="password" class="form-control @if ($errors->has('password')) is-invalid @endif" name="password" required>
+            @if ($errors->has('password'))
+                <span class="invalid-feedback" role="alert">
+                    {{ $errors->first('password') }}
+                </span>
+            @endif
         </div>
```

```diff:resources/views/users/edit.blade.php
         <div class="form-group">
             <label for="name">{{ __('Name') }}</label>
-            <input id="name" type="text" class="form-control" name="name" value="{{ $user->name }}" required autofocus>
+            <input id="name" type="text" class="form-control @if ($errors->has('name')) is-invalid @endif" name="name" value="{{ old('name', $user->name) }}" required autofocus>
+            @if ($errors->has('name'))
+                <span class="invalid-feedback" role="alert">
+                    {{ $errors->first('name') }}
+                </span>
+            @endif
         </div>
```

# カスタム属性名を追加する
```diff:resources/lang/ja/validation.php
     'attributes' => [
         'title' => __('Title'),
         'body' => __('Body'),
+        'name' => __('Name'),
+        'email' => __('E-Mail Address'),
+        'password' => __('Password'),
     ],
```

# Register用のルールと統合する
[以前の記事](https://qiita.com/sutara79/items/fdbfbf2a8f86f7d8f7fb)で自動生成したログイン関連のファイルのうち、ユーザー登録を担当するRegisterコントローラにもUserのバリデーション・ルールが記載されています。
これを削除して、StoreUser内のルールを流用することで統一します。

```php:app/Http/Controllers/Auth/RegisterController.php
// 忘れずにインポートすること
use App\Http\Requests\StoreUser;

    (中略)

    protected function validator(array $data)
    {
        // return Validator::make($data, [
        //     'name' => 'required|string|max:255',
        //     'email' => 'required|string|email|max:255|unique:users',
        //     'password' => 'required|string|min:6|confirmed',
        // ]);
        // 統一したバリデーション・ルールを用いる
        return Validator::make($data, (new StoreUser())->rules());
    }
```

# ResetPassword用のルールと統合する
パスワード再設定でも、独自のバリデーションルールが、下記のトレイトの`rules`メソッドで設定されています。
:link: [Illuminate\Foundation\Auth\ResetsPasswords](https://github.com/laravel/framework/blob/5.7/src/Illuminate/Foundation/Auth/ResetsPasswords.php#L64)

これをResetPasswordControllerで上書きします。
パスワードのルールのみを変更します。
メールアドレスは、対象ユーザーのメールアドレスと一致しなければどのみちエラーとなってしまうので、細かなルールに変更する必要はありません。

```php:app/Http/Controllers/Auth/ResetPasswordController.php
// 忘れずにインポートすること
use App\Http\Requests\StoreUser;

    (中略)

    /**
     * パスワード再設定用のバリデーションルール
     *
     * @return array
     */
    protected function rules()
    {
        return [
            'token' => 'required',
            'email' => 'required|email',
            'password' => (new StoreUser())->rules()['password'],
        ];
    }
```

- - -
# (余談) パスワードは6文字以上にすべき
6文字以上なら面倒がありません。

どういうことかと言うと、下記の場所で`mb_strlen($password) >= 6`とハードコーディングされていて、6文字未満に設定するのが難しいからです。
:link: [Illuminate\Auth\Passwords\PasswordBroker](https://github.com/laravel/framework/blob/5.7/src/Illuminate/Auth/Passwords/PasswordBroker.php#L175)

このハードコーディングされた箇所が実行されないようにするには、先ほどのResetPasswordControllerに下記のような追記が必要です。

```php:app/Http/Controllers/Auth/ResetPasswordController.php
// 追加でインポート
use Illuminate\Support\Facades\Password;
use Illuminate\Http\Request;

    (中略)

    /**
     * Reset the given user's password.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
     */
    public function reset(Request $request)
    {
        $this->validate($request, $this->rules(), $this->validationErrorMessages());

        $broker = Password::broker();
        $broker->validator(function() {
            return true; // 追加のバリデーションは不要
        });
        $response = $broker->reset(
            $this->credentials($request), function ($user, $password) {
                $this->resetPassword($user, $password);
            }
        );

        return $response == Password::PASSWORD_RESET
                    ? $this->sendResetResponse($request, $response)
                    : $this->sendResetFailedResponse($request, $response);
    }
```

上の`reset`メソッドは、[Illuminate\Foundation\Auth\ResetsPasswords](https://github.com/laravel/framework/blob/5.7/src/Illuminate/Foundation/Auth/ResetsPasswords.php#L38) トレイトの同名のメソッドを上書きします。
上のコードでも使っている`Password`ファサードの元が、`6`がハードコーディングされている`PasswordBroker`クラスなのです。

このようにフレームワークの奥深くにまで手を伸ばすと、メンテナンスが面倒だと思います。
たかが`mb_strlen($password) >= 6`を避けるだけのために…。
そういうわけで、パスワードの最小文字数は6以上にすべきだと思います。

一応、ハードコーディングしている箇所を削除してもらえるよう、要望を送りました。
https://github.com/laravel/ideas/issues/1307