[TOC]
# 說明
接上一篇繼續分析。控制器操作的代碼實際上都包含在`$this->dispatchToRoute($request)`中,展開來看:
```
protected function dispatchToRoute($request)
{
$withRoute = $this->app->config->get('app.with_route', true) ? function () {
$this->loadRoutes();
} : null;
return $this->app->route->dispatch($request, $withRoute);
}
```
上一節我們跳過路由,分析了URL解析,下面接著分析控制器和操作的解析。
`dispatch`方法:
```
public function dispatch(Request $request, $withRoute = null)
{
$this->request = $request;
$this->host = $this->request->host(true);
$this->init();
if ($withRoute) {
//加載路由
$withRoute();
$dispatch = $this->check();
} else {
$dispatch = $this->url($this->path());
}
$dispatch->init($this->app);
return $this->app->middleware->pipeline('route')
->send($request)
->then(function () use ($dispatch) {
return $dispatch->run();
});
}
```
重點關注后面的`return`語句,這里又給控制器操作的代碼包裹上了中間件閉包,其原理也跟前面分析的全局中間件原理一樣,不再贅述。重點的重點關注`run`方法。
# 控制器類的 run 方法分析
`run`方法代碼如下:
```
public function run(): Response
{
// HTTP的OPTIONS方法用于獲取目的資源所支持的通信選項。
if ($this->rule instanceof RuleItem && $this->request->method() == 'OPTIONS' && $this->rule->isAutoOptions()) {
$rules = $this->rule->getRouter()->getRule($this->rule->getRule());
$allow = [];
foreach ($rules as $item) {
$allow[] = strtoupper($item->getMethod());
}
return Response::create('', '', 204)->header(['Allow' => implode(', ', $allow)]);
}
//這里的exec方法位于Controller類
$data = $this->exec();
// 控制器操作返回的數據,進一步加工,設置Http報頭、狀態碼等,返回一個Response對象
return $this->autoResponse($data);
}
```
分析詳見注釋。主要的邏輯在`$data = $this->exec()`,其中,注意`exec`方法位于`think\route\dispatch\Controller`類,其代碼如下:
```
public function exec()
{
try {
// 實例化控制器
$instance = $this->controller($this->controller);
} catch (ClassNotFoundException $e) {
throw new HttpException(404, 'controller not exists:' . $e->getClass());
}
//A 注冊控制器中間件
$this->registerControllerMiddleware($instance);
// 這里跟前面全局中間件原理一樣,不再分析
return $this->app->middleware->pipeline('controller')
->send($this->request)
->then(function () use ($instance) {
// 獲取當前操作名
$action = $this->actionName . $this->rule->config('action_suffix');
if (is_callable([$instance, $action])) {
$vars = $this->request->param();
try {
$reflect = new ReflectionMethod($instance, $action);
// 嚴格獲取當前操作方法名
$actionName = $reflect->getName();
$this->request->setAction($actionName);
} catch (ReflectionException $e) {
$reflect = new ReflectionMethod($instance, '__call');
$vars = [$action, $vars];
$this->request->setAction($action);
}
} else {
// 操作不存在
throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
}
// 控制器操作的執行在這里
$data = $this->app->invokeReflectMethod($instance, $reflect, $vars);
return $this->autoResponse($data);
});
}
```
詳細分析見代碼注釋,以下再展開分析控制器的實例化和控制器中間件的注冊。
## A 控制器的實例化
執行實例化的`think\route\dispatch\Controller`類的`controller`方法代碼如下:
```
public function controller(string $name)
{
// 是否使用控制器后綴
$suffix = $this->rule->config('controller_suffix') ? 'Controller' : '';
// 訪問控制器層名稱
$controllerLayer = $this->rule->config('controller_layer') ?: 'controller';
// 空控制器名稱
$emptyController = $this->rule->config('empty_controller') ?: 'Error';
//獲取控制器完整的類名
$class = $this->app->parseClass($controllerLayer, $name . $suffix);
// 如果這個類存在
if (class_exists($class)) {
//通過容器獲取實例(非單例模式)
return $this->app->make($class, [], true);
//不存在時,如果有空控制器的類存在
} elseif ($emptyController && class_exists($emptyClass = $this->app->parseClass($controllerLayer, $emptyController . $suffix))) {
//同理,實例化空控制器
return $this->app->make($emptyClass, [], true);
}
// 如果找不到控制器的類,且連控控制器也沒有,拋出錯誤
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
```
具體分析見注釋。
## B 控制器中間件注冊
執行注冊的`registerControllerMiddleware`方法代碼如下:
```
protected function registerControllerMiddleware($controller): void
{
// 獲取反射類對象
$class = new ReflectionClass($controller);
// 檢查控制器類是否有middleware屬性
if ($class->hasProperty('middleware')) {
//提取middleware變量
$reflectionProperty = $class->getProperty('middleware');
//設置可見性為公有
$reflectionProperty->setAccessible(true);
//獲取middleware屬性的值
$middlewares = $reflectionProperty->getValue($controller);
//解析控制器中間件配置
foreach ($middlewares as $key => $val) {
if (!is_int($key)) {
//如果有設置only屬性
//$this->request->action(true)獲取當前操作名并轉為小寫
//$val['only']各元素也轉為小寫,然后判斷當前操作是否在$val['only']里面
//不在則跳過(說明該操作不需要執行該中間件)
if (isset($val['only']) && !in_array($this->request->action(true), array_map(function ($item) {
return strtolower($item);
}, is_string($val['only']) ? explode(",", $val['only']) : $val['only']))) {
continue;
//如果有設置except屬性,且當前操作在$val['except']里面,說明當前操作不需要該中間件,跳過
} elseif (isset($val['except']) && in_array($this->request->action(true), array_map(function ($item) {
return strtolower($item);
}, is_string($val['except']) ? explode(',', $val['except']) : $val['except']))) {
continue;
} else {
//保存中間件名稱或者類
$val = $key;
}
}
if (is_string($val) && strpos($val, ':')) {\
$val = explode(':', $val, 2);\
}
//注冊控制器中間件,跟前面注冊路由中間件一樣原理,只是,中間件的type為controller
$this->app->middleware->controller($val);
}
}
}
```
分析詳見注釋。
# 總結
長路漫漫,到這里,終于分析完了`runWithRequest`方法,前面的分析,基本是圍繞著`runWithRequest`方法展開。現在,讓我們將目光轉回Http類的`run`方法:
```
public function run(Request $request = null): Response
{
.
.
.
try {
$response = $this->runWithRequest($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
// 設置cookie并返回響應對象
return $response;
}
```
`runWithRequest`方法跑完,`run`方法也差不多結束了,最終它返回一個`Response`對象。