依赖注入和控制反转
在框架的底层设计中,需要很多类的继承调用,如果类的依赖性很强就会发生高耦合的情况。但是在软件工程提倡的是高内聚、低耦合。为了降低类的耦合性,控制反转(IOC)是一种有效的设计原则,而依赖注入是控制反转的一种实现方式。
简单注入
例子
namespace app\index\controller;
user think\Request;
class Test{
public function index(Request $req)
{
var_dump($req->get());
}
}
Container类很重要,在使用控制反转前,类与类的依赖关系是杂乱无章的,而使用了 Container容器类进行控制反转后,只有容器类依赖其他所有的类,而其他类之间没有了依赖关系,他们所需的实例都通过容器类进行依赖注入。
Container类
Container使用了单例和注册树模式,通过静态方法getInstance获取类对象,并且将容器中的对象实例保存在类的成员变量$instances中。另外还有成员变量$bind和$name,分别是容器绑定个标识和容器标识别名。
/**
* 容器中的对象实例
* @var array
*/
protected $instances = [];
/**
* 容器绑定标识
* @var array
*/
protected $bind = [];
/**
* 容器标识别名
* @var array
*/
protected $name = [];
/**
* 获取当前容器的实例 (单例实现)
* @access public
* @return static
*/
public static function getInstance()
{
if(is_null(static::$instance)) {
static::$instance = new static;
}
return static::$instance;
}
在通过容器获取类的实例时,可以通过助手函数app或者Container::get()传入类名或类别名获取,但其实助手函数app也是通过调用Container::get()来实现的。而get方法会调用make方法,make方法才是容器类的核心,注意调用make方法时是通过单例去调用的。
/**
* 获取容器中的对象实例
* @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);
}
make方法
make方法的第二个参数默认为空数组,若传true则说明总是创建新的实例化对象,否则代表需创建实例类的参数数组。
这里以$abstract = 'app'为例来分析代码,这里若$this->name数组中已经存在app,则直接返回其值,app就是think\App的类标识,在$this->name数组中,$this->name['app'] = 'think\App'。但在容器第一次获取app类实例时,这里name为空,所以这里的$abstract = 'test',这段代码$abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;。
同理,instances保存类的实例,若app类已经实例化过,则直接从instances中返回该实例,但在第一次执行中不存在app,所以程序继续向下执行。
$this->bind['app'] = 'think\App' 所以isset为ture,第一次走到if语句中,$concrete='think\App',但$concrete不是闭包,即$concrete instanceof Closure为false,这里走进else语句,设置$this->name['app']='think\App',然后调用自身,注意这时$concrete='think\App'
make方法第二次调用,同样$this−>name和$this->instances中不存在think\App,在外层if判断中这时走到else模块中,调用invokeClass方法实例化类,并将实例保存在$this->instances中,下次在请求时,会从中直接返回该实例。
invokeClass方法
通过反射类调用getConstructor方法获取类的构造函数,注意这里返回的是ReflectionMethod对象,然后调用bindParams方法绑定参数,最后调用ReflectionClass反射类的newInstanceArgs方法,该方法作用是从给出的参数创建一个新的类实例,将该实例返回。
/**
* 调用反射执行类的实例化 支持依赖注入
* @access public
* @param string $class 类名
* @param array $vars 变量
* @return mixed
*/
public function invokeClass($class, $vars = [])
{
$reflect = new ReflectionClass($class);
$constructor = $reflect->getConstructor();
if ($constructor) {
$args = $this->bindParams($constructor, $vars);
} else {
$args = [];
}
return $reflect->newInstanceArgs($args);
}