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特殊功能代码片段集合

需要一种有效的方法来避免使用 Laravel 5 重复代码片段

laravel中短信发送验证码的实现方法

Laravel:如何在控制器的几种方法中重用代码片段

17.)PHPWeb开发框架~Laravel中CSRF攻击原理讲解

自定义laravel表单请求验证类(FormRequest共用一个rules())