PHP如何实现依赖注入

简介:

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容器就实现了。


作者:o0无忧亦无怖

来源:51CTO

相关文章
|
2月前
|
设计模式 开发框架 PHP
深入浅出PHP中的依赖注入:实现与实践
在软件开发中,依赖注入(Dependency Injection, DI)作为一种设计模式,已经被广泛用于提高代码的模块化和可测试性。PHP作为一门动态脚本语言,在现代Web开发框架中广泛应用。本文将探讨依赖注入的基本概念、实现方式,并结合PHP语言特性,展示如何在日常开发中有效利用依赖注入来构建更加灵活和可维护的应用程序。
|
4月前
|
SQL 程序员 PHP
PHP网页下的注入原理
PHP网页下的注入原理
|
6月前
|
SQL 监控 安全
代码审计-PHP原生开发篇&SQL注入&数据库监控&正则搜索&文件定位&静态分析
代码审计-PHP原生开发篇&SQL注入&数据库监控&正则搜索&文件定位&静态分析
|
7月前
|
SQL 安全 PHP
【PHP 开发专栏】PHP 防止 SQL 注入的方
【4月更文挑战第30天】本文介绍了PHP防止SQL注入的策略,包括理解SQL注入的原理和危害,如数据泄露和系统控制。推荐使用参数化查询(如PDO扩展)、过滤和验证用户输入,以及选择安全的框架和库(如Laravel)。此外,强调了保持警惕、定期更新维护和开发人员安全培训的重要性,以确保应用安全。
254 3
|
SQL 安全 PHP
理解php对象注入
php对象注入是一个非常常见的漏洞,这个类型的漏洞虽然有些难以利用,但仍旧非常危险,为了理解这个漏洞,请读者具备基础的php知识。
|
SQL 安全 PHP
PHP 什么是SQL注入,如何防止SQL注入,不防止会怎样(SQL注入详解)
PHP 什么是SQL注入,如何防止SQL注入,不防止会怎样(SQL注入详解)
272 0
|
SQL 安全 JavaScript
跨站脚本攻击 (XSS)和SQL注入漏洞php排查解决方案
跨站脚本攻击 (XSS)和SQL注入漏洞php排查解决方案
243 0
|
SQL 监控 关系型数据库
PHP审计-SQL注入技巧
PHP审计-SQL注入技巧
|
设计模式 测试技术 PHP
PHP为什么需要依赖注入?底层原理是什么?
PHP为什么需要依赖注入?底层原理是什么?
151 0
|
PHP 容器
php 中的 DI 依赖注入
学好依赖注入,让编程更简单
394 4
php 中的 DI 依赖注入