Skip to main content

控制器与方法的运行

简介

历经 7 章的漫长路途,我们终于走完了路由中间件,总结一下:

[
// 加解密 Cookies
'App\Http\Middleware\EncryptCookies',
// 将队列中的 Cookies 添加到 响应 里
'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
// 开启服务端会话
'Illuminate\Session\Middleware\StartSession',
// 将储存在 session 中的错误加载到 view 视图中
'Illuminate\View\Middleware\ShareErrorsFromSession',
// 验证 CSRF
'App\Http\Middleware\VerifyCsrfToken',
// 从请求参数中获取实际模型对象,直接绑定到控制器方法中
'Illuminate\Routing\Middleware\SubstituteBindings',
// 请求登录、注册、忘记密码界面时,如果登录了,重定向到首页
'App\Http\Middleware\RedirectIfAuthenticated',
]

从本章开始,我们就正式进入大家比较关心的地方,就是我们定义的控制器方法,到底是怎么运行的??

路由中间件运行完了,控制器方法运行的入口位置

Illuminate\Routing\Router

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(
// 在这里!!,$route->run() 就是进入控制器方法运行的地方。
$request, $route->run()
);
});
}

run

Illuminate\Routing\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();
}
}

为什么 $this->container 已经赋值过

如下一套路由定义的例子:

web.php

Route::get('/', function () {
return view('welcome');
});

Route::get('/home', 'HomeController@index')->name('home');

Route::get('/test', function () {
return config('first.second.test.key');
});

Route::post('/test', 'TestController@postTest');

Route::put('/update', 'TestController@update');

Route::delete('/delete', 'TestController@delete');

路由中定义的 get、post、put、delete、any 等方法实际执行的位置如下:

Illuminate\Routing\Router

public function get($uri, $action = null)
{
return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}

public function post($uri, $action = null)
{
return $this->addRoute('POST', $uri, $action);
}

public function put($uri, $action = null)
{
return $this->addRoute('PUT', $uri, $action);
}

public function patch($uri, $action = null)
{
return $this->addRoute('PATCH', $uri, $action);
}

public function delete($uri, $action = null)
{
return $this->addRoute('DELETE', $uri, $action);
}

public function options($uri, $action = null)
{
return $this->addRoute('OPTIONS', $uri, $action);
}

public function any($uri, $action = null)
{
return $this->addRoute(self::$verbs, $uri, $action);
}

共同点是都执行了 addRoute 方法

Illuminate\Routing\Router

public function addRoute($methods, $uri, $action)
{
// createRoute 是重点
return $this->routes->add($this->createRoute($methods, $uri, $action));
}

着重看 createRoute 方法

Illuminate\Routing\Router

protected function createRoute($methods, $uri, $action)
{
if ($this->actionReferencesController($action)) {
$action = $this->convertToControllerAction($action);
}

// newRoute 方法是重点
$route = $this->newRoute(
$methods, $this->prefix($uri), $action
);

if ($this->hasGroupStack()) {
$this->mergeGroupAttributesIntoRoute($route);
}

$this->addWhereClausesToRoute($route);

return $route;
}

接下来我们来看 newRoute

Illuminate\Routing\Router

protected function newRoute($methods, $uri, $action)
{
return (new Route($methods, $uri, $action))
->setRouter($this)
// 在这里,嘿嘿
->setContainer($this->container);
}

看到 setContainer 方法了吗,我们就进入 Route 类中看看这个 setContainer 方法

Illuminate\Routing\Route

public function setContainer(Container $container)
{
$this->container = $container;

return $this;
}

看到没, $this->container = $container; ,所以说 $this->container 已经被赋值过。

路由里面 什么是控制器与方法运行;什么是闭包运行

  • 控制器与方法运行

    Route::get('/home', 'HomeController@index')->name('home');

    这类定义,就属于控制器与方法运行

  • 闭包运行

    Route::get('/', function () {
    return view('welcome');
    });

    这类定义,就属于闭包运行

那么 控制器与方法运行和闭包运行如何区分的呢

Illuminate\Routing\Route

