Laravel框架中對控制器的深入解析

這篇文章給大家分享的內(nèi)容是關(guān)于laravel框架中對控制器的深入解析,內(nèi)容很詳細(xì),有一定的參考價(jià)值,希望可以幫助到有需要的朋友。

控制器能夠?qū)⑾嚓P(guān)的請求處理邏輯組成一個(gè)單獨(dú)的類, 通過前面的路由和中間件兩個(gè)章節(jié)我們多次強(qiáng)調(diào)laravel應(yīng)用的請求在進(jìn)入應(yīng)用后首現(xiàn)會(huì)通過Http Kernel里定義的基本中間件

protected $middleware = [     IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::class,     IlluminateFoundationHttpMiddlewareValidatePostSize::class,     AppHttpMiddlewareTrimStrings::class,     IlluminateFoundationHttpMiddlewareConvertEmptyStringsToNull::class,     AppHttpMiddlewareTrustProxies::class, ];

然后Http Kernel會(huì)通過dispatchToRoute將請求對象移交給路由對象進(jìn)行處理,路由對象會(huì)收集路由上綁定的中間件然后還是像上面Http Kernel里一樣用一個(gè)Pipeline管道對象將請求傳送通過這些路由上綁定的這些中間鍵,到達(dá)目的地后會(huì)執(zhí)行路由綁定的控制器方法然后把執(zhí)行結(jié)果封裝成響應(yīng)對象,響應(yīng)對象一次通過后置中間件最后返回給客戶端。

下面是剛才說的這些步驟對應(yīng)的核心代碼:

namespace IlluminateFoundationHttp; class Kernel implements KernelContract {     protected function dispatchToRouter()     {         return function ($request) {             $this->app->instance('request', $request);              return $this->router->dispatch($request);         };     } }   namespace IlluminateRouting; class Router implements RegistrarContract, BindingRegistrar {         public function dispatch(Request $request)     {         $this->currentRequest = $request;          return $this->dispatchToRoute($request);     }          public function dispatchToRoute(Request $request)     {         return $this->runRoute($request, $this->findRoute($request));     }          protected function runRoute(Request $request, Route $route)     {         $request->setRouteResolver(function () use ($route) {             return $route;         });          $this->events->dispatch(new EventsRouteMatched($route, $request));          return $this->prepareResponse($request,             $this->runRouteWithinStack($route, $request)         );     }          protected function runRouteWithinStack(Route $route, Request $request)     {         $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&                             $this->container->make('middleware.disable') === true;         //收集路由和控制器里應(yīng)用的中間件         $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);          return (new Pipeline($this->container))                     ->send($request)                     ->through($middleware)                     ->then(function ($request) use ($route) {                         return $this->prepareResponse(                             $request, $route->run()                         );                     });          } }  namespace IlluminateRouting; class Route {     public function run()     {         $this->container = $this->container ?: new Container;         try {             if ($this->isControllerAction()) {                 return $this->runController();             }             return $this->runCallable();         } catch (HttpResponseException $e) {             return $e->getResponse();         }     }  }

我們在前面的文章里已經(jīng)詳細(xì)的解釋過Pipeline、中間件和路由的原理了,接下來就看看當(dāng)請求最終找到了路由對應(yīng)的控制器方法后Laravel是如何為控制器方法注入正確的參數(shù)并調(diào)用控制器方法的。

解析控制器和方法名

路由運(yùn)行控制器方法的操作runController首現(xiàn)會(huì)解析出路由中對應(yīng)的控制器名稱和方法名稱。我們在講路由那一章里說過路由對象的action屬性都是類似下面這樣的:

[     'uses' => 'AppHttpControllersSomeController@someAction',     'controller' => 'AppHttpControllersSomeController@someAction',     'middleware' => ... ]
class Route {     protected function isControllerAction()     {         return is_string($this->action['uses']);     }      protected function runController()     {         return (new ControllerDispatcher($this->container))->dispatch(             $this, $this->getController(), $this->getControllerMethod()         );     }          public function getController()     {         if (! $this->controller) {             $class = $this->parseControllerCallback()[0];              $this->controller = $this->container->make(ltrim($class, ''));         }          return $this->controller;     }          protected function getControllerMethod()     {         return $this->parseControllerCallback()[1];     }          protected function parseControllerCallback()     {         return Str::parseCallback($this->action['uses']);     } }  class Str {     //解析路由里綁定的控制器方法字符串,返回控制器和方法名稱字符串構(gòu)成的數(shù)組     public static function parseCallback($callback, $default = null)     {         return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];     } }

所以路由通過parseCallback方法將uses配置項(xiàng)里的控制器字符串解析成數(shù)組返回, 數(shù)組第一項(xiàng)為控制器名稱、第二項(xiàng)為方法名稱。在拿到控制器和方法的名稱字符串后,路由對象將自身、控制器和方法名傳遞給了IlluminateRoutingControllerDispatcher類,由ControllerDispatcher來完成最終的控制器方法的調(diào)用。下面我們詳細(xì)看看ControllerDispatcher是怎么來調(diào)用控制器方法的。

class ControllerDispatcher {     use RouteDependencyResolverTrait;      public function dispatch(Route $route, $controller, $method)     {         $parameters = $this->resolveClassMethodDependencies(             $route->parametersWithoutNulls(), $controller, $method         );          if (method_exists($controller, 'callAction')) {             return $controller->callAction($method, $parameters);         }          return $controller->{$method}(...array_values($parameters));     } }

上面可以很清晰地看出,ControllerDispatcher里控制器的運(yùn)行分為兩步:解決method的參數(shù)依賴resolveClassMethodDependencies、調(diào)用控制器方法。

解決method參數(shù)依賴

解決方法的參數(shù)依賴通過RouteDependencyResolverTrait這一trait負(fù)責(zé):

trait RouteDependencyResolverTrait {     protected function resolveClassMethodDependencies(array $parameters, $instance, $method)     {         if (! method_exists($instance, $method)) {             return $parameters;         }                           return $this->resolveMethodDependencies(             $parameters, new ReflectionMethod($instance, $method)         );     }      //參數(shù)為路由參數(shù)數(shù)組$parameters(可為空array)和控制器方法的反射對象     public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector)     {         $instanceCount = 0;          $values = array_values($parameters);          foreach ($reflector->getParameters() as $key => $parameter) {             $instance = $this->transformDependency(                 $parameter, $parameters             );              if (! is_null($instance)) {                 $instanceCount++;                  $this->spliceIntoParameters($parameters, $key, $instance);             } elseif (! isset($values[$key - $instanceCount]) &&                       $parameter->isDefaultValueAvailable()) {                 $this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());             }         }          return $parameters;     }      }

在解決方法的參數(shù)依賴時(shí)會(huì)應(yīng)用到PHP反射的ReflectionMethod類來對控制器方法進(jìn)行方向工程, 通過反射對象獲取到參數(shù)后會(huì)判斷現(xiàn)有參數(shù)的類型提示(type hint)是否是一個(gè)類對象參數(shù),如果是類對象參數(shù)并且在現(xiàn)有參數(shù)中沒有相同類的對象那么就會(huì)通過服務(wù)容器來make出類對象。

