Laravel控制器的解讀

來源:互聯網
上載者:User
這篇文章主要介紹了關於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!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.