当前位置: 首页 > 科技观察

PHP如何实现依赖注入

时间:2023-03-12 14:02:26 科技观察

摘要:控制反转(InversionofControl,简称IoC)是框架的一个重要特性。控制反转(IOC)是一种思想,依赖注入(DI)是实现它的方式。高级模块不应该依赖于低级模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。首先我们看一段代码:$this->t->echo();}}最初,我们都在内部使用新方法,EchoT类严重依赖于类A。每当类A更改时,EchoT类也必须更改。让我们优化代码classEchoT{protected$t;publicfunction__construct($t)//constructorinjection是由构造函数注入其中的{$this->t=$t;}这样做就可以看出来了在大型程序中,我们将程序解耦。不管你怎么改A类,EchoT类都不需要改。不再依赖A了。但是新的问题又出现了,我们现在只有A了,如果B来了,CDEFG来了怎么办。interfaceT{publicfunctionecho();}classA{publicfunctionecho(){echo'A'.PHP_EOL;}}classBimplementsT{publicfunctionecho(){echo'B'.PHP_EOL;}}classEchoT{protected$t;publicfunction__construct(T$t)//constructorinjection是由构造函数注入其中的{$this->t=$t;}publicfunctionecho(){$this->t->echo();}}将T抽象为一个接口,所以,EchoT的echo方法类中变成了抽象方法,直到运行的那一刻,才知道他们的Method方法是怎么实现的。工厂函数getT($str){if(class_exists($str)){returnnew$str();}}使用哪个T是不明确的,所以我们可以工厂化它。【看起来很简单,其实在DI中体现】DI(重点来了)首先我们来看一下PHP的psr规范。http://www.php-fig.org/psr/psr-11/官方定义的接口Psr\Container\ContainerInterface包含两个方法functionget($id);函数有($id);仔细看上面的工厂,是不是很符合get($id),PHP官方把它定义为容器(Container,我个人的理解,是一个复杂的工厂)dependencyinjectioncontainer依赖注入容器namespaceCore;usePsr\Container\ContainerInterface;classContainerimplementsContainerInterface{protected$instance=[];//对象存储数组publicfunction__construct($path){$this->_autoload($path);//首先我们需要自动加载psr-autoload}publicfunctionbuild($className){if(is_string($className)and$this->has($className)){return$this->get($className);}//反射$reflector=new\ReflectionClass($className);if(!$reflector->isInstantiable()){thrownew\Exception("Can'instantiate".$className);}//检查类是否可以实例化,排除抽象类abstract和对象接口interfaceif(!$reflector->isInstantiable()){thrownew\Exception("Can'tinstantiate".$className);}/**@var\ReflectionMethod$constructor获取构造函数roftheclass*/$constructor=$reflector->getConstructor();//如果没有构造函数,直接实例化返回if(is_null($constructor)){returnnew$className;}//取构造函数参数,通过ReflectionParameter数组返回参数列表$parameters=$constructor->getParameters();//递归解析构造函数的参数$dependencies=$this->getDependencies($参数);//创建类的新实例,给定的参数将传递给类的构造函数$class=$reflector->newInstanceArgs($dependencies);$this->instance[$className]=$class;返回$class;}/***@paramarray$parameters*@returnarray*/publicfunctiongetDependencies(array$parameters){$dependencies=[];/**@var\ReflectionParameter$parameter*/foreach($parametersas$parameter){/**@var\ReflectionClass$dependency*/$dependency=$parameter->getClass();if(is_null($dependency)){//是一个变量,如果有默认值,设置默认值$dependencies[]=$this->resolveNonClass($parameter);}else{//是一个类,递归解析$dependencies[]=$this->build($dependency->name);}}return$dependencies;}/***@param\ReflectionParameter$parameter*@returnmixed*@throws\Exception*/publicfunctionresolveNonClass(\ReflectionParameter$parameter){//如果有默认值,返回默认值if($parameter->isDefaultValueAvailable()){return$parameter->getDefaultValue();}thrownew\Exception($parameter->getName().'mustbenotnull');}/***参考psr-autoload规范*@param$路径*/publicfunction_autoload($path){spl_autoload_register(函数(string$class)use($path){$file=DIRECTORY_SEPARATOR.str_replace('\\',DIRECTORY_SEPARATOR,$class).'.php';if(is_file($path.$file)){include($path.$file);returntrue;}returnfalse;});}publicfunctionget($id){if($this->has($id)){return$this->instance[$id];}if(class_exists($id)){return$this->build($id);}thrownewClassNotFoundException('classnotfound');//实现的PSR规范的异常}publicfunctionhas($id){returnisset($this->instance[$id])?true:false;}}使用示例$container=newContainer('../');//假设这是路径$echoT=$container->get(\Test\EchoT::class);//假设echoT类的命名空间是\Test$echoT->echo();这时候就会出现问题://检查类是否可以实例化,排除抽象类abstract和对象接口interfaceif(!$reflector->isInstantiable()){thrownew\Exception("Can'instantiate".$班级名称);因为接口T不能实例化,一般在程序中,我们加一个别名(参考laravel框架)$container->alisa(\Test\T::class,\Test\T\A::class);//指定接口T为接口使用类A(控制反转)。下面是别名方法publicfunctionalalias(string$key,$class,bool$singleton=true){if($singleton){$this->singleton[]=$class;}$this->aliases[$key]=$类;返回$this;}//同时在构建时需要判断是否是别名publicfunctionbuild($className){if(is_string($className)and$this->has($className)){return$this->get($className);}if(isset($this->aliases[$className])){if(is_object($this->aliases[$className])){return$this->aliases[$className];}$className=$this->aliases[$className];}至此,一个简单的PHP容器就实现了