Posted at

Laravel5.7でAPIのJSONエラーメッセージをカスタマイズしたい

APIのバリデーションも(フロントと同様)FormRequestで行いたい場合の対応。


やりたいこと


  • エラー時のJSONメッセージのカスタマイズ(statusとかの追加)したい

  • URLパラメータ(/users/{id}/{password})もバリデーションしたい

JSONのカスタマイズのイメージは

{

"status": "NG",
"date": "2018-10-24",
"message": "password must be at least 4 chars。"
}

こな感じ。

status入れたり、エラーメッセージはバリデーション機能が返すものを利用するけどkeyは変えたいとか。


実装

FormRequestを利用していろいろ記述する。


ルーティング

api.phpに下記を記述。

POST送信時にURLパラメータ(idやpassword)もValidateしたい。POSTではnameというパラメータを送るつもり。


api.php

Route::post('store/{id}/{password}', 'UserController@store');

// Route::get('users/{id}/{password}', 'UserController@index');


サンプルとしてはpostのみ記載するが、getの際も問題なく動作しました。



Controller

nameを返すだけの記述。利用するFormRequestを指定。


UserController

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests\IndexRequest;
use App\Http\Requests\StoreRequest;

class UserController extends Controller
{
function store(StoreRequest $request,$id,$password)
{
return $request->name;
}

// function index(IndexRequest $request,$id,$password){
// return $id;
// }
}



FormRequest

下記のコマンドで生成。

php artisan make:request StoreRequest

そして実装。要点としては、


  • rulesとかmessagesとかは普通に記述

  • validationData()でパラメータを追加(id,password)

  • failedValidation()でバリデーションエラー時にカスタムJSONを返すようにする

という感じ。

項目毎に分岐分各ならカスタムで実装してもいいのでは?とは思うけど、ルールが統一できるのと、記述量もやや少なくても済むのかなと。


StoreRequest

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

class StoreRequest extends FormRequest
{
//とりあえず認証はtrueで
public function authorize()
{
return true;
}

//ルールの設定
public function rules()
{
return [
'id' => 'integer',
'password' => 'required|min:4',
'name' => 'required'
];
}

//カスタムメッセージ
public function messages(){
return [
'id.integer' => 'id must be integer.',
'password.required' => 'password must be required.',
'password.min' => 'password must be at least 4 chars。',
'name.required' => 'name must be required',
];
}

//URLパラメータも処理できるように
protected function validationData()
{
return array_merge($this->request->all(), [
'id' => $this->id,
'password' => $this->password,
]);
}

//エラー時HTMLページにリダイレクトされないようにオーバーライド
protected function failedValidation(Validator $validator)
{

//個人的ニーズに合わせ、項目別にエラーを返す仕様

//任意の値を追加
$response['status'] = 'NG';
$response['date'] = date('Y-m-d');

//もしidにエラーがあれば
if($validator->errors()->has('id')) {
$response['message'] = $validator->errors()->first('id');
throw new HttpResponseException(response()->json($response, 422));
}

//もしpasswordにエラーがあれば
if($validator->errors()->has('password')) {
$response['message'] = $validator->errors()->first('password');
throw new HttpResponseException(response()->json($response, 422));
}

//もしemailにエラーがあれば
if($validator->errors()->has('name')) {
$response['message'] = $validator->errors()->first('name');
throw new HttpResponseException(response()->json($response, 422));
}

//全体のエラーを表示
// $response['message'] = $validator->errors()->toArray();
// throw new HttpResponseException(
// response()->json($response, 422)
// );
}
}



ちなみにget(index)の場合はnameパラメータ関連の処理を除去するだけ。



検証

以下で期待通りの動きをするか検証をする。



  1. http://localhost:8000/1/hogehoge + name = 'foo'でpostリクエスト(正常)


  2. http://localhost:8000/1/ho + name = 'foo'でポストリクエスト(passwordが4文字以下)


  3. http://localhost:8000/a/hogehoge + name = 'foo'でポストリクエスト(idが数字じゃない)


  4. http://localhost:8000/1/hogehoge + (nameなし)でpostリクエスト(nameなし)

以下、2の場合の結果。

{

"status": "NG",
"date": "2018-10-24",
"message": "password must be at least 4 chars。"
}