public function run()
{
$this->container = $this->container ?: new Container;

try {
// isControllerAction 就是区分的判断
if ($this->isControllerAction()) {
return $this->runController();
}

return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}

那我们来看一下 isControllerAction 方法

protected function isControllerAction()
{
return is_string($this->action['uses']);
}

$this->action['uses'],是什么,我们看一下调试截图,就清楚了

Laravel

哦,我知道了,如果路由定义是控制器与方法,那么格式就是 控制器@方法 字符串;如果路由定义是闭包,那么格式就是闭包对象,而非字符串。故而区分出控制器与方法定义和闭包定义。

控制器与方法运行

由于教程请求的路由,是控制器与方法,故我们重点来看控制器与方法运行原理

Illuminate\Routing\Route

protected function runController()
{
return $this->controllerDispatcher()->dispatch(
// $this:当前 Route 对象
// $this->getController():控制器实例化的对象(控制器@方法,实例化@前面的控制器)
// $this->getControllerMethod():路由定义时的方法(控制器@方法,获取@后面的方法名称)
$this, $this->getController(), $this->getControllerMethod()
);
}

$this->controllerDispatcher() 返回控制器派遣管理对象

我们看一下控制器派遣管理对象的 dispatch 方法

Illuminate\Routing\ControllerDispatcher

public function dispatch(Route $route, $controller, $method)
{
// 从控制器方法的参数上,返回所以依赖的服务对象
$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);

// 如果我们定义的控制器中有 callAction 方法,优先执行 callAction 方法。
if (method_exists($controller, 'callAction')) {
return $controller->callAction($method, $parameters);
}

// !!!核心!!!,我们定义的控制器和方法终于运行啦,哈哈哈
return $controller->{$method}(...array_values($parameters));
}

方法参数依赖注入的实现

在 dispatch 方法上,我们看到这么一段代码

$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);

这段代码,就是实现 方法参数依赖注入 的源码,其原理就是利用 PHP 反射类机制,获取类方法参数依赖的类,然后获取依赖类的实例,最后在调用类方法时,将这个实例以实参的方式传入类方法中,使其正确运行。

那我们看一下 resolveClassMethodDependencies 方法的源码吧

Illuminate\Routing\RouteDependencyResolverTrait

/**
* @parameters:前端请求时,传过来的参数对
* @instance:我们自己定义的控制器实例
* @method:我们自己定义的控制器方法名称
*/
protected function resolveClassMethodDependencies(array $parameters, $instance, $method)
{
// 如果我们定义的控制器里面没有路由定义时的方法,直接返回请求的参数对
if (! method_exists($instance, $method)) {
return $parameters;
}

return $this->resolveMethodDependencies(
// 实例化 PHP 内置的反射类 ReflectionMethod
$parameters, new ReflectionMethod($instance, $method)
);
}

继续,我们看一下 resolveMethodDependencies 方法

Illuminate\Routing\RouteDependencyResolverTrait

public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector)
{
// 方法形参计数
$instanceCount = 0;

// 直接从请求的参数对中,获取参数值数组
$values = array_values($parameters);

// for 循环方法参数的反射类数组,每个 $parameter 代表一个参数的反射对象
foreach ($reflector->getParameters() as $key => $parameter) {
// 重点在这里,$parameter 对象里面存储了参数依赖的类全名,通过实例化这个类全名,我们得到了依赖的对象
$instance = $this->transformDependency(
$parameter, $parameters
);

if (! is_null($instance)) {
$instanceCount++;

// 替换 $parameters 请求的参数
$this->spliceIntoParameters($parameters, $key, $instance);
} elseif (! isset($values[$key - $instanceCount]) &&
$parameter->isDefaultValueAvailable()) {
$this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());
}
}

// 返回替换好的请求的参数
return $parameters;
}

最后:如果我们自己定义的控制器方法,有默认值,那么悠闲拿取默认值,放入 $parameters 中,下面代码就有所体现啦

Illuminate\Routing\RouteDependencyResolverTrait

protected function transformDependency(ReflectionParameter $parameter, $parameters)
{
$class = $parameter->getClass();

if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {
// $parameter->isDefaultValueAvailable(),表示当前参数有没有默认值,有则返回 true
return $parameter->isDefaultValueAvailable()
? $parameter->getDefaultValue()
: $this->container->make($class->name);
}
}

关于 isDefaultValueAvailable 的官方文档-->传送门