PHP DIY系列–一起手写一个api框架
依赖倒置原则(Dependence Inversion Principle) DIP是面向对象设计原则之一。 传统软件设计中,上层代码依赖于下层代码,当下层出现变动时, 上层代码也要相应变化,维护成本较高。而DIP的核心思想是上层定义接口,下层实现这个接口, 从而使得下层依赖于上层,降低耦合度,提高整个系统的弹性。这是一种经实践证明的有效策略。
控制反转(Inversion of Control) IoC则是DIP的一种具体思路,DIP只是一种理念、思想,而IoC是一种实现DIP的方法。 IoC的核心是将类(上层)所依赖的单元(下层)的实例化过程交由第三方来实现。
当调用者需要被调用者的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例,但在这里,创建被调用者的工作不再由调用者来完成,而是将被调用者的创建移到调用者的外部,从而反转被调用者的创建,消除了调用者对被调用者创建的控制,因此称为控制反转。
依赖注入(Dependence Injection) DI是IoC的一种设计模式,按照DI的模式,就可以实现IoC。 DI的实质就是把一个类不可能更换的部分和可更换的部分分离开来,通过注入的方式来使用,从而达到解耦的目的。
这里我们举个例子(旅行的接口)说明一下:
interface Travel { public function travelAlgorithm(); } /** *乘坐飞机 */ class AirPlanelStrategy implements Travel { public function travelAlgorithm() { echo"travelbyAirPlain\r\n"; } } /** *乘坐火车 */ class TrainStrategy implements Travel { public function travelAlgorithm() { echo"travelbyTrain\r\n"; } } /** * *算法解决类,以提供客户选择使用何种解决方案: */ class PersonContext { private $strategy = null; public function __construct(Travel $travel) { $this->strategy=$travel; } /** *旅行 */ public function travel() { return$this->strategy->travelAlgorithm(); } } // 乘坐火车旅行 $person = new PersonContext(new TrainStrategy()); $person->travel(); // 改乘飞机 $person =PersonContext(new AirPlanelStrategy()); $person->travel();
当我们更换交通工具时,只需要去增加Travel接口的实现,修改下实现的代码接口,无需去改动核心代码。
控制反转容器(IoC Container) 当项目比较大时,依赖关系可能会十分复杂。 而IoC Container提供了动态地创建、注入依赖单元,映射依赖关系等功能,方便开发者使用,并大大缩减了许多代码量。
因为我们的框架比较简单,我们不妨实现下Ioc容器(主要参考了Yii的di容器)。
代码实现用到了PHP的反射Api,有疑问的不妨先看看手册:
还记得PSR吗?PSR11是关于依赖注入容器接口规范:
然后我们利用composer执行
composer require psr/container
我们在library/Components新建Container实现ContainerInterface,在library/Exceptions下新建ContainerException实现Psr\Container\ContainerExceptionInterface,新建ContainerNotFoundException实现Psr\Container\NotFoundExceptionInterface。
我们主要来实现一下Container代码,我们预先定义三个属性,用以保存对象、依赖及依赖的定义信息。
<?php namespace Library\Components; use Library\Exceptions\ContainerException; use Library\Exceptions\ContainerNotFoundException; use Psr\Container\ContainerInterface; use ReflectionClass; class Container implements ContainerInterface { // 用于保存依赖的定义,以对象名称为键 private $definitions = []; // 用于缓存ReflectionClass对象,以对象名称为键 private $reflections = []; // 用于缓存依赖信息,以对象名称为键 private $dependencies = []; public function has($class) { return isset($this->definitions[$class]); } public function get($class) { ... } }
我们先添加一个set方法,用以定义,
public function set($class, $definition = []) { $this->definitions[$class] = $this->normalizeDefinition($class, $definition); return $this; } protected function normalizeDefinition($class, $definition) { // $definition 是空的转换成 ['class' => $class] 形式 if (empty($definition)) { return ['class' => $class]; // $definition 是字符串,转换成 ['class' => $definition] 形式 } elseif (is_string($definition)) { return ['class' => $definition]; // $definition 是对象,则直接将其作为依赖的定义 } elseif (is_object($definition)) { return $definition; // $definition 是数组则确保该数组定义了 class 元素 } elseif (is_array($definition)) { if (!isset($definition['class'])) { $definition['class'] = $class; } return $definition; // 这也不是,那也不是,那就抛出异常算了 } else { throw new ContainerException( "不支持的类型: \"$class\": " . gettype($definition)); } }
知识点:
然后我们重点实现get方法:
public function get($class) { // 加入未作set操作,我们依旧可以构建 if (!isset($this->definitions[$class])) { return $this->build($class); } $definition = $this->definitions[$class]; if (is_array($definition)) { $concrete = $definition['class']; unset($definition['class']); if ($concrete === $class) { $object = $this->build($class, $definition); } else { $object = $this->get($concrete); } } elseif (is_object($definition)) { return $this->_singletons[$class] = $definition; } else { throw new ContainerNotFoundException('不能识别的对象类型: ' . gettype($definition)); } return $object; }
build方法如下,主要是构建出对象并实现注入,
public function build($class, $params = []) { try { // 通过反射api获取对象 $reflector = $this->getReflectionClass($class); // 获取依赖关系数组 $dependencies = $this->getDependencies($class, $reflector); // 创建一个类的新实例,给出的参数将传递到类的构造函数. $reflector = $reflector->newInstanceArgs($dependencies); return $reflector; } catch (\Throwable $t) { throw new ContainerException('反射出错'); } }
获取对象:
public function getReflectionClass($class) { if (isset($this->reflections[$class])) { return $this->reflections[$class]; } $reflector = new ReflectionClass($class); if (!$reflector->isInstantiable()) { throw new ContainerException("不能实例化".$class); } return $this->reflections[$class] = $reflector; }
获取依赖关系:
public function getDependencies($class, $reflector) { // 判断是否有缓存依赖关系 if (isset($this->dependencies[$class])) { return $this->dependencies[$class]; } $constructor = $reflector->getConstructor(); #如果没有构造函数, 直接实例化并返回 if (is_null($constructor)) { return $this->dependencies[$class] = []; } $parameters = $constructor->getParameters(); $dependencies = []; foreach ($parameters as $className) { $dependency = $className->getClass(); if (is_null($dependency)) { $dependencies[] = $this->resolveNoneClass($className); } else { // 先取出容器中绑定的类 否则自动绑定 $dependencies[] = $this->get($dependency->getName()); } } $this->dependencies[$class] = $dependencies; return $dependencies; } public function resolveNoneClass($class) { // 有默认值则返回默认值 if ($class->isDefaultValueAvailable()) { return $class->getDefaultValue(); } throw new ContainerException('不能解析参数'); }
到这里,我们基本就完成了一个完整的IOC Container的代码。
我们来写一个demo:
<?php namespace App\Https\Controllers; class C { }
<?php namespace App\Https\Controllers; class B { public function __construct(C $c) { $this->ccc = $c; } }
<?php namespace App\Https\Controllers; class A { public function __construct(B $b, C $c) { $this->bbb = $b; $this->ccc = $c; } }
<?php namespace App\Https\Controllers; use Library\Components\Container; use Library\Https\Controller; class IndexController extends Controller { public function index() { $contain = new Container(); $contain->set('App\\Https\\Controllers\\A'); p($contain->get('App\\Https\\Controllers\\A')); } }
我们可以看到的结果:
是不是代码简洁很多了,不愿因为需要创建一个A对象,而先去实例化B和C,这些都是由我们完成的IOC Container去实现了。
总结 这一节我们实现了IOC容器,然而你如果仔细去想,我们会发现我们可以让容器更加强大,比如单例对象的实现,比如依赖的扩展(兼容对象参数注入,数组参数注入等)。这些你可以自行实现,我也在源代码做了简单的扩展,大家可以思考试着实现一下,当然也可以看看开源框架Laravel、Yii的服务容器的实现。