当前位置: 首页 > 后端技术 > PHP

Laravel核心解读--Controller

时间:2023-03-29 16:29:27 PHP

Controller控制器可以将相关的请求处理逻辑组合成一个单独的类。通过前面路由和中间件两章,我们反复强调了Laravel应用的request首先出现在进入应用之后。受保护的$middleware=[\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,\App\Http\Middleware\TrimStrings::class,\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,\App\Http\Middleware\TrustProxies::class,];然后HttpKernel会通过dispatchToRoute将请求对象交给路由对象处理,路由对象会收集路由上绑定的中间件,然后使用一个Pipeline管道对象通过这些路由上绑定的中间键来传递请求作为在上面的HttpKernel中,到达目的地后执行路由绑定的controller方法,然后将执行结果封装成response对象,response对象经过一次后置中间件,最后返回给客户端。undefined返回$this->prepareResponse($request,$this->runRouteWithinStack($route,$request));}protectedfunctionrunRouteWithinStack(Route$route,Request$request){$shouldSkipMiddleware=$this->container->bound('middleware.disable')&&$this->container->make('middleware.disable')===真;//收集路径由和控制器里应用的中间件$middleware=$shouldSkipMiddleware?[]:$this->gatherRouteMiddleware($route);return(newPipeline($this->container))->send($request)->through($middleware)->then(function($request)使用($route){return$this->prepareResponse($request,$route->run());});}}namespaceIlluminate\Routing;classRoute{publicfunctionrun(){$this->container=$this->container?:newContainer;尝试{if($this->isControllerAction()){返回$this->runController();}返回$this->runCallable();}catch(HttpResponseException$e){返回$e->getResponse();}}}Pipeline、中间件和路由的原理我们在上一篇文章中已经详细讲解过了。接下来我们看看请求是什么时候终于找到路由对应的controller的。方法之后,Laravel如何将正确的参数注入到控制器方法中,并调用控制器方法的解析以及控制器和方法的名称。路由运行控制器方法的操作。runController会先解析出路由中对应的控制器名和方法名。我们在路由一章说过,路由对象的action属性类似如下:['uses'=>'App\Http\Controllers\SomeController@someAction','controller'=>'App\Http\Controllers\SomeController@someAction','middleware'=>...]classRoute{protectedfunctionisControllerAction(){returnis_string($this->action['uses']);}protectedfunctionrunController(){return(newControllerDispatcher($this->container))->dispatch($this,$this->getController(),$this->getControllerMethod());}publicfunctiongetController(){if(!$this->controller){$class=$this->parseControllerCallback()[0];$this->controller=$this->container->make(ltrim($class,'\\'));}返回$this->控制器;}protectedfunctiongetControllerMethod(){return$this->parseControllerCallback()[1];}保护函数parseControllerCallback(){返回Str::parseCallback($this->action['uses']);}}classStr{//解析路由中绑定的控制器方法字符串,返回publicstaticfunctionparseCallback($callback,$default=null)的控制器和方法名字符串数组{returnstatic::contains($callback,'@')?explode('@',$callback,2):[$callback,$default];}}所以路由使用parseCallback方法将uses配置项中的controller字符串解析成数组返回。数组中的第一项是控制器名称,第二项是方法名称。路由对象在获取到控制器和方法的名称字符串后,将自身、控制器和方法名传递给Illuminate\Routing\ControllerDispatcher类,由ControllerDispatcher完成最终的控制器方法调用。让我们仔细看看ControllerDispatcher是如何调用控制器方法的。类ControllerDispatcher{使用RouteDependencyResolverTrait;publicfunctiondispatch(Route$route,$controller,$method){$parameters=$this->resolveClassMethodDependencies($route->parametersWithoutNulls(),$controller,$method);}如果(method_exists($controller,'callAction')){return$controller->callAction($method,$parameters);}返回$controller->{$method}(...array_values($parameters));}}上面可以很清楚的看出ControllerDispatcher中controller的操作分为两步:解析方法的参数依赖于resolveClassMethodDependencies,调用controller方法。解析方法参数依赖解析方法的参数依赖由routeDependencyResolverTraittrait负责:traitRouteDependencyResolverTrait{protectedfunctionresolveClassMethodDependencies(array$parameters,$instance,$method){if(!method_exists($instance,$method)){return$参数;}返回$this->resolveMethodDependencies($parameters,newReflectionMethod($instance,$method));}//参数为路由参数数组$parameters(可以为空数组)和控制器方法的反射对象publicfunctionresolveMethodDependencies(array$parameters,ReflectionFunctionAbstract$reflector){$instanceCount=0;$values=array_values($parameters);foreach($reflector->getParameters()as$key=>$parameter){$instance=$this->transformDependency($parameter,$parameters);}如果(!is_null($instance)){$instanceCount++;$this->spliceIntoParameters($pa参数,$key,$instance);}elseif(!isset($values[$key-$instanceCount])&&$parameter->isDefaultValueAvailable()){$this->spliceIntoParameters($parameters,$key,$parameter->getDefaultValue());}}返回$参数;在解决方法的参数依赖时,会应用到PHP反射的ReflectionMethod类中,对controller方法进行方向工程。通过反射对象获取到参数后,会判断现有参数是否是类型提示(typehint)是否为类对象参数,如果是类对象参数且现有参数中不存在同类对象,然后将通过服务容器创建类对象受保护的函数transformDependency(ReflectionParameter$parameter,$parameters){$class=$parameter->getClass();如果($class&&!$this->alreadyInParameters($class->name,$parameters)){return$parameter->isDefaultValueAvailable()?$parameter->getDefaultValue():$this->container->make($class->name);}}protectedfunctionalreadyInParameters($class,array$parameters){返回!is_null(Arr::first($parameters,function($value)use($class){return$valueinstanceof$class;}));}解析出类对象后,需要将类对象插入到参数列表中进行保护functionspliceIntoParameters(array&$parameters,$offset,$value){array_splice($parameters,$offset,0,[$value]);之前我们讲服务容器的时候,里面提到的服务解析解决方案就是类构造方法的参数依赖,而这里的resolveClassMethodDependencies是解决具体方法的参数依赖,也就是Laravel对方法依赖概念的实现注射。当路由参数数组和服务容器构造的类对象个数之和不足以覆盖controller方法参数个数时,需要判断参数是否有默认参数,即elseif分支在resolveMethodDependencies方法的foreach块中,将执行参数的默认参数插入到方法的参数列表$parameters中。}elseif(!isset($values[$key-$instanceCount])&&$parameter->isDefaultValueAvailable()){$this->spliceIntoParameters($parameters,$key,$parameter->getDefaultValue());}调用控制controller方法解析完方法的参数依赖后,就可以调用方法了。这很简单。如果controller有callAction方法,则调用callAction方法,否则直接调用该方法。publicfunctiondispatch(Route$route,$controller,$method){$parameters=$this->resolveClassMethodDependencies($route->parametersWithoutNulls(),$controller,$method);}如果(method_exists($controller,'callAction')){return$controller->callAction($method,$parameters);}返回$controller->{$method}(...array_values($parameters));逻辑,结果将被转换成一个响应对象。然后响应对象会经过之前应用的所有中间件的后期操作,最后返回给客户端。本文已收录在Laravel源码学习系列文章中,欢迎访问阅读。