這篇文章主要介紹了關於Laravel控制器的解讀,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
控制器
控制器能夠將相關的請求處理邏輯組成一個單獨的類, 通過前面的路由和中介軟體兩個章節我們多次強調Laravel應用的請求在進入應用後首現會通過Http Kernel裡定義的基本中介軟體
protected $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,];
然後Http Kernel會通過dispatchToRoute
將請求對象移交給路由對象進行處理,路由對象會收集路由上綁定的中介軟體然後還是像上面Http Kernel裡一樣用一個Pipeline管道對象將請求傳送通過這些路由上綁定的這些中間鍵,到達目的地後會執行路由綁定的控制器方法然後把執行結果封裝成響應對象,響應對象一次通過後置中介軟體最後返回給用戶端。
下面是剛才說的這些步驟對應的核心代碼:
namespace Illuminate\Foundation\Http;class Kernel implements KernelContract{ protected function dispatchToRouter() { return function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); }; }}namespace Illuminate\Routing;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 Events\RouteMatched($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; //收集路由和控制器裡應用的中介軟體 $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 Illuminate\Routing;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(); } }}
我們在前面的文章裡已經詳細的解釋過Pipeline、中介軟體和路由的原理了,接下來就看看當請求最終找到了路由對應的控制器方法後Laravel是如何為控制器方法注入正確的參數並調用控制器方法的。
解析控制器和方法名
路由運行控制器方法的操作runController
首現會解析出路由中對應的控制器名稱和方法名稱。我們在講路由那一章裡說過路由對象的action屬性都是類似下面這樣的:
[ 'uses' => 'App\Http\Controllers\SomeController@someAction', 'controller' => 'App\Http\Controllers\SomeController@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{ //解析路由裡綁定的控制器方法字串,返回控制器和方法名稱字串構成的數組 public static function parseCallback($callback, $default = null) { return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default]; }}
所以路由通過parseCallback
方法將uses配置項裡的控制器字串解析成數組返回, 數組第一項為控制器名稱、第二項為方法名稱。在拿到控制器和方法的名稱字串後,路由對象將自身、控制器和方法名傳遞給了Illuminate\Routing\ControllerDispatcher
類,由ControllerDispatcher
來完成最終的控制器方法的調用。下面我們詳細看看ControllerDispatcher
是怎麼來調用控制器方法的。
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裡控制器的運行分為兩步:解決method的參數依賴resolveClassMethodDependencies
、調用控制器方法。
解決method參數依賴
解決方案的參數依賴通過RouteDependencyResolverTrait
這一trait
負責:
trait RouteDependencyResolverTrait{ protected function resolveClassMethodDependencies(array $parameters, $instance, $method) { if (! method_exists($instance, $method)) { return $parameters; } return $this->resolveMethodDependencies( $parameters, new ReflectionMethod($instance, $method) ); } //參數為路由參數數組$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; } }
在解決方案的參數依賴時會應用到PHP反射的ReflectionMethod
類來對控制器方法進行方向工程, 通過反射對象擷取到參數後會判斷現有參數的類型提示(type hint)是否是一個類對象參數,如果是類對象參數並且在現有參數中沒有相同類的對象那麼就會通過服務容器來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; })); }
解析出類對象後需要將類對象插入到參數列表中去
protected function spliceIntoParameters(array &$parameters, $offset, $value) { array_splice( $parameters, $offset, 0, [$value] ); }
我們之前講服務容器時,裡面講的服務解析解決是類構造方法的參數依賴,而這裡resolveClassMethodDependencies裡解決的是具體某個方法的參數依賴,它Laravel對method dependency injection概念的實現。
當路由的參數數組與服務容器構造的類對象數量之和不足以覆蓋控制器方法參數個數時,就要去判斷該參數是否具有預設參數,也就是會執行resolveMethodDependencies
方法foreach
塊裡的else if
分支將參數的預設參數插入到方法的參數列表$parameters
中去。
} elseif (! isset($values[$key - $instanceCount]) && $parameter->isDefaultValueAvailable()) { $this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());}
調用控制器方法
解決完method的參數依賴後就該調用方法了,這個很簡單, 如果控制器有callAction方法就會調用callAction方法,否則的話就直接調用方法。
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)); }
執行完拿到結果後,按照上面runRouteWithinStack
裡的邏輯,結果會被轉換成響應對象。然後響應對象會依次經過之前應用過的所有中介軟體的後置操作,最後返回給用戶端。
以上就是本文的全部內容,希望對大家的學習有所協助,更多相關內容請關注topic.alibabacloud.com!