当前位置:首页 > PHP教程 > php高级应用 > 列表

php中依赖注入深入理解

发布:smiling 来源: PHP粉丝网  添加日期:2018-10-13 14:15:49 浏览: 评论:0 

PHP程序员如何理解依赖注入容器(dependency injection container)

背景知识

传统的思路是应用程序用到一个Foo类,就会创建Foo类并调用Foo类的方法,假如这个方法内需要一个Bar类,就会创建Bar类并调用Bar类的方法,而这个方法内需要一个Bim类,就会创建Bim类,接着做些其它工作。

  1. <?php 
  2. // 代码【1】 
  3. class Bim 
  4. { 
  5.     public function doSomething() 
  6.     { 
  7.         echo __METHOD__, '|'; 
  8.     } 
  9. } 
  10.  
  11. class Bar 
  12. { 
  13.     public function doSomething() 
  14.     { 
  15.         $bim = new Bim(); 
  16.         $bim->doSomething(); 
  17.         echo __METHOD__, '|'; 
  18.     } 
  19. } 
  20.  
  21. class Foo 
  22. { 
  23.     public function doSomething() 
  24.     { 
  25.         $bar = new Bar(); 
  26.         $bar->doSomething(); 
  27.         echo __METHOD__; 
  28.     } 
  29. } 
  30.  
  31. $foo = new Foo(); 
  32. $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething 

使用依赖注入的思路是应用程序用到Foo类,Foo类需要Bar类,Bar类需要Bim类,那么先创建Bim类,再创建Bar类并把Bim注入,再创建Foo类,并把Bar类注入,再调用Foo方法,Foo调用Bar方法,接着做些其它工作。

  1. <?php 
  2. // 代码【2】 
  3. class Bim 
  4. { 
  5.     public function doSomething() 
  6.     { 
  7.         echo __METHOD__, '|'; 
  8.     } 
  9. } 
  10.  
  11. class Bar 
  12. { 
  13.     private $bim; 
  14.  
  15.     public function __construct(Bim $bim) 
  16.     { 
  17.         $this->bim = $bim; 
  18.     } 
  19.  
  20.     public function doSomething() 
  21.     { 
  22.         $this->bim->doSomething(); 
  23.         echo __METHOD__, '|'; 
  24.     } 
  25. } 
  26.  
  27. class Foo 
  28. { 
  29.     private $bar; 
  30.  
  31.     public function __construct(Bar $bar) 
  32.     { 
  33.         $this->bar = $bar; 
  34.     } 
  35.  
  36.     public function doSomething() 
  37.     { 
  38.         $this->bar->doSomething(); 
  39.         echo __METHOD__; 
  40.     } 
  41. } 
  42.  
  43. $foo = new Foo(new Bar(new Bim())); 
  44. $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething 

这就是控制反转模式。依赖关系的控制反转到调用链的起点。这样你可以完全控制依赖关系,通过调整不同的注入对象,来控制程序的行为。例如Foo类用到了memcache,可以在不修改Foo类代码的情况下,改用redis。

使用依赖注入容器后的思路是应用程序需要到Foo类,就从容器内取得Foo类,容器创建Bim类,再创建Bar类并把Bim注入,再创建Foo类,并把Bar注入,应用程序调用Foo方法,Foo调用Bar方法,接着做些其它工作.

总之容器负责实例化,注入依赖,处理依赖关系等工作。

代码演示 依赖注入容器 (dependency injection container)

通过一个最简单的容器类来解释一下,这段代码来自 Twittee

  1. <?php 
  2.  
  3. class Container 
  4. { 
  5.     private $s = array(); 
  6.  
  7.     function __set($k, $c) 
  8.     { 
  9.         $this->s[$k] = $c; 
  10.     } 
  11.  
  12.     function __get($k) 
  13.     { 
  14.         return $this->s[$k]($this); 
  15.     } 
  16. } 

这段代码使用了魔术方法,在给不可访问属性赋值时,__set() 会被调用。读取不可访问属性的值时,__get() 会被调用。

  1. <?php 
  2.  
  3. $c = new Container(); 
  4.  
  5. $c->bim = function () { 
  6.     return new Bim(); 
  7. }; 
  8. $c->bar = function ($c) { 
  9.     return new Bar($c->bim); 
  10. }; 
  11. $c->foo = function ($c) { 
  12.     return new Foo($c->bar); 
  13. }; //phpfensi.com 
  14.  
  15. // 从容器中取得Foo 
  16. $foo = $c->foo; 
  17. $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething 

这段代码使用了匿名函数

再来一段简单的代码演示一下,容器代码来自simple di container

  1. <?php 
  2.  
  3. class IoC 
  4. { 
  5.     protected static $registry = []; 
  6.  
  7.     public static function bind($name, Callable $resolver) 
  8.     { 
  9.         static::$registry[$name] = $resolver; 
  10.     } 
  11.  
  12.     public static function make($name) 
  13.     { 
  14.         if (isset(static::$registry[$name])) { 
  15.             $resolver = static::$registry[$name]; 
  16.             return $resolver(); 
  17.         } 
  18.         throw new Exception('Alias does not exist in the IoC registry.'); 
  19.     } 
  20. } 
  21.  
  22. IoC::bind('bim', function () { 
  23.     return new Bim(); 
  24. }); 
  25. IoC::bind('bar', function () { 
  26.     return new Bar(IoC::make('bim')); 
  27. }); 
  28. IoC::bind('foo', function () { 
  29.     return new Foo(IoC::make('bar')); 
  30. }); 
  31.  
  32.  
  33. // 从容器中取得Foo 
  34. $foo = IoC::make('foo'); 
  35. $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething 

这段代码使用了后期静态绑定

依赖注入容器 (dependency injection container) 高级功能

