Laravel HTTP-控制器方法的参数构建与运行

思考并回答以下问题:

前言

经过前面一系列中间件的工作,现在请求终于要达到了正确的控制器方法了。本篇文章主要讲Laravel如何调用控制器方法,并且为控制器方法依赖注入构建参数的过程。

路由控制器的调用

我们前面已经解析过中间件的搜集与排序、pipeline的原理,接下来就要进行路由的run运行函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

namespace Illuminate\Routing;

class Router implements BindingRegistrar, RegistrarContract
{
/**
* Run the given route within a Stack "onion" instance.
*
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
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()
);
});
}
}

路由的run函数主要负责路由控制器方法与路由闭包函数的运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace Illuminate\Routing;

class Route
{
/**
* Run the route action and return the response.
*
* @return mixed
*/
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();
}
}
}

路由的运行主要靠ControllerDispatcher这个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Route
{
/**
* Checks whether the route's action is a controller.
*
* @return bool
*/
protected function isControllerAction()
{
return is_string($this->action['uses']);
}

/**
* Run the route action and return the response.
*
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
protected function runController()
{
return $this->controllerDispatcher()->dispatch(
$this, $this->getController(), $this->getControllerMethod()
);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace Illuminate\Routing;

use Illuminate\Container\Container;
use Illuminate\Routing\Contracts\ControllerDispatcher as ControllerDispatcherContract;

class ControllerDispatcher implements ControllerDispatcherContract
{
use RouteDependencyResolverTrait;

/**
* Dispatch a request to a given controller and method.
*
* @param \Illuminate\Routing\Route $route
* @param mixed $controller
* @param string $method
* @return mixed
*/
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));
}
}

上面可以很清晰地看出,控制器的运行分为两步:解析函数参数、调用callAction

解析控制器方法参数

解析参数的功能主要由ControllerDispatcher类的RouteDependencyResolverTrait这一trait负责:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php

namespace Illuminate\Routing;

use Illuminate\Support\Arr;
use ReflectionFunctionAbstract;
use ReflectionMethod;
use ReflectionParameter;

trait RouteDependencyResolverTrait
{
/**
* Resolve the object method's type-hinted dependencies.
*
* @param array $parameters
* @param object $instance
* @param string $method
* @return array
*/
protected function resolveClassMethodDependencies(array $parameters, $instance, $method)
{
if (! method_exists($instance, $method)) {
return $parameters;
}

return $this->resolveMethodDependencies(
$parameters, new ReflectionMethod($instance, $method)
);
}

/**
* Resolve the given method's type-hinted dependencies.
*
* @param array $parameters
* @param \ReflectionFunctionAbstract $reflector
* @return 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;
}
}

控制器方法函数参数构造难点在于,参数来源有三种:

  • 路由参数赋值
  • IoC容器自动注入
  • 函数自带默认值

在IoC容器自动注入的时候,要保证路由的现有参数中没有相应的类,防止依赖注入覆盖路由绑定的参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Attempt to transform the given parameter into a class instance.
*
* @param \ReflectionParameter $parameter
* @param array $parameters
* @return mixed
*/
protected function transformDependency(ReflectionParameter $parameter, $parameters)
{
$class = $parameter->getClass();

// If the parameter has a type-hinted class, we will check to see if it is already in
// the list of parameters. If it is we will just skip it as it is probably a model
// binding and we do not want to mess with those; otherwise, we resolve it here.
if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {
return $parameter->isDefaultValueAvailable()
? $parameter->getDefaultValue()
: $this->container->make($class->name);
}
}

由IoC容器构造出的参数需要插入到原有的路由参数数组中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (! is_null($instance)) 
{
$instanceCount++;

$this->spliceIntoParameters($parameters, $key, $instance);
}

/**
* Splice the given value into the parameter list.
*
* @param array $parameters
* @param string $offset
* @param mixed $value
* @return void
*/
protected function spliceIntoParameters(array &$parameters, $offset, $value)
{
array_splice(
$parameters, $offset, 0, [$value]
);
}

当路由的参数数组与IoC容器构造的参数数量不足以覆盖控制器参数个数时,就要去判断控制器是否具有默认参数:

1
2
3
4
elseif (! isset($values[$key - $instanceCount]) && $parameter->isDefaultValueAvailable()) 
{
$this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());
}

调用控制器方法callAction

所有的控制器并非是直接调用相应方法的,而是通过callAction函数再分配,如果实在没有相应方法还会调用魔术方法__call() :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
namespace Illuminate\Routing;

use BadMethodCallException;

abstract class Controller
{
/**
* Execute an action on the controller.
*
* @param string $method
* @param array $parameters
* @return \Symfony\Component\HttpFoundation\Response
*/
public function callAction($method, $parameters)
{
return call_user_func_array([$this, $method], $parameters);
}

/**
* Handle calls to missing methods on the controller.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
throw new BadMethodCallException(sprintf(
'Method %s::%s does not exist.', static::class, $method
));
}
}

路由闭包函数的调用

路由闭包函数的调用与控制器方法一样,仍然需要依赖注入,参数构造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

class Route
{
/**
* Run the route action and return the response.
*
* @return mixed
*/
protected function runCallable()
{
$callable = $this->action['uses'];

return $callable(...array_values($this->resolveMethodDependencies(
$this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses'])
)));
}
}
0%