PHP实现一个轻量级容器的方法
什么是容器
在开发过程中,经常会用到的一个概率就是依赖注入。我们借助依懒注入来解耦代码,选择性的按需加载服务,而这些通常都是借助容器来实现。
容器实现对类的统一管理,并且确保对象实例的唯一性
常用的容器网上有很多,如PHP-DI、YII-DI等各种实现,通常他们要么大而全,要么高度适配特定业务,与实际需要存在冲突。
出于需要,我们自己造一个轻量级的轮子,为了保持规范,我们基于PSR-11来实现。
PSR-11
PSR是php-fig提供的标准建议,虽然不是官方组织,但是得到广泛认可。PSR-11提供了容器接口。他包含ContainerInterface和两个异常接口,提供使用建议。
/** *Describestheinterfaceofacontainerthatexposesmethodstoreaditsentries. */ interfaceContainerInterface { /** *Findsanentryofthecontainerbyitsidentifierandreturnsit. * *@paramstring$idIdentifieroftheentrytolookfor. * *@throwsNotFoundExceptionInterfaceNoentrywasfoundfor**this**identifier. *@throwsContainerExceptionInterfaceErrorwhileretrievingtheentry. * *@returnmixedEntry. */ publicfunctionget($id); /** *Returnstrueifthecontainercanreturnanentryforthegivenidentifier. *Returnsfalseotherwise. * *`has($id)`returningtruedoesnotmeanthat`get($id)`willnotthrowanexception. *Itdoeshowevermeanthat`get($id)`willnotthrowa`NotFoundExceptionInterface`. * *@paramstring$idIdentifieroftheentrytolookfor. * *@returnbool */ publicfunctionhas($id); }
实现示例
我们先来实现接口中要求的两个方法
abstractclassAbstractContainerimplementsContainerInterface { protected$resolvedEntries=[]; /** *@vararray */ protected$definitions=[]; publicfunction__construct($definitions=[]) { foreach($definitionsas$id=>$definition){ $this->injection($id,$definition); } } publicfunctionget($id) { if(!$this->has($id)){ thrownewNotFoundException("Noentryorclassfoundfor{$id}"); } $instance=$this->make($id); return$instance; } publicfunctionhas($id) { returnisset($this->definitions[$id]); }
实际我们容器中注入的对象是多种多样的,所以我们单独抽出实例化方法。
publicfunctionmake($name) { if(!is_string($name)){ thrownew\InvalidArgumentException(sprintf( 'Thenameparametermustbeoftypestring,%sgiven', is_object($name)?get_class($name):gettype($name) )); } if(isset($this->resolvedEntries[$name])){ return$this->resolvedEntries[$name]; } if(!$this->has($name)){ thrownewNotFoundException("Noentryorclassfoundfor{$name}"); } $definition=$this->definitions[$name]; $params=[]; if(is_array($definition)&&isset($definition['class'])){ $params=$definition; $definition=$definition['class']; unset($params['class']); } $object=$this->reflector($definition,$params); return$this->resolvedEntries[$name]=$object; } publicfunctionreflector($concrete,array$params=[]) { if($concreteinstanceof\Closure){ return$concrete($params); }elseif(is_string($concrete)){ $reflection=new\ReflectionClass($concrete); $dependencies=$this->getDependencies($reflection); foreach($paramsas$index=>$value){ $dependencies[$index]=$value; } return$reflection->newInstanceArgs($dependencies); }elseif(is_object($concrete)){ return$concrete; } } /** *@param\ReflectionClass$reflection *@returnarray */ privatefunctiongetDependencies($reflection) { $dependencies=[]; $constructor=$reflection->getConstructor(); if($constructor!==null){ $parameters=$constructor->getParameters(); $dependencies=$this->getParametersByDependencies($parameters); } return$dependencies; } /** * *获取构造类相关参数的依赖 *@paramarray$dependencies *@returnarray$parameters **/ privatefunctiongetParametersByDependencies(array$dependencies) { $parameters=[]; foreach($dependenciesas$param){ if($param->getClass()){ $paramName=$param->getClass()->name; $paramObject=$this->reflector($paramName); $parameters[]=$paramObject; }elseif($param->isArray()){ if($param->isDefaultValueAvailable()){ $parameters[]=$param->getDefaultValue(); }else{ $parameters[]=[]; } }elseif($param->isCallable()){ if($param->isDefaultValueAvailable()){ $parameters[]=$param->getDefaultValue(); }else{ $parameters[]=function($arg){ }; } }else{ if($param->isDefaultValueAvailable()){ $parameters[]=$param->getDefaultValue(); }else{ if($param->allowsNull()){ $parameters[]=null; }else{ $parameters[]=false; } } } } return$parameters; }
如你所见,到目前为止我们只实现了从容器中取出实例,从哪里去提供实例定义呢,所以我们还需要提供一个方水法
/** *@paramstring$id *@paramstring|array|callable$concrete *@throwsContainerException */ publicfunctioninjection($id,$concrete) { if(is_array($concrete)&&!isset($concrete['class'])){ thrownewContainerException('数组必须包含类定义'); } $this->definitions[$id]=$concrete; }
只有这样吗?对的,有了这些操作我们已经有一个完整的容器了,插箱即用。
不过为了使用方便,我们可以再提供一些便捷的方法,比如数组式访问。
classContainerextendsAbstractContainerimplements\ArrayAccess { publicfunctionoffsetExists($offset) { return$this->has($offset); } publicfunctionoffsetGet($offset) { return$this->get($offset); } publicfunctionoffsetSet($offset,$value) { return$this->injection($offset,$value); } publicfunctionoffsetUnset($offset) { unset($this->resolvedEntries[$offset]); unset($this->definitions[$offset]); } }
这样我们就拥有了一个功能丰富,使用方便的轻量级容器了,赶快整合到你的项目中去吧。
点击这里查看完整代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。