专业的编程技术博客社区

网站首页 > 博客文章 正文

TP6依赖注入是如何实现的(依赖注入setter注入)

baijin 2024-09-12 11:21:20 博客文章 6 ℃ 0 评论

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开发,学习研究欢迎使用,关注我们保持联系!

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表