In `Laravel`, each request will be encapsulated into a `Request` object, `Form Request `Objects are custom `Request` classes that contain additional validation logic (and access control). This article analyzes the FormRequest exception handling process and proposes a custom way to handle FormRequest verification failure.
All examples are based on Laravel 5.1.39 (LTS)
The weather is nice today, let’s talk about form validation.
Do form validation in Controller
Some students write form verification logic in the Controller, such as this verification of user-submitted comment content:
<?php // ... use Validator; class CommentController { public function postStoreComment(Request $request) { $validator = Validator::make($request->all(), [ 'comment' => 'required', // 只是实例,就写个简单的规则,你的网站要是这么写欢迎在评论里贴网址 ]); if ($validator->fails()) { return redirect() ->back() ->withErrors($validator) ->withInput(); } }
If written like this, form validation and business logic will be squeezed together, there will be too much code in our Controller, and repeated validation rules will basically be copied and pasted.
We can use Form Request to encapsulate the form validation code, thereby streamlining the code logic in the Controller and focusing it on the business. The independent form validation logic can even be reused in other requests, such as modifying comments.
What is Form Request
In Laravel, each request will be encapsulated as a Request object. The Form Request object is a custom Request class that contains additional verification logic (and access control).
How to use Form Request for form verification
Laravel provides Artisan commands to generate Form Request:
$ php artisan make:request StoreCommentRequest
So app/Http/Requests/StoreCommentRequest.php was generated, let us analyze the content:
<?php namespace App\Http\Requests; use App\Http\Requests\Request; // 可以看到,这个基类是在我们的项目中的,这意味着我们可以修改它 class StoreCommentRequest extends Request { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() // 这个方法可以用来控制访问权限,例如禁止未付费用户评论… { return false; // 注意!这里默认是false,记得改成true } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() // 这个方法返回验证规则数组,也就是Validator的验证规则 { return [ // ]; } }
Then it’s easy. In addition to letting the authorize method return true, we also have to let the rules method return our verification rules:
<?php // ... public function rules() { return [ ]; } // ...
Then modify our Controller:
<?php // ... // 之前:public function postStoreComment(Request $request) public function postStoreComment(\App\Http\Requests\StoreCommentRequest $request) { // ... } // ...
In this way, Laravel will automatically call StoreCommentRequest for form verification.
Exception handling
If the form validation fails, Laravel will redirect to the previous page and write the error to the Session. If it is an AJAX request, a piece of JSON data with HTTP status 422 will be returned, similar to this:
{comment: ["The comment field is required."]}
I won’t go into details here on how to modify the prompt information. If anyone wants to see related tutorials, you can leave a message.
We mainly talk about how to customize error handling.
Generally speaking, errors in Laravel are exceptions, and we can all handle them uniformly in appExceptionshandler.php. Form Request did throw an IlluminateHttpExceptionHttpResponseException exception, but this exception was specially handled in the routing logic.
First let’s take a look at how Form Request is executed:
IlluminateValidationValidationServiceProvider:
<?php namespace Illuminate\Validation; use Illuminate\Support\ServiceProvider; use Illuminate\Contracts\Validation\ValidatesWhenResolved; class ValidationServiceProvider extends ServiceProvider { /** * Register the service provider. * * @return void */ public function register() { $this->registerValidationResolverHook(); // 看我看我看我 $this->registerPresenceVerifier(); $this->registerValidationFactory(); } /** * Register the "ValidatesWhenResolved" container hook. * * @return void */ protected function registerValidationResolverHook() // 对,就是我 { // 这里可以看到对`ValidatesWhenResolved`的实现做了一个监听 $this->app->afterResolving(function (ValidatesWhenResolved $resolved) { $resolved->validate(); // 然后调用了它的`validate`方法进行验证 }); } // ...
You guessed it, Form Request implements this IlluminateContractsValidationValidatesWhenResolved interface:
<?php namespace Illuminate\Foundation\Http; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Illuminate\Routing\Redirector; use Illuminate\Container\Container; use Illuminate\Contracts\Validation\Validator; use Illuminate\Http\Exception\HttpResponseException; use Illuminate\Validation\ValidatesWhenResolvedTrait; use Illuminate\Contracts\Validation\ValidatesWhenResolved; // 是你 use Illuminate\Contracts\Validation\Factory as ValidationFactory; // 我们`app\Http\Requests\Request`便是继承于这个`FormRequest`类 class FormRequest extends Request implements ValidatesWhenResolved // 就是你 { use ValidatesWhenResolvedTrait; // 这个我们待会儿也要看看 // ...
The validate method in the FormRequest base class is implemented by this IlluminateValidationValidatesWhenResolvedTrait:
IlluminateValidationValidatesWhenResolvedTrait:
<?php namespace Illuminate\Validation; use Illuminate\Contracts\Validation\ValidationException; use Illuminate\Contracts\Validation\UnauthorizedException; /** * Provides default implementation of ValidatesWhenResolved contract. */ trait ValidatesWhenResolvedTrait { /** * Validate the class instance. * * @return void */ public function validate() // 这里实现了`validate`方法 { $instance = $this->getValidatorInstance(); // 这里获取了`Validator`实例 if (! $this->passesAuthorization()) { $this->failedAuthorization(); // 这是调用了访问授权的失败处理 } elseif (! $instance->passes()) { $this->failedValidation($instance); // 这里调用了验证失败的处理,我们主要看这里 } } // ...
In validate, if the verification fails, $this->failedValidation() will be called, continue:
IlluminateFoundationHttpFormRequest:
<?php // ... /** * Handle a failed validation attempt. * * @param \Illuminate\Contracts\Validation\Validator $validator * @return mixed */ protected function failedValidation(Validator $validator) { throw new HttpResponseException($this->response( // 这里抛出了传说中的异常 $this->formatErrors($validator) )); }
Finally saw something unusual! But this exception was handled in another place:
IlluminateRoutingRoute:
<?php // ... /** * Run the route action and return the response. * * @param \Illuminate\Http\Request $request * @return mixed */ public function run(Request $request) { $this->container = $this->container ?: new Container; try { if (! is_string($this->action['uses'])) { return $this->runCallable($request); } if ($this->customDispatcherIsBound()) { return $this->runWithCustomDispatcher($request); } return $this->runController($request); } catch (HttpResponseException $e) { // 就是这里 return $e->getResponse(); // 这里直接返回了Response给客户端 } } // ...
At this point, the whole idea is clear, but let’s take a look at how the Response in the HttpResponseException generated here is generated:
IlluminateFoundationHttpFormRequest:
<?php // ... // 132行: if ($this->ajax() || $this->wantsJson()) { // 对AJAX请求的处理 return new JsonResponse($errors, 422); } return $this->redirector->to($this->getRedirectUrl()) // 对普通表单提交的处理 ->withInput($this->except($this->dontFlash)) ->withErrors($errors, $this->errorBag); // ...
I believe you understand everything.
How to implement custom error handling, here are two ideas, both of which need to rewrite the failedValidation of appHttpRequestsRequest:
Throw a new exception, inherit the HttpResponseException exception, and reimplement the getResponse method. We can put this exception class under app/Exceptions/ for easy management, and error returns are still handed over to Laravel;
Throws a custom exception and handles it in appExceptionshandler.
The specific implementation will not be written here (refer to the error handling section in the Laravel documentation, Chinese document portal). If you have other methods or ideas, you can communicate with me in the comments.
Supplement
If your Controller uses the validate method of the IlluminateFoundationValidationValidatesRequests Trait for verification, similarly, if the verification fails here, an IlluminateHttpExceptionHttpResponseException exception will be thrown. You can refer to the above solution for processing.