TP6依赖注入是如何实现的
先看下app/provider容器文件,次文件会在think\APP实例化的时候
直接从新绑定类到的容器上。复制原有容器中的类
可以先看下think\APP 构造方法中的处理逻辑
/**
* 架构方法
* @access public
* @param string $rootPath 应用根目录
*/
public function __construct(string $rootPath = '')
{
//设置thinkphp扩展的目录
$this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR;
//项目更目录
$this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR
//应用根目录
$this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
//runtime根目录
$this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATO
//检测app/provider.php文件进行替换容器绑定
if (is_file($this->appPath . 'provider.php')) {
$this->bind(include $this->appPath . 'provider.php');
}
//将当前容器实例保存到成员变量「$instance」中,也就是容器自己保存自己的一个
static::setInstance($this);
//// 保存绑定的实例到「$instances」数组中,见对应分析
$this->instance('app', $this);
$this->instance('think\Container', $this);
}
在控制中可以使用app()->db 可以看到think\App中根本没有此属性,php的类中,调用一个类的不存在的属性就会自动进入魔术方法__get(),再来看看app类当中的__get方法,app类中没有找到集成的类中也就是think\Container 中直接搜索__get方法,就能找到。
think\Container
//$name就是就是没有定义的属性的名称
public function __get($name)
{
return $this->get($name);
}
找到当前类的get方法,首先是检查了以下容器中有没有,没有就直接实例化,进行调用make方法进行创建类的实例化。
/**
* 获取容器中的对象实例
* @access public
* @param string $abstract 类名或者标识
* @return object
*/
public function get($abstract)
{
if ($this->has($abstract)) {
return $this->make($abstract);
}
throw new ClassNotFoundException('class not exists: ' . $abstract, $a
}
make主要检测有没实例化过由实例化过后就直接返回使用就行,没有就需要利用类的反射来创建类的实例化,可以看到调用了本类的invokeClass方法
public function make(string $abstract, array $vars = [], bool $newInstance =
{
//如果已经存在实例,且不强制创建新的实例,直接返回已存在的实例
if (isset($this->instances[$abstract]) && !$newInstance) {
return $this->instances[$abstract];
}
//如果有绑定,比如 'http'=> 'think\Http',则 $concrete = 'think\Http'
if (isset($this->bind[$abstract])) {
$concrete = $this->bind[$abstract];
if ($concrete instanceof Closure) {
$object = $this->invokeFunction($concrete, $vars);
} else {
//重走一遍make函数,比如上面http的例子,则会调到后面「invokeClass
return $this->make($concrete, $vars, $newInstance);
}
} else {
//实例化需要的类,比如'think\Http'
$object = $this->invokeClass($abstract, $vars);
}
if (!$newInstance) {
$this->instances[$abstract] = $object;
}
return $object;
}
invokeClass 方法主要为了绑定参数然后进行实例化类,绑定参数由bindParams方法实现,而bindParams方法中的getObjectParam方法中又会回调make方法。
/**
* 调用反射执行类的实例化 支持依赖注入
* @access public
* @param string $class 类名
* @param array $vars 参数
* @return mixed
*/
public function invokeClass(string $class, array $vars = [])
{
try {
//通过反射实例化类
$reflect = new ReflectionClass($class);
//检查是否有「__make」方法
if ($reflect->hasMethod('__make')) {
$method = new ReflectionMethod($class, '__make');
//检查是否是公有方法且是静态方法
if ($method->isPublic() && $method->isStatic()) {
//绑定参数
$args = $this->bindParams($method, $vars);
//调用该方法(__make),因为是静态的,所以第一个参数是null
//因此,可得知,一个类中,如果有__make方法,在类实例化之前会首
return $method->invokeArgs(null, $args);
}
}
//获取类的构造函数
$constructor = $reflect->getConstructor();
//有构造函数则绑定其参数
$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, $
}
}
getObjectParam方法中是拿到当前类实例化的参数,找到当前参数是否是类,如果是就会直接再次调用make方法,如果下个参数还是个类的实例化结果,会再次进行回调,这就是一个类中可以无限制的注入多个类的原理,所以在使用的当中运用app()->make()来进行获取类的例化,更加方便简洁
/**
* 获取对象类型的参数值
* @access protected
* @param string $className 类名
* @param array $vars 参数
* @return mixed
*/
protected function getObjectParam(string $className, array &$vars)
{
$array = $vars;
$value = array_shift($array);
if ($value instanceof $className) {
$result = $value;
array_shift($vars);
} else {
$result = $this->make($className);
}
return $result;
}
总的来说,整个过程大概是这样的:需要实例化 Db 类 ==> 提取构造函数发现其依赖App 类 ==> 开始实例化 App 类(如果发现还有依赖,则一直提取下去,直到依赖全部加载完)==> 将实例化好的依赖(App 类的实例)传入 Db 类来实例化 Db 类。
感谢您的阅读,如果对您有帮助,欢迎关注"CRMEB"掘金号。码云上有我们开源的商城项目,知识付费项目,均是基于PHP开发,学习研究欢迎使用,关注我们保持联系!