多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
### :-: **介绍** >[danger] > **在阅读此章节之前先阅读完 “耦合与解耦”和“反射类”章节** > **如果要自行追踪代码学习,必须确保断点调试环境已经搭建好,参考“断点调试技巧章节”** “耦合与解耦”章节我们介绍到在软件的设计过程中解耦的意义和基本原理,在软件涉及的功能过多的时候,仅仅依赖该章节里讲到的方法是远远达不到要求的,不够智能,不够自动化,而控制反转的思想就是为了更方便更自动化的解决类之间耦合的问题,简单的说就是在一个类的方法里需要另一个类的实例时,不需要你自己去 new 了。 >[danger] > **很多朋友可能没搞清楚控制反转和依赖注入的区别,误以为控制反转就是依赖注入,事实并非如此,控制反转是一种机制,通俗的说就是有一个专门的类来管理我们需要 new 的对象,而依赖注入是实现控制反转的一种方式而已,除了依赖注入,还有依赖查找等其他方式** ### :-: **thinkphp5.0 里的依赖注入实现** 这里我们用thinkphp5官方介绍的依赖注入做实例,讲解他的实现机制 手册参考 https://www.kancloud.cn/manual/thinkphp5/215849 **这是控制器里的一个方法,我们使用传统的 new 方式得到一个类的实例** > ``` > public function testInjection() > { > $Spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); > > var_dump($Spreadsheet);exit; > } > > ``` > ![](https://box.kancloud.cn/a7b653839884bf1f9ab993c0478f9ed0_673x266.png) **这里我们用tp的依赖注入机制实现** > ``` > public function testInjection(\PhpOffice\PhpSpreadsheet\Spreadsheet $Spreadsheet) > { > var_dump($Spreadsheet);exit; > } > ``` > ![](https://box.kancloud.cn/c58c278bf2992776e925de0ab6bfb2eb_704x186.png) **同样可以得到这么一个实例,那么这个机制是怎么实现的?** **这里需要大家有反射类的基础,不懂的请移步“反射类”章节 需要理解这个问题我们要先搞清楚这个控制器类是怎么被实例化的,这个 testInjection 方法是如何被调用的** > 在浏览器里请求这个地址 `http://local2.cc/demo/example/testInjection ` > ![](https://box.kancloud.cn/a2ecd8093e78d8df08a2952458e99fe5_938x725.png) > 代码执行到 `App.php:139, think\App::run()` 这个方法,我们在 `self::exec($dispatch, $config)` 这个位置打一个断点,打开` $dispatch `这个参数的 module属性,会发现系统已经解析了我们请求的 url `http://local2.cc/demo/example/testInjection `后面的 `demo/example/testInjection` 这个就是后面定位到的模块,控制器,方法 > ![](https://box.kancloud.cn/8c7326b709862cd8bf1dee607409dd7e_873x283.png) > 我们跟进 `self::exec($dispatch, $config)` 方法,571行的 `Loader::controller` 方法调用。控制器本身就是一个类,要调用里面的方法必须先实例化,这行的作用就是实例化我们的控制器,继续跟进 > ![](https://box.kancloud.cn/cfb37c7881be3c936327331497fc0128_1029x707.png) 跟进后发现这里调用的 `App::invokeClass` 方法,再次跟进 > ![](https://box.kancloud.cn/83f4fd4d0c72da31116f2172a5980669_1038x709.png) > 355 行就是建立控制器 `app\demo\controller\Example` 的反射类,356行 获取他的构造方法,357 行是判断这个控制器有没有定义构造方法 `__construct`,如果有定义,就调用 `self::bindParams` 方法计算`__construct`应该传入的对象,`self::bindParams` 这个是方法的实现我们在下面调用 `testInjection` 方法的时候讲解,在 359 行就是调用 newInstanceArgs 方法,返回一个控制器的实例,效果和 `new app\demo\controller\Example()` 一样。之所有要使用反射来获取而不用 new 就是为了 356 行拿到那个构造方法,实现自动注入。 > ![](https://box.kancloud.cn/4e9749fbbe67403314b104fa8fe99595_1033x744.png) > 执行完 571行的 `Loader::controller` 方法以后 `$instance` 已经为控制器的实例,在 589 行又建立了 `testInjection` 方法的反射,`new \ReflectionMethod($instance, $action)` 中,`$instance` 为控制器对象实例,`$action` 则为方法名,这一步实际为辅助方法,跟核心逻辑没太大关系,在 `config.php` 配置里,有个 `'action_suffix' `配置,这里实际就是添加这个方法前缀。最终得到的方法名为 `$actionName` 这个变量。 > ![](https://box.kancloud.cn/5c5f6e9288d953f59905ce41c3dc5ff7_968x742.png) > 控制器的实例有了( `$instance` ),方法名有了( `$actionName`),下一步就可以开始执行方法了,在606行就是执行这个方法,我们跟进` self::invokeMethod($call, $vars)` 方法 > ![](https://box.kancloud.cn/96df7b448f6b18de68f423e07521ff9f_998x766.png) > 333 行建立了 `testinjection` 方法的反射,339 行就是依赖注入最核心的实现了,我们跟进 > ![](https://box.kancloud.cn/c41882dd64841608b50a7d5a31bd8896_1190x825.png) > 这个方法传入的第一个参数` $reflect` 为` testInjection` 方法的反射,第二个参数` $vars `是我们在请求的时候url后面传递的参数,这里我们不管,在 379 行执行了一个判断 `$reflect->getNumberOfParameters()` ,这行的意思是拿到` testInjection `这个方法需要传递几个参数,参考我们之前定义的 `testInjection `方法,我们一共传递了一个参数 `$Spreadsheet`,并且强制指定这个参数是个 `\PhpOffice\PhpSpreadsheet\Spreadsheet` 对象,而这个强制指定,就是后面依赖注入的依据。 > ![](https://box.kancloud.cn/e018b885f181c560a15822cd7aec44c6_992x867.png) > 代码走到 384 行,这里 `$reflect->getParameters()` 这个方法的实质是遍历传入的参数,上图圈起来的部分,`name `等于 `Spreadsheet `,这个 `Spreadsheet `就是定义 `testInjection `这个方法参数中的 `Spreadsheet`,而 `$param` 这个变量实质是个 `ReflectionParameter `对象,也就是 `testInjection` 方法里的参数`$Spreadsheet`的反射。 > ![](https://box.kancloud.cn/b7bde0d0b1b907c51e4988ddfa031062_1083x110.png) > 385 行调用了 ` self::getParamValue` 这个方法,这个就是根据强制指定对象类型实例化这个对象的方法了,我们跟进 > ![](https://box.kancloud.cn/56a3ab754e3de1c8213976e68a092927_1206x833.png) > 403 行,调用 `$param->getClass()` 方法,可以拿到`$Spreadsheet`参数强制指定的类型是`\PhpOffice\PhpSpreadsheet\Spreadsheet`,中间经过一系列的判断,我们不用管,一般用不到。在 422 行,这里有个一 `new $className `的动作,`\PhpOffice\PhpSpreadsheet\Spreadsheet`这个对象在这里被实例化,随后返回,返回的结果加到 `$args `这个变量上。 > ![](https://box.kancloud.cn/fed1a97f845a5d90ed3e5574c4697b36_1053x829.png) > 返回到 ` self::invokeMethod($call, $vars)` 方法,下面就是调用 `$reflect->invokeArgs` 执行我们定义的 `testInjection `方法,如果你明白 `invokeArgs `这个方法的用法,那么就一定知道这个参数是如何被传进去的。 > ![](https://box.kancloud.cn/b7a29f3cad2efa43274ab467e98119e2_1175x831.png) > 再次跟进的时候,会发现代码已经执行到了我们自己定义的方法里,至此,注入机制完成 > ![](https://box.kancloud.cn/f373c1eaa69f57c1a341e775fa352860_1100x496.png) >[danger] > **这里有朋友可能会发现,似乎整个过程都没有提及到控制反转和容器,事实上根据我对这个过程的观察,在tp5.0里确实没用到所谓的容器,但是这个跟依赖注入并不冲突,依赖注入的作用是实现类的自动实例化,而控制反转里的容器实质是为了实现一个实例的复用,跟单例模式类似,但是更高级,比较专业的说法是*注册树模式*,这点在tp5.1里面有运用到,大家可以自行查阅tp5.1的源码,控制反转管理类是`container`类,容器则是里面的 `$instances` 属性。篇幅关系,这里不再探讨** **其他参考** 如何理解php的依赖注入 http://www.cnblogs.com/phpper/p/7781119.html 理解 PHP 依赖注入 https://www.cnblogs.com/liliuguang/p/7825078.html PHP控制反转(IOC)和依赖注入(DI) https://www.cnblogs.com/sweng/p/6392336.html PHP程序员如何理解IoC/DI https://segmentfault.com/a/1190000002411255 学习依赖注入与控制反转 https://segmentfault.com/a/1190000010456858 PHP 依赖注入(DI) 和 控制反转(IoC)实例教程 http://www.php.cn/php-weizijiaocheng-367767.html