当前位置: 首页 > 文档资料 > Laravel 源码详解 >

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

优质
小牛编辑
129浏览
2023-12-01

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

路由控制器的调用

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

  1. protected function runRouteWithinStack(Route $route, Request $request)
  2. {
  3. $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
  4. $this->container->make('middleware.disable') === true;
  5. $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
  6. return (new Pipeline($this->container))
  7. ->send($request)
  8. ->through($middleware)
  9. ->then(function ($request) use ($route) {
  10. return $this->prepareResponse(
  11. $request, $route->run()
  12. );
  13. });
  14. }

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

  1. public function run()
  2. {
  3. $this->container = $this->container ?: new Container;
  4. try {
  5. if ($this->isControllerAction()) {
  6. return $this->runController();
  7. }
  8. return $this->runCallable();
  9. } catch (HttpResponseException $e) {
  10. return $e->getResponse();
  11. }
  12. }

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

  1. class Route
  2. {
  3. protected function isControllerAction()
  4. {
  5. return is_string($this->action['uses']);
  6. }
  7. protected function runController()
  8. {
  9. return (new ControllerDispatcher($this->container))->dispatch(
  10. $this, $this->getController(), $this->getControllerMethod()
  11. );
  12. }
  13. }
  14. class ControllerDispatcher
  15. {
  16. use RouteDependencyResolverTrait;
  17. public function dispatch(Route $route, $controller, $method)
  18. {
  19. $parameters = $this->resolveClassMethodDependencies(
  20. $route->parametersWithoutNulls(), $controller, $method
  21. );
  22. if (method_exists($controller, 'callAction')) {
  23. return $controller->callAction($method, $parameters);
  24. }
  25. return $controller->{$method}(...array_values($parameters));
  26. }
  27. }

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

解析控制器方法参数

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

  1. trait RouteDependencyResolverTrait
  2. {
  3. protected function resolveClassMethodDependencies(array $parameters, $instance, $method)
  4. {
  5. if (! method_exists($instance, $method)) {
  6. return $parameters;
  7. }
  8. return $this->resolveMethodDependencies(
  9. $parameters, new ReflectionMethod($instance, $method)
  10. );
  11. }
  12. public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector)
  13. {
  14. $instanceCount = 0;
  15. $values = array_values($parameters);
  16. foreach ($reflector->getParameters() as $key => $parameter) {
  17. $instance = $this->transformDependency(
  18. $parameter, $parameters
  19. );
  20. if (! is_null($instance)) {
  21. $instanceCount++;
  22. $this->spliceIntoParameters($parameters, $key, $instance);
  23. } elseif (! isset($values[$key - $instanceCount]) &&
  24. $parameter->isDefaultValueAvailable()) {
  25. $this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());
  26. }
  27. }
  28. return $parameters;
  29. }
  30. }

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

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

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

  1. protected function transformDependency(ReflectionParameter $parameter, $parameters)
  2. {
  3. $class = $parameter->getClass();
  4. if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {
  5. return $this->container->make($class->name);
  6. }
  7. }
  8. protected function alreadyInParameters($class, array $parameters)
  9. {
  10. return ! is_null(Arr::first($parameters, function ($value) use ($class) {
  11. return $value instanceof $class;
  12. }));
  13. }

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

  1. if (! is_null($instance)) {
  2. $instanceCount++;
  3. $this->spliceIntoParameters($parameters, $key, $instance);
  4. }
  5. protected function spliceIntoParameters(array &$parameters, $offset, $value)
  6. {
  7. array_splice(
  8. $parameters, $offset, 0, [$value]
  9. );
  10. }

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

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

调用控制器方法 callAction

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

  1. public function callAction($method, $parameters)
  2. {
  3. return call_user_func_array([$this, $method], $parameters);
  4. }
  5. public function __call($method, $parameters)
  6. {
  7. throw new BadMethodCallException("Method [{$method}] does not exist.");
  8. }

路由闭包函数的调用

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

  1. protected function runCallable()
  2. {
  3. $callable = $this->action['uses'];
  4. return $callable(...array_values($this->resolveMethodDependencies(
  5. $this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses'])
  6. )));
  7. }