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

栏目: IT技术 · 发布时间: 7年前

内容简介:Laravel HTTP——控制器方法的参数构建与运行

前言

本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/laravel-source-analysis
经过前面一系列中间件的工作,现在请求终于要达到了正确的控制器方法了。本篇文章主要讲 laravel 如何调用控制器方法,并且为控制器方法依赖注入构建参数的过程。

 

路由控制器的调用

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

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 函数主要负责路由控制器方法与路由闭包函数的运行:

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 这个类:

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

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));
    }
}

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

解析控制器方法参数

解析参数的功能主要由 ControllerDispatcher 类的 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)
        );
    }

    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 容器自动注入的时候,要保证路由的现有参数中没有相应的类,防止依赖注入覆盖路由绑定的参数:

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

    if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {
        return $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;
    }));
}

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

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

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

protected function spliceIntoParameters(array &$parameters, $offset, $value)
{
    array_splice(
        $parameters, $offset, 0, [$value]
    );
}

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

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

调用控制器方法 callAction

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

public function callAction($method, $parameters)
{
    return call_user_func_array([$this, $method], $parameters);
}

public function __call($method, $parameters)
{
    throw new BadMethodCallException("Method [{$method}] does not exist.");
}

 

路由闭包函数的调用

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

protected function runCallable()
{
    $callable = $this->action['uses'];

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

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

How to Think About Algorithms

How to Think About Algorithms

Jeff Edmonds / Cambridge University Press / 2008-05-19 / USD 38.99

HOW TO THINK ABOUT ALGORITHMS There are many algorithm texts that provide lots of well-polished code and proofs of correctness. Instead, this one presents insights, notations, and analogies t......一起来看看 《How to Think About Algorithms》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具