    protected function transformDependency(ReflectionParameter $parameter, $parameters)     {         $class = $parameter->getClass();         if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {             return $parameter->isDefaultValueAvailable()                 ? $parameter->getDefaultValue()                 : $this->container->make($class->name);         }     }          protected function alreadyInParameters($class, array $parameters)     {         return ! is_null(Arr::first($parameters, function ($value) use ($class) {             return $value instanceof $class;         }));     }

解析出類對象后需要將類對象插入到參數(shù)列表中去

    protected function spliceIntoParameters(array &$parameters, $offset, $value)     {         array_splice(             $parameters, $offset, 0, [$value]         );     }

我們之前講服務(wù)容器時(shí),里面講的服務(wù)解析解決是類構(gòu)造方法的參數(shù)依賴,而這里resolveClassMethodDependencies里解決的是具體某個(gè)方法的參數(shù)依賴,它Laravel對method dependency injection概念的實(shí)現(xiàn)。

當(dāng)路由的參數(shù)數(shù)組與服務(wù)容器構(gòu)造的類對象數(shù)量之和不足以覆蓋控制器方法參數(shù)個(gè)數(shù)時(shí),就要去判斷該參數(shù)是否具有默認(rèn)參數(shù),也就是會(huì)執(zhí)行resolveMethodDependencies方法foreach塊里的else if分支將參數(shù)的默認(rèn)參數(shù)插入到方法的參數(shù)列表$parameters中去。

} elseif (! isset($values[$key - $instanceCount]) &&     $parameter->isDefaultValueAvailable()) {     $this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue()); }

調(diào)用控制器方法

解決完method的參數(shù)依賴后就該調(diào)用方法了,這個(gè)很簡單, 如果控制器有callAction方法就會(huì)調(diào)用callAction方法,否則的話就直接調(diào)用方法。

    public function dispatch(Route $route, $controller, $method)     {         $parameters = $this->resolveClassMethodDependencies(             $route->parametersWithoutNulls(), $controller, $method         );          if (method_exists($controller, 'callAction')) {             return $controller->callAction($method, $parameters);         }          return $controller->{$method}(...array_values($parameters));     }

執(zhí)行完拿到結(jié)果后,按照上面runRouteWithinStack里的邏輯,結(jié)果會(huì)被轉(zhuǎn)換成響應(yīng)對象。然后響應(yīng)對象會(huì)依次經(jīng)過之前應(yīng)用過的所有中間件的后置操作,最后返回給客戶端。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊8 分享