|
|
|
|
移动端

PHP如何实现依赖注入

控制反转(Inversion of Control,英文缩写为IoC)是框架的重要特征。控制反转(IOC)是一种思想,依赖注入(DI)是实施这种思想的方法。

作者:o0无忧亦无怖来源:o0无忧亦无怖的博客|2017-08-16 16:00

开发者盛宴来袭!7月28日51CTO首届开发者大赛决赛带来技术创新分享

PHP如何实现依赖注入

摘要: 控制反转(Inversion of Control,英文缩写为IoC)是框架的重要特征。控制反转(IOC)是一种思想,依赖注入(DI)是实施这种思想的方法。

高层模块不应该依赖于底层模块,两个都应该依赖抽象。

抽象不应该依赖于细节,细节应该依赖于抽象。

首先,我们来看一段代码:

  1. class A{ 
  2.         public function echo() 
  3.         { 
  4.                 echo 'A'.PHP_EOL; 
  5.         } 
  6. class EchoT { 
  7.         protected  $t; 
  8.         public function __construct() 
  9.         { 
  10.               $this->t = new A(); 
  11.         } 
  12.         public function echo(){ 
  13.                 $this->t->echo(); 
  14.         } 
  15.  

初始,我们都使用new 的方式在内部进行,EchoT类严重依赖于类A。每当类A变化时,EchoT类也得进行变化。

我们优化一下代码

  1. class EchoT { 
  2.         protected  $t; 
  3.         public function __construct($t)  //构造器注入由构造器注入到其中 
  4.         { 
  5.               $this->t = $t; 
  6.         }  

可以看到,这样做的话。很大程序上,我们对程序进行了解耦。类A无论你如何变动,EchoT类是不需要变动的。不再依赖于A。但是新问题又来了,我们现在只有A,万一来了B,来了CDEFG怎么办。

面向接口

  1. interface T{ 
  2.         public function echo(); 
  3.  
  4. class A{ 
  5.         public function echo() 
  6.         { 
  7.                 echo 'A'.PHP_EOL; 
  8.         } 
  9.  
  10. class B implements T{ 
  11.         public function echo() 
  12.         { 
  13.                 echo 'B'.PHP_EOL; 
  14.         } 
  15. class EchoT { 
  16.         protected  $t; 
  17.         public function __construct(T $t)  //构造器注入由构造器注入到其中 
  18.         { 
  19.               $this->t = $t; 
  20.         } 
  21.         public function echo(){ 
  22.                 $this->t->echo(); 
  23.         } 
  24.  

将T抽象出为接口,这样,EchoT类中的echo方法变成一个抽象的方法,不到运行那一刻,不知道他们的Method方式是怎么实现的。

工厂

  1. function getT($str) { 
  2.     if(class_exists($str)){ 
  3.         return new $str(); 
  4.         } 
  5.  

T要使用哪个是不明确的,因此,我们可以将其工厂化。【看上去很简单,在DI实际上有体现】

DI(重点来了)

首先,我们看一下PHP的psr规范。

http://www.php-fig.org/psr/psr-11/

官方定义的接口

Psr\Container\ContainerInterface

包含两个方法

function get($id);

function has($id);

仔细看上面的工厂,是不是和get($id)很一致,PHP官方将其定义为容器(Container,我个人理解,就是一个复杂的工厂)

dependency injection container

依赖注入容器

  1. namespace Core; 
  2. use Psr\Container\ContainerInterface; 
  3. class Container implements ContainerInterface 
  4.         protected $instance = [];//对象存储的数组 
  5.         public function __construct($path) { 
  6.                 $this->_autoload($path);  //首先我们要自动加载  psr-autoload 
  7.         } 
  8.  
  9.         public function build($className) 
  10.         { 
  11.                 if(is_string($className) and $this->has($className)) { 
  12.                         return $this->get($className); 
  13.                 } 
  14.                 //反射 
  15.                 $reflector = new \ReflectionClass($className); 
  16.                 if (!$reflector->isInstantiable()) { 
  17.                         throw new \Exception("Can't instantiate ".$className); 
  18.                 } 
  19.                 // 检查类是否可实例化, 排除抽象类abstract和对象接口interface 
  20.                 if (!$reflector->isInstantiable()) { 
  21.                         throw new \Exception("Can't instantiate ".$className); 
  22.                 } 
  23.                 /** @var \ReflectionMethod $constructor 获取类的构造函数 */ 
  24.                 $constructor = $reflector->getConstructor(); 
  25.                 // 若无构造函数,直接实例化并返回 
  26.                 if (is_null($constructor)) { 
  27.                         return new $className; 
  28.                 } 
  29.                 // 取构造函数参数,通过 ReflectionParameter 数组返回参数列表 
  30.                 $parameters = $constructor->getParameters(); 
  31.                 // 递归解析构造函数的参数 
  32.                 $dependencies = $this->getDependencies($parameters); 
  33.                 // 创建一个类的新实例,给出的参数将传递到类的构造函数。 
  34.                 $class =  $reflector->newInstanceArgs($dependencies); 
  35.                 $this->instance[$className] = $class; 
  36.                 return $class; 
  37.         } 
  38.  
  39.         /** 
  40.          * @param array $parameters 
  41.          * @return array 
  42.          */ 
  43.         public function getDependencies(array $parameters) 
  44.         { 
  45.                 $dependencies = []; 
  46.                 /** @var \ReflectionParameter $parameter */ 
  47.                 foreach ($parameters as $parameter) { 
  48.                         /** @var \ReflectionClass $dependency */ 
  49.                         $dependency = $parameter->getClass(); 
  50.                         if (is_null($dependency)) { 
  51.                                 // 是变量,有默认值则设置默认值 
  52.                                 $dependencies[] = $this->resolveNonClass($parameter); 
  53.                         } else { 
  54.                                 // 是一个类,递归解析 
  55.                                 $dependencies[] = $this->build($dependency->name); 
  56.                         } 
  57.                 } 
  58.                 return $dependencies; 
  59.         } 
  60.  
  61.         /** 
  62.          * @param \ReflectionParameter $parameter 
  63.          * @return mixed 
  64.          * @throws \Exception 
  65.          */ 
  66.         public function resolveNonClass(\ReflectionParameter $parameter) 
  67.         { 
  68.                 // 有默认值则返回默认值 
  69.                 if ($parameter->isDefaultValueAvailable()) { 
  70.                         return $parameter->getDefaultValue(); 
  71.                 } 
  72.                 throw new \Exception($parameter->getName().' must be not null'); 
  73.         } 
  74.         /** 
  75.          * 参照psr-autoload规范 
  76.          * @param $path 
  77.          */ 
  78.         public function _autoload($path) { 
  79.                 spl_autoload_register(function(string $class) use ($path) { 
  80.                         $file = DIRECTORY_SEPARATOR.str_replace('\\',DIRECTORY_SEPARATOR, $class).'.php'; 
  81.                         if(is_file($path.$file)) { 
  82.                                 include($path.$file); 
  83.                                 return true
  84.                         } 
  85.                         return false
  86.                 }); 
  87.         } 
  88.  
  89.         public function get($id) 
  90.         { 
  91.                 if($this->has($id)) { 
  92.                         return $this->instance[$id]; 
  93.                 } 
  94.                 if(class_exists($id)){ 
  95.                         return $this->build($id); 
  96.                 } 
  97.                 throw new ClassNotFoundException('class not found');  //实现的PSR规范的异常 
  98.         } 
  99.  
  100.         public function has($id) 
  101.         { 
  102.                 return isset($this->instance[$id]) ? true : false
  103.         } 
  104.  

使用示例

  1. $container = new Container('../');//假设这是路径 
  2. $echoT = $container->get(\Test\EchoT::class);     //假设echoT类的命名空间是\Test 
  3. $echoT->echo();  

这个时候,会出现一个问题:

  1. // 检查类是否可实例化, 排除抽象类abstract和对象接口interface 
  2.                 if (!$reflector->isInstantiable()) { 
  3.                         throw new \Exception("Can't instantiate ".$className);  

因为接口T是无法实例化的,所以,一般在程序内,我们都加上别名(参照laravel框架)

  1. $container->alisa(\Test\T::class,\Test\T\A::class); //指定接口T使用类A(控制反转) 

针对接口

下面是alias方法

  1. public function alias(string $key, $class, bool $singleton = true)  
  2.        { 
  3.                if($singleton) { 
  4.                        $this->singleton[] = $class; 
  5.                } 
  6.                $this->aliases[$key] = $class; 
  7.                return $this; 
  8.        } 
  9.    //同时,我们需要在build的时候进行判断是否为别名 
  10. public function build($className) 
  11.        { 
  12.                if(is_string($className) and $this->has($className)) { 
  13.                        return $this->get($className); 
  14.                } 
  15.                if(isset($this->aliases[$className])) { 
  16.                        if(is_object($this->aliases[$className])) { 
  17.                               return $this->aliases[$className]; 
  18.                        } 
  19.                        $className = $this->aliases[$className]; 
  20.                }  

就此,一个简单的PHP容器就实现了。

【编辑推荐】

  1. 放弃了 7 年的 Java,投身互联网做 PHP,我是如何成为创业公司的 CTO?
  2. [PHP内核探索]PHP中的哈希表
  3. PHP基础|如何解决中文乱码问题?
  4. 轻松建站,20个PHP开源内容管理系统(CMS)推荐
  5. 关于PHP协程与阻塞的思考
【责任编辑:庞桂玉 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

Wicked Cool Java中文版

本书主要介绍由Sun微系统公司创建的Java编程语言。 除了核心内容外,Java还有许多免费的财富,即开放源代码的库。本书就是为了介绍这些库...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