分析tp5.1框架源码,看Container是如何来获取一个类的实例并实现依赖注入。

index.php /public/index.php base.php /think/base.php Container类位置在/think/libbrary/think/Container.php

Container get方法

Container::get('app')最终返回了app的实例对象。
首先入口文件index.php 的最后一行执行了Container::get('app')->run()->send(),只看前半部分Container::get('app')

# Container的get方法
/**
 * 获取容器中的对象实例
 * @access public
 * @param  string        $abstract       类名或者标识
 * @param  array|true    $vars           变量
 * @param  bool          $newInstance    是否每次创建新的实例
 * @return object
 */
public static function get($abstract, $vars = [], $newInstance = false)
{
    return static::getInstance()->make($abstract, $vars, $newInstance);
}

该方法调用了geitInstance()来获取当前容易对象的实例,并且是单例模式,因为在tp框架里容器类是tp的核心,每次的请求都会用到容器类,而在每一次的请求里,会用到不同的容器对象,所以单例模式能有效节省资源创建的开销。
接下里是调用Container的make($abstract,$vars,$newInstance),并传递了app参数进去。

make方法



// Container 属性
protected $bind = [
'app'                   => App::class,
'build'                 => Build::class,
...


public function make($abstract, $vars = [], $newInstance = false)
{
    // 判断该容器表示‘app’表示是否已经存在属性$name里,
    $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
    
    
    // 判断当前要获取的容器是否已经存在,存在则直接返回,第一次不会走该方法
    if (isset($this->instances[$abstract]) && !$newInstance) {
        return $this->instances[$abstract];
    }
    
    // 判断容器是否在已经预先设定的$bind属性里,
    // tp在$bind属性里预先设定了一系列标识,所以进入该方法。
    
    if (isset($this->bind[$abstract])) {
        $concrete = $this->bind[$abstract]; // 获取bind数组里对应的值。
        // 值如果是闭包函数则走这里,app对应的值是一个类,不会走该判断
        if ($concrete instanceof Closure) { 
            $object = $this->invokeFunction($concrete, $vars);
        } else {
            // 对name属性进行赋值,$this->name['app'] = 'think\App',
            $this->name[$abstract] = $concrete;
            
            //再次调用make ,此时传入的就是'think\App';
            return $this->make($concrete, $vars, $newInstance);
        }
    } else {
        // 再次调用后会走到这里
        $object = $this->invokeClass($abstract, $vars);
    }
    
    if (!$newInstance) {
        $this->instances[$abstract] = $object;
    }
    
    return $object;
}

make方法在第一次调用传入了’app’,而后会拿到预先定义在$bind里的值thin\App,然后再次调用后传入think\app,最后会走到invokeClass这个方法,该方法作用是获APP这个类的反射类,并最终对APP这个类进行实例化后返回

invokeClass方法

/**
 * 调用反射执行类的实例化 支持依赖注入
 * @access public
 * @param  string    $class 类名
 * @param  array     $vars  参数
 * @return mixed
 */
public function invokeClass($class, $vars = [])
{
    try {
        // new ReflectionClass('think\App') 
        $reflect = new ReflectionClass($class);
        // 判断App类里是否有make方法,App类里没有,跳过
        if ($reflect->hasMethod('__make')) {
            $method = new ReflectionMethod($class, '__make');

            if ($method->isPublic() && $method->isStatic()) {
                $args = $this->bindParams($method, $vars);
                return $method->invokeArgs(null, $args);
            }
        }
        
        // 获取App的构造方法
        $constructor = $reflect->getConstructor();
        
        // 如果存在构造方法调用bindParams返回参数
        $args = $constructor ? $this->bindParams($constructor, $vars) : [];
        // 拿到参数后进行实例化并返回
        return $reflect->newInstanceArgs($args);

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

在invokeClass首先实例化一个反射类,并且判断是否有make方法,接下来获取该反射类的构造方法,有构造方法,则调用$bindParam,没有则直接实例化返回。

bindParams方法

protected function bindParams($reflect, $vars = [])
    {
        // 判断App的构造函数参数,等于0则直接返回
        if ($reflect->getNumberOfParameters() == 0) {
            return [];
        }

        // 判断数组类型 数字数组时按顺序绑定参数
        reset($vars);
        $type   = key($vars) === 0 ? 1 : 0;
        // 获取构造函数参数
        $params = $reflect->getParameters();
        
        foreach ($params as $param) {
            $name      = $param->getName();
            $lowerName = Loader::parseName($name);
            // 循环参数对象。
            $class     = $param->getClass();

            if ($class) {
                // $class->getName()获取的是命名空间+类名 例如think\App
                $args[] = $this->getObjectParam($class->getName(), $vars);
            } elseif (1 == $type && !empty($vars)) {
                $args[] = array_shift($vars);
            } elseif (0 == $type && isset($vars[$name])) {
                $args[] = $vars[$name];
            } elseif (0 == $type && isset($vars[$lowerName])) {
                $args[] = $vars[$lowerName];
            } elseif ($param->isDefaultValueAvailable()) {
                $args[] = $param->getDefaultValue();
            } else {
                throw new InvalidArgumentException('method param miss:' . $name);
            }
        }

        return $args;
    }

之所以支持依赖注入,也是因为在该方法里获取了构造函数的参数,构造函数参数如果是一个类的话,则获取该类的命名空间+类名,例如

Pay.php
namespace Order;
class Pay{}

App.php
public __construct(Pay $pay)

App的构造方法传入的依赖Pay这个类,上面bindParams方法里的$class->getName()获得是’Order\Pay‘,接下来调用$this->getObjectParam('Order\Pay'),getObjectParam里会再次调用make('Order\Pay')方法来实例化类,从而实现依赖注入。