高层模块不应该依赖于底层模块,两个都应该依赖抽象。
抽象不应该依赖于细节,细节应该依赖于抽象。
首先,我们来看一段代码:
1class A{ 2 public function echo() 3 { 4 echo 'A'.PHP_EOL; 5 } 6} 7class EchoT { 8 protected $t; 9 public function __construct() 10 { 11 $this->t = new A(); 12 } 13 public function echo(){ 14 $this->t->echo(); 15 } 16}
初始,我们都使用new 的方式在内部进行,EchoT类严重依赖于类A。每当类A变化时,EchoT类也得进行变化。
我们优化一下代码
1class EchoT { 2 protected $t; 3 public function __construct($t) //构造器注入由构造器注入到其中 4 { 5 $this->t = $t; 6 }
可以看到,这样做的话。很大程序上,我们对程序进行了解耦。类A无论你如何变动,EchoT类是不需要变动的。不再依赖于A。但是新问题又来了,我们现在只有A,万一来了B,来了CDEFG怎么办。
面向接口
1interface T{ 2 public function echo(); 3} 4 5class A{ 6 public function echo() 7 { 8 echo 'A'.PHP_EOL; 9 } 10} 11 12class B implements T{ 13 public function echo() 14 { 15 echo 'B'.PHP_EOL; 16 } 17} 18class EchoT { 19 protected $t; 20 public function __construct(T $t) //构造器注入由构造器注入到其中 21 { 22 $this->t = $t; 23 } 24 public function echo(){ 25 $this->t->echo(); 26 } 27}
将T抽象出为接口,这样,EchoT类中的echo方法变成一个抽象的方法,不到运行那一刻,不知道他们的Method方式是怎么实现的。
工厂
1function getT($str) { 2 if(class_exists($str)){ 3 return new $str(); 4 } 5}
T要使用哪个是不明确的,因此,我们可以将其工厂化。【看上去很简单,在DI实际上有体现】
DI(重点来了)
首先,我们看一下PHP的psr规范。
官方定义的接口
1Psr\Container\ContainerInterface 2包含两个方法 3function get($id); 4function has($id);
仔细看上面的工厂,是不是和get($id)很一致,PHP官方将其定义为容器(Container,我个人理解,就是一个复杂的工厂)
dependency injection container
依赖注入容器
1namespace Core; 2use Psr\Container\ContainerInterface; 3class Container implements ContainerInterface 4{ 5 protected $instance = [];//对象存储的数组 6 public function __construct($path) { 7 $this->_autoload($path); //首先我们要自动加载 psr-autoload 8 } 9 10 public function build($className) 11 { 12 if(is_string($className) and $this->has($className)) { 13 return $this->get($className); 14 } 15 //反射 16 $reflector = new \ReflectionClass($className); 17 if (!$reflector->isInstantiable()) { 18 throw new \Exception("Can't instantiate ".$className); 19 } 20 // 检查类是否可实例化, 排除抽象类abstract和对象接口interface 21 if (!$reflector->isInstantiable()) { 22 throw new \Exception("Can't instantiate ".$className); 23 } 24 /** @var \ReflectionMethod $constructor 获取类的构造函数 */ 25 $constructor = $reflector->getConstructor(); 26 // 若无构造函数,直接实例化并返回 27 if (is_null($constructor)) { 28 return new $className; 29 } 30 // 取构造函数参数,通过 ReflectionParameter 数组返回参数列表 31 $parameters = $constructor->getParameters(); 32 // 递归解析构造函数的参数 33 $dependencies = $this->getDependencies($parameters); 34 // 创建一个类的新实例,给出的参数将传递到类的构造函数。 35 $class = $reflector->newInstanceArgs($dependencies); 36 $this->instance[$className] = $class; 37 return $class; 38 } 39 40 /** 41 * @param array $parameters 42 * @return array 43 */ 44 public function getDependencies(array $parameters) 45 { 46 $dependencies = []; 47 /** @var \ReflectionParameter $parameter */ 48 foreach ($parameters as $parameter) { 49 /** @var \ReflectionClass $dependency */ 50 $dependency = $parameter->getClass(); 51 if (is_null($dependency)) { 52 // 是变量,有默认值则设置默认值 53 $dependencies[] = $this->resolveNonClass($parameter); 54 } else { 55 // 是一个类,递归解析 56 $dependencies[] = $this->build($dependency->name); 57 } 58 } 59 return $dependencies; 60 } 61 62 /** 63 * @param \ReflectionParameter $parameter 64 * @return mixed 65 * @throws \Exception 66 */ 67 public function resolveNonClass(\ReflectionParameter $parameter) 68 { 69 // 有默认值则返回默认值 70 if ($parameter->isDefaultValueAvailable()) { 71 return $parameter->getDefaultValue(); 72 } 73 throw new \Exception($parameter->getName().' must be not null'); 74 } 75 /** 76 * 参照psr-autoload规范 77 * @param $path 78 */ 79 public function _autoload($path) { 80 spl_autoload_register(function(string $class) use ($path) { 81 $file = DIRECTORY_SEPARATOR.str_replace('\\',DIRECTORY_SEPARATOR, $class).'.php'; 82 if(is_file($path.$file)) { 83 include($path.$file); 84 return true; 85 } 86 return false; 87 }); 88 } 89 90 public function get($id) 91 { 92 if($this->has($id)) { 93 return $this->instance[$id]; 94 } 95 if(class_exists($id)){ 96 return $this->build($id); 97 } 98 throw new ClassNotFoundException('class not found'); //实现的PSR规范的异常 99 } 100 101 public function has($id) 102 { 103 return isset($this->instance[$id]) ? true : false; 104 } 105}
使用示例
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); 4 } 5因为接口T是无法实例化的,所以,一般在程序内,我们都加上别名(参照laravel框架) 6$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容器就实现了。