ThinkPHP6 实例化 Http 类和依赖注入

index.php 第二句

// 执行HTTP应用并响应
$http = (new App())->http;

1 实例化App类

public function __construct(string $rootPath = '')
{
    // 定义一些目录地址
    $this->thinkPath   = dirname(__DIR__) . DIRECTORY_SEPARATOR;
    $this->rootPath    = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
    $this->appPath     = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
    $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;

    // 将provider.php中返回的类的映射合并到 $bind 变量中,相同名称的会覆盖,如此可以通过在该文件中定义方式改变框架所使用的某些核心类
    if (is_file($this->appPath . 'provider.php')) {
        $this->bind(include $this->appPath . 'provider.php');
    }

    // 将当前App实例保存到静态属性 $instance 中
    static::setInstance($this);

    // 绑定当前实例到 $instances 数组的中,后面再有用到这个类实例时直接取这里的
    $this->instance('app', $this);
    $this->instance('think\Container', $this);
}

看下App类继承的Container类中的 bind 方法

public function bind($abstract, $concrete = null)
{
    // 如果是数组的话,就循环该数组,数组中的每个元素重走该 bind 方法,我们上面调用的 bind 就是先走了这里
    if (is_array($abstract)) {
        foreach ($abstract as $key => $val) {
            $this->bind($key, $val);
        }

    // 如果是匿名函数,直接保存到 bind 中
    } elseif ($concrete instanceof Closure) {
        $this->bind[$abstract] = $concrete;

    // 如果是一个类的实例,则保存到 instances 数组中
    } elseif (is_object($concrete)) {
        $this->instance($abstract, $concrete);

    // 我们前面调用 bind 方法在进过第一次 foreach 循环后就是走这里了,绑定到 bind 变量中
    } else {
        // 获取真实类名,因为有可能传过来的是一个别名,当然在这里两个都不是,依然返回了它们自己
        $abstract = $this->getAlias($abstract);
        $this->bind[$abstract] = $concrete;
    }

    return $this;
}

再看看 getAlias 方法

public function getAlias(string $abstract): string
{
    // 检查 $bind 中是否有保存对应的类名
    if (isset($this->bind[$abstract])) {
        $bind = $this->bind[$abstract];
        // 如果有,并且是一个字符串(还有可能是闭包),那这个字符串有可能还是别名,继续查找 $bind
        if (is_string($bind)) {
            return $this->getAlias($bind);
        }
    }

    return $abstract;
}

实例化Http类

App类中并没有http这个成员变量,(new App())->http 会调用 App 类的魔术方法 __get

// 在 App 继承的 Container类中
public function __get($name)
{
    return $this->get($name);
}

public function get($abstract)
{
    // 判断 bind 变量中是否有绑定对应的类名或者 instances 变量中是否已保存对应类的实例
   // 比如这里 $abstract=http, 在 bind 中是有的 
    if ($this->has($abstract)) {
        // 获取这个类的实例
        return $this->make($abstract);
    }

    throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
}

让我们看看 make方法里做了什么

/**
 * 创建类的实例 已经存在则直接获取
 * @access public
 * @param string $abstract    类名或者标识
 * @param array  $vars        变量
 * @param bool   $newInstance 是否每次创建新的实例
 * @return mixed
 */
public function make(string $abstract, array $vars = [], bool $newInstance = false)
{
    $abstract = $this->getAlias($abstract);

    // 如果 instances 已保存对应的实例,就直接返回
    if (isset($this->instances[$abstract]) && !$newInstance) {
        return $this->instances[$abstract];
    }

    // 如果是一个闭包
    if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
        // em....这里还没看
        $object = $this->invokeFunction($this->bind[$abstract], $vars);
    } else {
        // 通过反射获得所需要的类实例
        $object = $this->invokeClass($abstract, $vars);
    }

    if (!$newInstance) {
        // 保存实例到 instances 数组中
        $this->instances[$abstract] = $object;
    }

    return $object;
}

然后是 invokeClass 方法

public function invokeClass(string $class, array $vars = [])
{
    try {
        // 获取 http 的反射类
        $reflect = new ReflectionClass($class);

        if ($reflect->hasMethod('__make')) {
            $method = new ReflectionMethod($class, '__make');

            // 如果有 __make 方法,并且是公有的静态方法,执行 __make 方法并返回,后面在实例 Request 类时会走这里。
            if ($method->isPublic() && $method->isStatic()) {
                $args = $this->bindParams($method, $vars);
                return $method->invokeArgs(null, $args);
            }
        }

        // 获取类的构造函数
        $constructor = $reflect->getConstructor();

        // 绑定构造函数的参数,http 这个类的构造方法需要一个App类的实例作为参数,这个方法的注释中有些到支持依赖注入,如何实现的依赖注入就在这个 bindParams 方法中
        $args = $constructor ? $this->bindParams($constructor, $vars) : [];

        // 
        $object = $reflect->newInstanceArgs($args);

        $this->invokeAfter($class, $object);

        return $object;
    } catch (ReflectionException $e) {
        throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
    }
}

bindParams 是如何实现依赖注入的

protected function bindParams($reflect, array $vars = []): array
{
    // 获取方法的参数个数,0的话直接返回空数组
    if ($reflect->getNumberOfParameters() == 0) {
        return [];
    }

    // 判断数组类型 数字数组时按顺序绑定参数
    reset($vars); // 将数组的指针指到数组的第一个元素
    $type   = key($vars) === 0 ? 1 : 0; // 获取当前指针所在数组元素的下标,判断是不是0
    $params = $reflect->getParameters(); // 获取方法所需的参数
    $args   = [];

    foreach ($params as $param) {
        $name      = $param->getName(); // app
        $lowerName = Str::snake($name); // 大写转下划线
        $class     = $param->getClass(); // 获取参数类型限定的类 think\App

        // 如果参数类型限定是一个类,这里就是走的这个
        if ($class) {
            // getObjectParam 方法中再次调用了 make 方法来获取需要的 App类,而这个 App 类已经在前面 new App()的时候保存到 instances 变量中了,如此就完成了依赖注入
            // 假如 App 类的构造函数中需要其它类的实例作为参数,那就会再走一遍这个流程
            // 我的理解,依赖注入就是对类实例的需要通过传参来实现,控制反转就是这个传参不需要使用者主动传,交由Container完成
            $args[] = $this->getObjectParam($class->getName(), $vars);
        } elseif (1 == $type && !empty($vars)) {
        ...
    }
    return $args;
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!