TP6依赖注入是如何实现的
先看下app/provider容器文件,此文件会在thinkAPP实例化的时候
直接从新绑定类到的容器上。复制原有容器中的类
可以先看下thinkAPP 构造方法中的处理逻辑
/** * 架构方法 * @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('thinkContainer', $this); }
在控制中可以使用app()->db 可以看到thinkApp中根本没有此属性,php的类中,调用一个类的不存在的属性就会自动进入魔术方法__get(),再来看看app类当中的__get方法,app类中没有找到集成的类中也就是thinkContainer 中直接搜索__get方法,就能找到。
thinkContainer //$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'=> 'thinkHttp',则 $concrete = 'thinkHttp' 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 { //实例化需要的类,比如'thinkHttp' $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开发,学习研究欢迎使用,关注我们保持联系!