依赖注入和控制反转
在框架的底层设计中,需要很多类的继承调用,如果类的依赖性很强就会发生高耦合的情况。但是在软件工程提倡的是高内聚、低耦合。为了降低类的耦合性,控制反转(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
则说明总是创建新的实例化对象,否则代表需创建实例类的参数数组。
/**
* 创建类的实例
* @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]) {
$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 {
$object = $this->invokeClass($abstract, $vars);
}
if (!$newInstance) {
$this->instances[$abstract] = $object;
}
return $object;
}
这里以$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);
}
Container类源码
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st
// +----------------------------------------------------------------------
namespace think;
use Closure;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
class Container
{
/**
* 容器对象实例
* @var Container
*/
protected static $instance;
/**
* 容器中的对象实例
* @var array
*/
protected $instances = [];
/**
* 容器绑定标识
* @var array
*/
protected $bind = [];
/**
* 获取当前容器的实例(单例)
* @access public
* @return static
*/
public static function getInstance()
{
if (is_null(static::$instance)) {
static::$instance = new static;
}
return static::$instance;
}
/**
* 获取容器中的对象实例
* @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);
}
/**
* 绑定一个类、闭包、实例、接口实现到容器
* @access public
* @param string $abstract 类标识、接口
* @param mixed $concrete 要绑定的类、闭包或者实例
* @return Container
*/
public static function set($abstract, $concrete = null)
{
return static::getInstance()->bind($abstract, $concrete);
}
/**
* 绑定一个类、闭包、实例、接口实现到容器
* @access public
* @param string|array $abstract 类标识、接口
* @param mixed $concrete 要绑定的类、闭包或者实例
* @return $this
*/
public function bind($abstract, $concrete = null)
{
if (is_array($abstract)) {
$this->bind = array_merge($this->bind, $abstract);
} elseif ($concrete instanceof Closure) {
$this->bind[$abstract] = $concrete;
} elseif (is_object($concrete)) {
$this->instances[$abstract] = $concrete;
} else {
$this->bind[$abstract] = $concrete;
}
return $this;
}
/**
* 绑定一个类实例当容器
* @access public
* @param string $abstract 类名或者标识
* @param object $instance 类的实例
* @return $this
*/
public function instance($abstract, $instance)
{
if (isset($this->bind[$abstract])) {
$abstract = $this->bind[$abstract];
}
$this->instances[$abstract] = $instance;
return $this;
}
/**
* 判断容器中是否存在类及标识
* @access public
* @param string $abstract 类名或者标识
* @return bool
*/
public function bound($abstract)
{
return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
}
/**
* 判断容器中是否存在类及标识
* @access public
* @param string $name 类名或者标识
* @return bool
*/
public function has($name)
{
return $this->bound($name);
}
/**
* 创建类的实例
* @access public
* @param string $abstract 类名或者标识
* @param array|true $args 变量
* @param bool $newInstance 是否每次创建新的实例
* @return object
*/
public function make($abstract, $vars = [], $newInstance = false)
{
if (true === $vars) {
// 总是创建新的实例化对象
$newInstance = true;
$vars = [];
}
if (isset($this->instances[$abstract]) && !$newInstance) {
$object = $this->instances[$abstract];
} else {
if (isset($this->bind[$abstract])) {
$concrete = $this->bind[$abstract];
if ($concrete instanceof Closure) {
$object = $this->invokeFunction($concrete, $vars);
} else {
$object = $this->make($concrete, $vars, $newInstance);
}
} else {
$object = $this->invokeClass($abstract, $vars);
}
if (!$newInstance) {
$this->instances[$abstract] = $object;
}
}
return $object;
}
/**
* 执行函数或者闭包方法 支持参数调用
* @access public
* @param string|array|\Closure $function 函数或者闭包
* @param array $vars 变量
* @return mixed
*/
public function invokeFunction($function, $vars = [])
{
$reflect = new ReflectionFunction($function);
$args = $this->bindParams($reflect, $vars);
return $reflect->invokeArgs($args);
}
/**
* 调用反射执行类的方法 支持参数绑定
* @access public
* @param string|array $method 方法
* @param array $vars 变量
* @return mixed
*/
public function invokeMethod($method, $vars = [])
{
if (is_array($method)) {
$class = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
$reflect = new ReflectionMethod($class, $method[1]);
} else {
// 静态方法
$reflect = new ReflectionMethod($method);
}
$args = $this->bindParams($reflect, $vars);
return $reflect->invokeArgs(isset($class) ? $class : null, $args);
}
/**
* 调用反射执行callable 支持参数绑定
* @access public
* @param mixed $callable
* @param array $vars 变量
* @return mixed
*/
public function invoke($callable, $vars = [])
{
if ($callable instanceof Closure) {
$result = $this->invokeFunction($callable, $vars);
} else {
$result = $this->invokeMethod($callable, $vars);
}
return $result;
}
/**
* 调用反射执行类的实例化 支持依赖注入
* @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);
}
/**
* 绑定参数
* @access protected
* @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
* @param array $vars 变量
* @return array
*/
protected function bindParams($reflect, $vars = [])
{
$args = [];
if ($reflect->getNumberOfParameters() > 0) {
// 判断数组类型 数字数组时按顺序绑定参数
reset($vars);
$type = key($vars) === 0 ? 1 : 0;
$params = $reflect->getParameters();
foreach ($params as $param) {
$name = $param->getName();
$class = $param->getClass();
if ($class) {
$className = $class->getName();
$args[] = $this->make($className);
} elseif (1 == $type && !empty($vars)) {
$args[] = array_shift($vars);
} elseif (0 == $type && isset($vars[$name])) {
$args[] = $vars[$name];
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
throw new InvalidArgumentException('method param miss:' . $name);
}
}
}
return $args;
}
}