真实的dependency injection container会提供更多的特性,如:

自动绑定(Autowiring)或 自动解析(Automatic Resolution)

注释解析器(Annotations)

延迟注入(Lazy injection)

下面的代码在Twittee的基础上,实现了Autowiring。

  1. <?php 
  2.  
  3. class Bim 
  4. { 
  5.     public function doSomething() 
  6.     { 
  7.         echo __METHOD__, '|'; 
  8.     } 
  9. } 
  10.  
  11. class Bar 
  12. { 
  13.     private $bim; 
  14.  
  15.     public function __construct(Bim $bim) 
  16.     { 
  17.         $this->bim = $bim; 
  18.     } 
  19.  
  20.     public function doSomething() 
  21.     { 
  22.         $this->bim->doSomething(); 
  23.         echo __METHOD__, '|'; 
  24.     } 
  25. } 
  26.  
  27. class Foo 
  28. { 
  29.     private $bar; 
  30.  
  31.     public function __construct(Bar $bar) 
  32.     { 
  33.         $this->bar = $bar; 
  34.     } 
  35.  
  36.     public function doSomething() 
  37.     { 
  38.         $this->bar->doSomething(); 
  39.         echo __METHOD__; 
  40.     } 
  41. } 
  42.  
  43. class Container 
  44. { 
  45.     private $s = array(); 
  46.  
  47.     public function __set($k, $c) 
  48.     { 
  49.         $this->s[$k] = $c; 
  50.     } 
  51.  
  52.     public function __get($k) 
  53.     { 
  54.         // return $this->s[$k]($this); 
  55.         return $this->build($this->s[$k]); 
  56.     } 
  57.  
  58.     /** 
  59.      * 自动绑定(Autowiring)自动解析(Automatic Resolution) 
  60.      * 
  61.      * @param string $className 
  62.      * @return object 
  63.      * @throws Exception 
  64.      */ 
  65.     public function build($className) 
  66.     { 
  67.         // 如果是匿名函数(Anonymous functions),也叫闭包函数(closures) 
  68.         if ($className instanceof Closure) { 
  69.             // 执行闭包函数,并将结果 
  70.             return $className($this); 
  71.         } 
  72.  
  73.         /** @var ReflectionClass $reflector */ 
  74.         $reflector = new ReflectionClass($className); 
  75.  
  76.         // 检查类是否可实例化, 排除抽象类abstract和对象接口interface 
  77.         if (!$reflector->isInstantiable()) { 
  78.             throw new Exception("Can't instantiate this."); 
  79.         } 
  80.  
  81.         /** @var ReflectionMethod $constructor 获取类的构造函数 */ 
  82.         $constructor = $reflector->getConstructor(); 
  83.  
  84.         // 若无构造函数,直接实例化并返回 
  85.         if (is_null($constructor)) { 
  86.             return new $className; 
  87.         } 
  88.  
  89.         // 取构造函数参数,通过 ReflectionParameter 数组返回参数列表 
  90.         $parameters = $constructor->getParameters(); 
  91.  
  92.         // 递归解析构造函数的参数 
  93.         $dependencies = $this->getDependencies($parameters); 
  94.  
  95.         // 创建一个类的新实例,给出的参数将传递到类的构造函数。 
  96.         return $reflector->newInstanceArgs($dependencies); 
  97.     } 
  98.  
  99.     /** 
  100.      * @param array $parameters 
  101.      * @return array 
  102.      * @throws Exception 
  103.      */ 
  104.     public function getDependencies($parameters) 
  105.     { 
  106.         $dependencies = []; 
  107.  
  108.         /** @var ReflectionParameter $parameter */ 
  109.         foreach ($parameters as $parameter) { 
  110.             /** @var ReflectionClass $dependency */ 
  111.             $dependency = $parameter->getClass(); 
  112.  
  113.             if (is_null($dependency)) { 
  114.                 // 是变量,有默认值则设置默认值 
  115.                 $dependencies[] = $this->resolveNonClass($parameter); 
  116.             } else { 
  117.                 // 是一个类,递归解析 
  118.                 $dependencies[] = $this->build($dependency->name); 
  119.             } 
  120.         } 
  121.  
  122.         return $dependencies; 
  123.     } 
  124.  
  125.     /** 
  126.      * @param ReflectionParameter $parameter 
  127.      * @return mixed 
  128.      * @throws Exception 
  129.      */ 
  130.     public function resolveNonClass($parameter) 
  131.     { 
  132.         // 有默认值则返回默认值 
  133.         if ($parameter->isDefaultValueAvailable()) { 
  134.             return $parameter->getDefaultValue(); 
  135.         } 
  136.  
  137.         throw new Exception('I have no idea what to do here.'); 
  138.     } 
  139. } 
  140.  
  141. // ---- 
  142. $c = new Container(); 
  143. $c->bar = 'Bar'; 
  144. $c->foo = function ($c) { 
  145.     return new Foo($c->bar); 
  146. }; 
  147. // 从容器中取得Foo 
  148. $foo = $c->foo; 
  149. $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething 
  150.  
  151. // ---- 
  152. $di = new Container(); 
  153.  
  154. $di->foo = 'Foo'; 
  155.  
  156. /** @var Foo $foo */ 
  157. $foo = $di->foo; 
  158.  
  159. var_dump($foo); 
  160. /* 
  161. Foo#10 (1) { 
  162.   private $bar => 
  163.   class Bar#14 (1) { 
  164.     private $bim => 
  165.     class Bim#16 (0) { 
  166.     } 
  167.   } 
  168. } 
  169. */ 
  170.  
  171. $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething 

以上代码的原理参考PHP官方文档:反射,PHP 5 具有完整的反射 API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。

Tags: php依赖注入 php注入

分享到: