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パラメータ関連の処理を除去するだけ。
検証
以下で期待通りの動きをするか検証をする。
- http://localhost:8000/1/hogehoge + name = 'foo'でpostリクエスト(正常)
- http://localhost:8000/1/ho + name = 'foo'でポストリクエスト(passwordが4文字以下)
- http://localhost:8000/a/hogehoge + name = 'foo'でポストリクエスト(idが数字じゃない)
- http://localhost:8000/1/hogehoge + (nameなし)でpostリクエスト(nameなし)
以下、2の場合の結果。
{
"status": "NG",
"date": "2018-10-24",
"message": "password must be at least 4 chars。"
}