Laravel5.5 REST APIを想定してフォームリクエストでバリデーションを行い、バリデーションのエラーをJSONで返す方法

REST APIでログイン認証を行う方法を調べていてバリデーション部分はどう対応するのかを調べた。
https://qiita.com/junsan50/items/ec7f810decd3b82d3d76

色々調べたがここが一番わかりやすかった。

僕の場合、マルチログイン環境でuserとadminでログイン認証を分けていて今回はadmin部分でメモ
さらにidとemail両方に対応した認証で実装。

バリデーションを行う方法の1つにフォームリクエストがある。
ロジックを分離することでコントローラーをすっきりさせることができる。

まずフォームリクエストを作成
php artisan make:request AdminRequest

作成された app/Http/Requests/AdminRequest.php を下記のように変更

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;  // 追加
use Illuminate\Http\Exceptions\HttpResponseException;  // 追加 
use Illuminate\Http\Request; // 追加

class AdminRequest extends FormRequest
{
    public function authorize()
    {
        // フォームリクエストが許可されているかどうか bool $this->path() などでどこからのリクエストかチェックしたりして不正な場合はfasleにして403を返したりする。
        return true;//フォームリクエストが許可されているかどうか bool

    }

    public function rules(Request $request)
    {
        // $request->loginはフォームのidまたはメールアドレスを入力するテキストフィールドの名前

        if (filter_var($request->login, \FILTER_VALIDATE_EMAIL)) {
            // メールアドレス形式である場合
            return [
                'login' => 'required|max:30',
                'password' => 'required|between:6,20',
            ];
        } else {
            // id形式の場合
            return [
                'login' => 'required|between:6,20',
                'password' => 'required|between:6,20',
            ];
        }

    }

    public function messages()
    {
        return [
            'login.required' => 'メールアドレスかidは必須です。',
            'login.email' => 'メールアドレスが正しくありません。',
            'login.max' => 'メールアドレスが30文字以上になっています。',
            'login.between' => 'ログインidは6文字以上20文字以下で入力して下さい。',
            'password.required' => 'パスワードは必須です。',
            'password.betwee' => 'パスワードはは6文字以上20文字以下で入力して下さい。',
        ];
    }

    // FormRequest::failedValidation() をオーバーライド

    protected function failedValidation( Validator $validator)
    {
        $response['data']    = [];
        $response['status']  = 'NG';
        $response['summary'] = 'Failed validation.';
        $response['errors']  = $validator->errors()->toArray();

        throw new HttpResponseException(
            response()->json( $response, 422 )
        );
    }
}

これでバリデーションロジックは分離したので
AdminLoginControllerのloginメソッドで作成したフォームリクエストをuseする。

namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\AdminRequest; //作ったやつ

class AdminLoginController extends Controller
{
    public function __construct(Request $request)
    {
      $this->middleware('guest:admin');
    }

    public function showLoginForm()
    {
      return view('auth.admin-login');
    }

    public function username()
    {
        return 'login';
    }

    // AdminRequestを利用
    public function login(AdminRequest $request)
    {

        $username = $request->input($this->username());
        $password = $request->input('password');

        if (filter_var($username, \FILTER_VALIDATE_EMAIL)) {
            $credentials = ['email' => $username, 'password' => $password];
        } else {
            $credentials = [$this->username() => $username, 'password' => $password];
        }

        return $credentials; // ログインするための情報でとりあえず処理が通ってるかどうか確認するだけ。情報が間違っていればバリデーションのエラーがjsonで返却される。
    }

}

テストする前にcsrf対策を無効にする。
laravelの場合postはデフォルトでcsrfが有効になるのでREST APIの場合困ってしまうので
app/Http/Middleware/VerifyCsrfToken.php を下記のように修正

namespace App\Http\Middleware;
 
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
 
class VerifyCsrfToken extends BaseVerifier
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    // urlにadmin とつくものは全てcsrf除外
    protected $except = [
        'admin/*',
    ];
}

apiのエンドポイントをadmin認証の場合
http://example.com/admin/login
としているので
上位エンドポイントに
AdvancedRestClientで
login ・・・・ id、メールアドレス入力のテキストフィールドの名前
password ・・・・ パスワード
をpost送信。エラーだと

{
“data”: [],
“status”: “NG”,
“summary”: “Failed validation.”,
“errors”: {
“login”: [
“ログインidは6文字以上です”
],
“password”: [
“パスワードは6文字以上です。”
],
}
}

みたいなデータが返ってくる。