LumenのControllerクラスにあるバリデーションハンドリング
Lumenのバリデーションエラーをハンドリングするお作法ってどんなだろうと思って基底Controllerクラスのソースコード読んでました。
protected function throwValidationException(Request $request, $validator)
{
throw new ValidationException($validator, $this->buildFailedValidationResponse(
$request, $this->formatValidationErrors($validator)
));
}
protected function buildFailedValidationResponse(Request $request, array $errors)
{
if (isset(static::$responseBuilder)) {
return call_user_func(static::$responseBuilder, $request, $errors);
}
return new JsonResponse($errors, 422);
}
protected function formatValidationErrors(Validator $validator)
{
if (isset(static::$errorFormatter)) {
return call_user_func(static::$errorFormatter, $validator);
}
return $validator->errors()->getMessages();
}
ValidationExceptionを投げる時第2引数にレスポンスデータを渡す模様。
レスポンスのフォーマットするのは $errorFormatter
と $responseBuilder
の2つのプロパティ(コールバック関数)らしい。
この2つのプロパティの設定は下記セッターがありました。
public static function buildResponseUsing(BaseClosure $callback)
{
static::$responseBuilder = $callback;
}
public static function formatErrorsUsing(BaseClosure $callback)
{
static::$errorFormatter = $callback;
}
ということでコンストラクタでフォーマットをセットすれば良さそうに見える。
public function __construct()
{
self::formatErrorsUsing(function(Validator $validator) {
// バリデーションエラーのメッセージフォーマットを設定
});
self::buildResponseUsing(function(Request $request, array $errors) {
// バリデーションエラーのレスポンスフォーマットを設定
});
}
バリデーションエラーのハンドリングをControllerでやるべきなのか
Relux Advent Calendar 2018の昨日の記事「Lumenで例外ハンドラをいじって異常系レスポンスを自由に変更しよう」のようにLaravel/LumenのException処理はExceptionHandlerに集約することが標準な気がします。
上記のようにControllerでハンドリングをすると、バリデーションエラーはController、それ以外のException(NotFoundなど)はExceptionHandlerに持たせる、というちぐはぐな状態になってしまいます。
$errorFormatter
はバリデーションに引っかかったフィールド、値、エラーメッセージの構造を定義できます。これはバリデーションエラーの詳細データの構造なのでValidationExceptionが持つべき責務のように思えます。
$responseBuilder
は文字通りレスポンスのフォーマットを定義できます。ExceptionHandlerで異常系レスポンスを定義する設計なのであればExceptionHandlerが持つべき責務のように思えます。
結論、こんな構成が良いのでは。
use Illuminate\Validation\ValidationException
class CustomValidationException extends ValidationException
{
public function errors()
{
$errors = parent::errors();
// TODO バリデーションエラー情報をフォーマット
}
class BaseController extends Controller
{
protected function throwValidationException(Request $request, $validator)
{
// 上記のエラーフォーマットを返すExceptionクラスを投げる
throw new CustomValidationException($validator, $this->buildFailedValidationResponse(
$request, $this->formatValidationErrors($validator)
));
}
最後はいつものExceptionHandlerでレスポンスをハンドリングする。
ということで前段のControllerに定義しているバリデーションエラーハンドリングは使っていないのですが、こちらの構成のほうが収まりが良いと思います。