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

相关文章
|
21天前
|
SQL 程序员 PHP
PHP网页下的注入原理
PHP网页下的注入原理
|
3月前
|
SQL 监控 安全
代码审计-PHP原生开发篇&SQL注入&数据库监控&正则搜索&文件定位&静态分析
代码审计-PHP原生开发篇&SQL注入&数据库监控&正则搜索&文件定位&静态分析
|
4月前
|
SQL 安全 PHP
【PHP 开发专栏】PHP 防止 SQL 注入的方
【4月更文挑战第30天】本文介绍了PHP防止SQL注入的策略,包括理解SQL注入的原理和危害,如数据泄露和系统控制。推荐使用参数化查询(如PDO扩展)、过滤和验证用户输入,以及选择安全的框架和库(如Laravel)。此外,强调了保持警惕、定期更新维护和开发人员安全培训的重要性,以确保应用安全。
181 3
|
11月前
|
SQL 安全 PHP
理解php对象注入
php对象注入是一个非常常见的漏洞,这个类型的漏洞虽然有些难以利用,但仍旧非常危险,为了理解这个漏洞,请读者具备基础的php知识。
|
11月前
|
SQL 安全 PHP
PHP 什么是SQL注入,如何防止SQL注入,不防止会怎样(SQL注入详解)
PHP 什么是SQL注入,如何防止SQL注入,不防止会怎样(SQL注入详解)
227 0
|
SQL 安全 JavaScript
跨站脚本攻击 (XSS)和SQL注入漏洞php排查解决方案
跨站脚本攻击 (XSS)和SQL注入漏洞php排查解决方案
197 0
|
SQL 监控 关系型数据库
PHP审计-SQL注入技巧
PHP审计-SQL注入技巧
|
设计模式 测试技术 PHP
PHP为什么需要依赖注入?底层原理是什么?
PHP为什么需要依赖注入?底层原理是什么?
119 0
Cyi
|
PHP 容器
php 控制反转和依赖注入
曾经网上找了很久的概念,发现很多讲的都是很深奥的概念和复杂的哲学。今日朋友突然问我关于控制反转和依赖注入的理解,闲来无事就将其写成了本文,文笔稚嫩还请海涵,作为一个笨比,我有笨比自己的理解方式,如果你也在苦恼这些深奥的概念,那不如先看看笨比的理解。
Cyi
98 0
|
PHP 容器
php 中的 DI 依赖注入
学好依赖注入,让编程更简单
385 4
php 中的 DI 依赖注入