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里的邏輯,結果會被轉換成響應對象。然后響應對象會依次經過之前應用過的所有中間件的后置操作,最后返回給客戶端。

本文已經收錄在系列文章Laravel源碼學習里,歡迎訪問閱讀。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/254617.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/254617.shtml
英文地址,請注明出處:http://en.pswp.cn/news/254617.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C#枚舉、值、字符串的相互轉換

目錄枚舉的定義使用方式優點代碼示例枚舉的定義 枚舉是整數類型,用戶自定義的整數類型的一個集合。 使用方式 public enum A {a0,b1,c2 }注意:枚舉定義的不同變量之間要用“,”分割,結尾不需要加上“,” 優點 可以…

制作404頁面的重要性

在網站的運行過程中會面臨很多問題,當用戶搜索頁面時,會提示服務器出錯,請求的頁面不存在,程序配置錯誤等問題。用戶請求瀏覽網頁碰到這些的情況時,會自動跳出系統默認的錯誤提示,對用戶體驗造成不好的感觸…

明晰C++內存分配的五種方法的區別

在C中,內存分成5個區,他們分別是堆、棧、自由存儲區、全局/靜態存儲區和常量存儲區。 棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清楚的變量的存儲區。里面的變量通常是局部變量、函數參數等。 堆,就是那…

【BZOJ-4631】踩氣球 線段樹 + STL

4631: 踩氣球 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 224 Solved: 114[Submit][Status][Discuss]Description 六一兒童節到了, SHUXK 被迫陪著M個熊孩子玩一個無聊的游戲:有N個盒子從左到右排成一排,第i個盒子里裝著Ai個氣球。SH…

3D Reconstruction三維重建halcon算子,持續更新

目錄3D Reconstruction三維重建Binocular Stereo雙目立體binocular_disparitybinocular_disparity_mgbinocular_disparity_msbinocular_distancebinocular_distance_mgbinocular_distance_msdisparity_image_to_xyzdisparity_to_distancedisparity_to_point_3ddistance_to_disp…

遺傳算法初級

遺傳算法是一種基于仿生學的計算機算法,通過模擬自然進化和優勝劣汰法則來搜索問題的最優解(我會說這其實就是稍微改良了一下的暴搜?) 它是由美國的J.Holland于1975年提出來的玄學概率學混合暴力搜索方法,廣泛適用于尋找算法優解、機器學習、…

C++ vector容器類型

vector類為內置數組提供了一種替代表示&#xff0c;與string類一樣 vector 類是隨標準 C引入的標準庫的一部分 &#xff0c;為了使用vector 我們必須包含相關的頭文件 &#xff1a;#include <vector> 使用vector有兩種不同的形式&#xff0c;即所謂的數組習慣和 STL習慣…

redis在linux命令行下連續進行命令操作

redis-cli -a password -n 9 keys "friend*" -a 是auth -n 是選擇數據池 keys就是找key啦、 要是后面再跟上 xargs */redis-cli del redis-cli -a password -n 9 keys "friend*" | xargs redis-cli -a password -n 9 del 就完美了23333 轉載于:https://www…

Calibration校準halcon算子,持續更新

目錄Calibration校準Binocular雙目相機binocular_calibrationCalibration Object 校準物體caltab_pointscreate_caltabdisp_caltabfind_calib_objectfind_caltabfind_marks_and_posegen_caltabsim_caltabCamera parameter相機參數cam_mat_to_cam_parcam_par_to_cam_matdeserial…

javascript:正則表達式、一個表單驗證的例子

閱讀目錄 本文內容&#xff1a;正則表達式&#xff1a;利用正則表達式進行表單驗證的例子&#xff1a;回到頂部本文內容&#xff1a; 正則表達式正則表達式的使用方法正則表達式的特殊匹配字符正則表達式修飾符利用正則表達式進行表單驗證的例子首發日期&#xff1a;2018-05-13…

Spring_01 spring容器、控制反轉(IOC)、依賴注入(DI)

目錄 1 什么是spring框架 2 spring框架的特點 3 spring容器 3.1 什么是spring容器 3.2 spring容器創建對象的編程步驟 3.4 spring容器創建對象的方式 3.5 bean元素的幾個重要屬性 4 IOC 4.1 什么是IOC 4.2 什么事DI 4.3 DI的三種方式 1 什么是spring框架 是一個開源的用來簡化企…

EntityFramework 插件之EntityFramework.Extended (批量處理)

接手了一個用EF來做的項目&#xff0c;由于項目中使用的原生處理&#xff0c;導致很多update都是采用先select 后 update的方式來實現&#xff0c;同時無法批量執行邏輯如&#xff1a;根據訂單類型統一更新狀態等。所以在經過了N多查找之后 發現了一個國外寫的擴展插件EntityFr…

一個傳值的問題”*”與”*”

1/********************************************************* 2* Desc:參數傳遞&#xff1a;使用引用傳遞指針和直接傳遞指針地址的區別 3* Author:charley 4* DateTime:2010-12-7 11:00 02***********************************************************/ 03#include <…

Classification分類halcon算子,持續更新

目錄ClassificationGaussian Mixture Models高斯混合模型add_class_train_data_gmmadd_sample_class_gmmclassify_class_gmmclear_class_gmmclear_samples_class_gmmcreate_class_gmmdeserialize_class_gmmevaluate_class_gmmget_class_train_data_gmmget_params_class_gmmget_…

spring boot 擴展之AutoConfigurationImportListener

最近閱讀spring boot源碼時發現&#xff0c;發現當spring使用ConfigurationClassParser加載使用Configuration注解類后&#xff0c;會使用AutoConfigurationImportSelector對加載的 Configuration注解的類進行一次過濾。當AutoConfigurationImportSelector過濾完成后會自動加載…

classpath: spring 中的查找方式

Spring可以通過指定classpath*:與classpath:前綴加路徑的方式從classpath加載文件,如bean的定義文件.classpath*:的出現是為了從多個jar文件中加載相同的文件.classpath:只能加載找到的第一個文件. 比如 resource1.jar中的package com.test.rs 有一個 jarAppcontext.xml 文件,內…

《高效程序員的45個習慣》-之一

敏捷開發是當下最流行的開發方法&#xff0c;它采用的是一種以人為核心、迭代、循序漸進的開發思想&#xff0c;值得你關注和學習。 最近我就閱讀了一本有關敏捷開發的書籍&#xff0c;《高效程序員的45個習慣》。 它以“舉反例”的方式來講述了敏捷開發中程序員應該運用的…

教你如何在 elasticsearch 中重建索引

序言 Elasticsearch 是一個實時的分布式搜索分析引擎。Teambition 使用 Elastisearch 作為搜索引擎&#xff0c;為用戶提供搜索服務&#xff0c;當我們決定存儲某種數據時&#xff0c;我們需要使用PUT /teambition創建索引&#xff0c;在創建索引的時候需要將數據結構完整確定下…

halcon控制算子Control,持續更新

目錄Controlassignassign_atbreakcasecatchcommentcontinueconvert_tuple_to_vector_1dconvert_vector_to_tupledefaultelseelseifendforendifendswitchendtryendwhileexecutable_expressionexitexport_defforglobalififelseimportinsertpar_joinrepeatreturnstopswitchthrowtr…

《CLR via C#》之線程處理——線程基礎

《CLR via C#》之線程處理——線程基礎 《CLR via C#》之線程處理——線程基礎windows為什么要支持線程線程開銷CPU發展趨勢CLR線程和Windows線程使用專用線程執行異步的計算限制操作線程調度和優先級windows為什么要支持線程 早期的操作系統只有一個執行線程&#xff0c;但同時…