laravel验证器实现原理
Posted halt丶dodo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了laravel验证器实现原理相关的知识,希望对你有一定的参考价值。
本文根据老司机带你深入laravel验证器(下)这篇文章总结而来。
由于TP用validate用习惯了,还没看laravel的验证器怎么用便直接去看这篇文章了,结果一头雾水,看完laravel的验证器之后再去看这篇文章,才恍然大悟。
本文从上一篇开始吧laravel控制器参数解析学习,来到这个控制器。
1 /** 2 * Store a newly created resource in storage. 3 * 4 * @param \\Illuminate\\Http\\Request $request 5 * @return \\Illuminate\\Http\\Response 6 */ 7 public function store(DebugFormPost $request) 8 { 9 // 10 }
在给这个方法添加DebugFormPost这个类型的参数之前需要运行命令 php artisan make:request DebugFormPost 。接下来进入到上篇说的解析控制器参数了。
1 /** 2 * Attempt to transform the given parameter into a class instance. 3 * 4 * @param \\ReflectionParameter $parameter 5 * @param array $parameters 6 * @return mixed 7 */ 8 protected function transformDependency(ReflectionParameter $parameter, $parameters) 9 { 10 $class = $parameter->getClass(); 11 12 // If the parameter has a type-hinted class, we will check to see if it is already in 13 // the list of parameters. If it is we will just skip it as it is probably a model 14 // binding and we do not want to mess with those; otherwise, we resolve it here. 15 if ($class && ! $this->alreadyInParameters($class->name, $parameters)) { 16 return $parameter->isDefaultValueAvailable() 17 ? $parameter->getDefaultValue() 18 : $this->container->make($class->name); 19 } 20 }
根据反射创建DebugFormPost的对象,进入容器去看这个make方法。
/** * Resolve the given type from the container. * * @param string $abstract * @param array $parameters * @return mixed * * @throws \\Illuminate\\Contracts\\Container\\BindingResolutionException */ public function make($abstract, array $parameters = []) { return $this->resolve($abstract, $parameters); }
$abstract实际就是我们刚刚通过命令创建的类名,进入resolve
1 protected function resolve($abstract, $parameters = [], $raiseEvents = true) 2 { 3 $abstract = $this->getAlias($abstract); 4 5 $needsContextualBuild = ! empty($parameters) || ! is_null( 6 $this->getContextualConcrete($abstract) 7 ); 8 9 if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { 10 return $this->instances[$abstract]; 11 } 12 13 $this->with[] = $parameters; 14 15 $concrete = $this->getConcrete($abstract); 16 17 if ($this->isBuildable($concrete, $abstract)) { 18 $object = $this->build($concrete); 19 } else { 20 $object = $this->make($concrete); 21 } 22 23 foreach ($this->getExtenders($abstract) as $extender) { 24 $object = $extender($object, $this); 25 } 26 27 if ($this->isShared($abstract) && ! $needsContextualBuild) { 28 $this->instances[$abstract] = $object; 29 } 30 31 if ($raiseEvents) { 32 $this->fireResolvingCallbacks($abstract, $object); 33 } 34 35 $this->resolved[$abstract] = true; 36 37 array_pop($this->with); 38 39 return $object; 40 }
注释已省略有兴趣可以看看源码,这里是解决依赖的,容器会自动创建这个类的对象,调用 $this->fireResolvingCallbacks($abstract,$object); 进入这个方法。
1 protected function fireResolvingCallbacks($abstract, $object) 2 { 3 $this->fireCallbackArray($object, $this->globalResolvingCallbacks); 4 5 $this->fireCallbackArray( 6 $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks) 7 ); 8 9 $this->fireAfterResolvingCallbacks($abstract, $object); 10 }
调用了两次$this->fireCallbackArray(),进入这个方法看一看,这里就不贴代码了,这段代码的意思是,执行所有之前服务提供者注册的回调,代码也很好懂,调用了两次这个方法,第一个还没具体分析,来看看第二个,进入 getCallbacksForType方法。
protected function getCallbacksForType($abstract, $object, array $callbacksPerType) { $results = []; foreach ($callbacksPerType as $type => $callbacks) { if ($type === $abstract || $object instanceof $type) { $results = array_merge($results, $callbacks); } } return $results; }
laravel引导的时候注册了很多回调,而要实例化这个类的回调是在什么时候注册的呢,这个类继承于FormRequest,进入这个类,这个类实现了ValidateWhenResolved这个接口,全局搜索ValidatesWhenResolved::class,路径:vendor/laravel/framework/src/Illuminate/Foundation/Providers/FormRequestServiceProvider.php。这个服务提供者里的boot方法。
public function boot() { $this->app->afterResolving(ValidatesWhenResolved::class, function ($resolved) { $resolved->validateResolved(); }); $this->app->resolving(FormRequest::class, function ($request, $app) { $request = FormRequest::createFrom($app[\'request\'], $request); $request->setContainer($app)->setRedirector($app->make(Redirector::class)); }); }
进入到afteResolvoing方法中,$app在是vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Application.php类对象,它继承了容器,所以回到哦容器看这个方法。
1 public function afterResolving($abstract, Closure $callback = null) 2 { 3 if (is_string($abstract)) { 4 $abstract = $this->getAlias($abstract); 5 } 6 7 if ($abstract instanceof Closure && is_null($callback)) { 8 $this->globalAfterResolvingCallbacks[] = $abstract; 9 } else { 10 $this->afterResolvingCallbacks[$abstract][] = $callback; 11 } 12 }
所以这个回调早在解析参数之前就早已注册了,然后回到getCallbacksForType方法,由于上面讲到继承关系,于是进入了if语句,执行了DebugFormPost这个类对象的validateResolved方法。
综上,所有的表单验证器如果采用以上方式创建而且继承了FormRequest类,那么都会自动调用这个方法。
进入到这个类的validateResolved这个方法,这个方法会直接调用父类ValidatesWhenResolvedTrait这个trait中的方法。
1 public function validateResolved() 2 { 3 $this->prepareForValidation(); 4 5 if (! $this->passesAuthorization()) { 6 $this->failedAuthorization(); 7 } 8 9 $instance = $this->getValidatorInstance(); 10 11 if ($instance->fails()) { 12 $this->failedValidation($instance); 13 } 14 15 $this->passedValidation(); 16 }
$this->prepareForValidation(); 将会进行一些准备工作,并没有发现trait和这个类对这个方法进行了什么操作,接着就是下一步, $this->passesAuthorization() ,
protected function passesAuthorization() { if (method_exists($this, \'authorize\')) { return $this->authorize(); } return true; }
如果当前对象存在这个方法会直接去调用,这不就是文档上说的吗,进入 $this->failedValidation($instance); 直接抛出异常了,先不管这个,接着实例化了一个对象,这个 getValidatorInstance() 在FormRequest中。
1 protected function getValidatorInstance() 2 { 3 if ($this->validator) { 4 return $this->validator; 5 } 6 7 $factory = $this->container->make(ValidationFactory::class); 8 9 if (method_exists($this, \'validator\')) { 10 $validator = $this->container->call([$this, \'validator\'], compact(\'factory\')); 11 } else { 12 $validator = $this->createDefaultValidator($factory); 13 } 14 15 if (method_exists($this, \'withValidator\')) { 16 $this->withValidator($validator); 17 } 18 19 $this->setValidator($validator); 20 21 return $this->validator; 22 }
这里暂时还没找到validator这个方法,直接看啊可能 $this->createDefaultValidator($factory); 进入这个方法,通过验证类工厂创建一个对象,这个类工厂对象是容器创建了,全局搜索一下 ValidationFactory::class ,然而发现什么都没有,回到命名空间,应该搜索 Illuminate\\Contracts\\Validation\\Factory ,于是找到契约对应的类在哪了。
Illuminate/Validation/Factory.php 进入make方法
1 public function make(array $data, array $rules, array $messages = [], array $customAttributes = []) 2 { 3 $validator = $this->resolve( 4 $data, $rules, $messages, $customAttributes 5 ); 6 7 if (! is_null($this->verifier)) { 8 $validator->setPresenceVerifier($this->verifier); 9 } 10 11 if (! is_null($this->container)) { 12 $validator->setContainer($this->container); 13 } 14 15 $this->addExtensions($validator); 16 17 return $validator; 18 }
只有第一句有用进入这个方法。
1 protected function resolve(array $data, array $rules, array $messages, array $customAttributes) 2 { 3 if (is_null($this->resolver)) { 4 return new Validator($this->translator, $data, $rules, $messages, $customAttributes); 5 } 6 7 return call_user_func($this->resolver, $this->translator, $data, $rules, $messages, $customAttributes); 8 }
找到了,这个对象即为 Illuminate\\Validation\\Validator.php 下的对象,回到FormRequest这个类,它调用了withValidator, 这个方法又在文档里看见了,接着返回这个对象,调用了fails方法,fails返回的是passes()方法相反的结果,进入这个方法,开始验证了,这里就不深入分析了,在 \\Illuminate\\Validation\\ValidatesWhenResolvedTrait.php 如果验证失败,则会抛出异常。之前权限验证没有通过也会抛出异常,这些异常都会被捕获,全局搜索一下ValidationException,能发现在 Illuminate\\Foundation\\Exceptions\\Handler.php 这里。
1 public function render($request, Exception $e) 2 { 3 if (method_exists($e, \'render\') && $response = $e->render($request)) { 4 return Router::toResponse($request, $response); 5 } elseif ($e instanceof Responsable) { 6 return $e->toResponse($request); 7 } 8 9 $e = $this->prepareException($e); 10 11 if ($e instanceof HttpResponseException) { 12 return $e->getResponse(); 13 } elseif ($e instanceof AuthenticationException) { 14 return $this->unauthenticated($request, $e); 15 } elseif ($e instanceof ValidationException) { 16 return $this->convertValidationExceptionToResponse($e, $request); 17 } 18 19 return $request->expectsJson() 20 ? $this->prepareJsonResponse($request, $e) 21 : $this->prepareResponse($request, $e); 22 }
同时也看见之前权限异常的处理也在这里,来看验证错误是如何处理的吧
1 protected function convertValidationExceptionToResponse(ValidationException $e, $request) 2 { 3 if ($e->response) { 4 return $e->response; 5 } 6 7 return $request->expectsJson() 8 ? $this->invalidJson($request, $e) 9 : $this->invalid($request, $e); 10 }
·这里返回响应,同时更具返回类型来确定是普通响应还是json响应,而验证错误信息则在验证错误的时候就已经被设置了。
以上是关于laravel验证器实现原理的主要内容,如果未能解决你的问题,请参考以下文章
需要一种有效的方法来避免使用 Laravel 5 重复代码片段