TP5源码解析之容器(一)

 阅读约 9 分钟

TP5的容器文件是thinkphp/library/think/Container.php,该类实现了三个接口,分别是:

  • ArrayAccess使容器支持数组的形式访问
  • IteratorAggregate使容器支持迭代
  • Countable使容器可以使用count()函数获取容器中实例的个数

该类中的方法较多,分两篇文章讲述,本篇分析如何绑定到容器、容器如何创建类的实例,主要涉及类中的几个属性$instance、$instances、$bind、$name,以及三个方法:bindTo()instance()make()

属性

  • $instance 容器对象实例,通过类方法getInstance()单例实现
  • $instances 容器中的对象实例,用于存放容器中的类、接口有直接实例的情况
  • $bind 容器绑定标识,保存了系统中常用的标识和对应类的映射关系,这里主要是为了开发者通过一个简单的标识就能快速定位到系统中要绑定的类
  • $name 容器标识别名,这里也是为了方便开发者通过自定义的标识,就快速定位到系统zhong要绑定的类

方法

  • bindTo($abstract, $concrete = null)
  1. 作用:批量绑定标识、闭包、对象、接口到容器
  2. 代码解析:
/**
 * 绑定一个类、闭包、实例、接口实现到容器
 * @access public
 * @param  string|array  $abstract    标识
 * @param  mixed         $concrete    实现
 * @return $this
 */
public function bindTo($abstract, $concrete = null)
{
    if (is_array($abstract)) {
        //如果第一个参数是数组,将传进来的数组与类中已有的$this->bind属性合并(传进来的数组会覆盖同名的数组项)
        $this->bind = array_merge($this->bind, $abstract);
    } elseif ($concrete instanceof Closure) {
        //如果第二个参数是一个闭包,绑定标识和闭包放入$this->bind
        $this->bind[$abstract] = $concrete;
    } elseif (is_object($concrete)) {
        //如果第二个参数是一个对象,并且第一个参数在属性 $this->bind 数组中已存在,先取出该标识对应的完整类名,然后将类名和类实例绑定,放入$this->instances 属性中
        if (isset($this->bind[$abstract])) {
            $abstract = $this->bind[$abstract];
        }
        $this->instances[$abstract] = $concrete;
    } else {
        //都没有匹配到,直接绑定标识和对应的实现,放入属性 $this->bind 
        $this->bind[$abstract] = $concrete;
    }

    return $this;
}
  • instance($abstract, $instance)
  1. 作用:该方法与bindTo()方法类似,但是更专一,主要是绑定接口和类实现到容器中,bindTo()中已经包含这部分代码,不再介绍
  2. 代码解析:
/**
 * 绑定一个类实例当容器
 * @access public
 * @param  string           $abstract    标识
 * @param  object|\Closure  $instance    实现
 * @return $this
 */
public function instance($abstract, $instance)
{
    if ($instance instanceof \Closure) {
        $this->bind[$abstract] = $instance;
    } else {
        if (isset($this->bind[$abstract])) {
            $abstract = $this->bind[$abstract];
        }

        $this->instances[$abstract] = $instance;
    }

    return $this;
}
  • make($abstract, $vars = [], $newInstance = false)
  1. 作用:创建类的实例
  2. 代码解析:
/**
     * 创建类的实例
     * @access public
     * @param  string        $abstract       类名或者标识
     * @param  array|true    $vars           变量
     * @param  bool          $newInstance    是否每次创建新的实例
     * @return object
     */
    public function make($abstract, $vars = [], $newInstance = false)
    {
        if (true === $vars) {
            // 总是创建新的实例化对象
            $newInstance = true;
            $vars        = [];
        }
        
        //首先判断标识别名数组中是否包含该标识,如果存在,取出对应的值,当作新的标识
        $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;

        //容器中的对象实例数组中是否包含该标识,如果不存在,并且不需要一个新的实例,直接返回该标识对应的实例
        if (isset($this->instances[$abstract]) && !$newInstance) {
            return $this->instances[$abstract];
        }
       
        if (isset($this->bind[$abstract])) {
            //如果容器标识数组中存在该标识,判断对应的值是否是闭包,
            // 是:利用php的反射机制调用闭包函数
            // 否:将该标识和值放入别名数组中,递归调用自己,直到$this->bind 属性数组没有标识。其实正常情况下,这里的值应该就是类名了,但是因为在 bindTo 数组绑定时直接进行了合并,所以存在这种可能:bindTo(['myapp' => 'app']),make 的时候传入的参数是:myapp,如果是这种情况,那就需要先根据 myapp 标识取出 app 标识,再根据 app 标识获取对应的类:App::class
            $concrete = $this->bind[$abstract];

            if ($concrete instanceof Closure) {
                $object = $this->invokeFunction($concrete, $vars);
            } else {
                $this->name[$abstract] = $concrete;
                return $this->make($concrete, $vars, $newInstance);
            }
        } else {
            //等到 $this->bind 属性中匹配不到值的时候,就会走到这里,调用 php 的反射类 invokeClass,获取类实例 
            $object = $this->invokeClass($abstract, $vars);
        }
        //如果 make 的时候不要求产生一个新的对象,可以将类实例放入容器中的对象实例数组中,下次调用就可以直接获取
        if (!$newInstance) {
            $this->instances[$abstract] = $object;
        }
        //返回生成的类实例
        return $object;
    }
阅读 107发布于 9月22日
推荐阅读
目录