当前位置: 首页 > 后端技术 > PHP

PHP之反射、控制反转与依赖注入

时间:2023-03-29 20:51:39 PHP

我的博文来自Inn的博客:http://gog5.cn/archives/8/第一次开博客,欢迎小伙伴们点赞交流友情链接什么是IoC?建议先看看IoC基础知识。写得很好。InversionofControl,即“控制反转”,不是一种技术,而是一种设计思想。ioc的意思是把你设计好的对象交给容器去控制,而不是传统的在你的对象内部直接控制。传统编程中,我们通过new直接在对象内部创建对象,程序主动创建依赖对象;而IoC有专门的容器来创建这些对象,即对象的创建是由Ioc容器控制的。传统应用是在对象中自己控制直接获取依赖对象,即正向旋转,而反向是让容器帮助创建和注入依赖对象。为什么会反过来呢?因为容器帮我们找到并注入依赖对象,对象只是被动接受依赖对象,依赖对象的获取是逆向的。database=newMysql();}publicfunctioninsertLog(){$this->database->insert();}}$app=newApp();$app->insertLog();上面的代码实现了写入数据库日志的功能,貌似没什么问题,但是如果想把Mysql改成Oracle数据库,就得重新修改App类了。这实际上并没有实现解耦。我们可以稍微修改一下。应用类{受保护的$数据库;公共函数__construct(数据库$database){$this->database=$database;}publicfunctioninsertLog(){$this->database->insert();}}$app=newApp(newOracle());$app->insertLog();现在可以通过构造函数传递参数来修改数据库,从外部注入依赖对象。App对象只是被动的接受依赖对象,所以反过来;哪些方面发生了逆转?依赖对象的获取是反向的,我们可以称之为控制反转。IoC和DIDI—DependencyInjection,即“依赖注入”:组件之间的依赖关系由容器在运行时决定。形象地讲,容器动态地向组件中??注入了一定的依赖。依赖注入的目的不是给软件系统带来更多的功能,而是增加组件重用的频率,为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,无需任何代码,指定目标需要的资源,完成自己的业务逻辑,而不用关心具体的资源来自哪里,由谁来实现。理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入什么”,那么我们深入分析一下:谁依赖谁:当然应用依赖于IoC容器;为什么需要依赖:application程序需要IoC容器来提供对象需要的外部资源;whoinjectwhom:很明显IoC容器注入了一个应用程序的对象,应用程序依赖的对象;注入的是什么:就是注入一个对象需要的外部资源(包括对象、资源、常量数据)。我们看一下thinkphp6=app('org\utils\ArrayItem');容器中的实例方法$arrayItem这里实际上调用了底层think\Container的invokeClass方法publicfunctioninvokeClass(string$class,array$vars=[]){try{$reflect=newReflectionClass($class);}catch(ReflectionException$e){thrownewClassNotFoundException('类不存在:'.$class,$class,$e);}if($reflect->hasMethod('__make')){$method=$reflect->getMethod('__make');如果($method->isPublic()&&$method->isStatic()){$args=$this->bindParams($method,$vars);返回$method->invokeArgs(null,$args);}}$constructor=$reflect->getConstructor();$args=$构造函数?$this->bindParams($constructor,$vars):[];$object=$reflect->newInstanceArgs($args);$this->invokeAfter($class,$object);return$object;}原来tp是用反射来实现依赖注入的,我们看到这一行,它实例化了一个反射类。$reflect=newReflectionClass($class);反射是指在PHP运行状态下对PHP程序进行扩展和分析,导出或提取类、方法、属性、参数等详细信息,包括注释。这种动态获取信息,动态调用对象方法的功能,称为反射API。反射类的方法是:https://www.php.net/manual/zh/book.reflection.phpReflectionClass::__construct—初始化ReflectionClass类ReflectionClass::export—导出一个类ReflectionClass::getConstant—获取一个定义好的常量ReflectionClass::getConstants—获取一组常量ReflectionClass::getConstructor—获取类构造函数ReflectionClass::getDefaultProperties—获取默认属性ReflectionClass::getDocComment—获取文档注释ReflectionClass::getEndLine—获取最后一行的编号ReflectionClass::getExtension—根据定义类获取扩展的ReflectionExtension对象ReflectionClass::getExtensionName—获取定义类为扩展的名称ReflectionClass::getFileName—获取定义类的文件名ReflectionClass::getInterfaceNames—获取接口名称ReflectionClass::getInterfaces—获取接口ReflectionClass::getMethod—获取一个的ReflectionMethod类方法。ReflectionClass::getMethods—获取方法数组ReflectionClass::getModifiers—获取类的修饰符ReflectionClass::getName—获取类名ReflectionClass::getNamespaceName—获取命名空间的名称ReflectionClass::getParentClass—获取父类ReflectionClass::getProperties—获取一组属性ReflectionClass::getProperty—获取类属性的ReflectionPropertyReflectionClass::getReflectionConstant—获取类常量的ReflectionClassConstantReflectionClass::getReflectionConstants—获取类常量ReflectionClass::getShortName—获取短名称ReflectionClass::getStartLine—获取起始行号ReflectionClass::getStaticProperties—获取静态属性ReflectionClass::getStaticPropertyValue—获取静态属性的值ReflectionClass::getTraitAliases—返回特征别名数组ReflectionClass::getTraitNames—返回此类ArrayReflectionClass::ge使用的特征的名称tTraits—返回此类使用的特征数组ReflectionClass::hasConstant—检查是否定义了常量ReflectionClass::hasMethod—检查是否定义了方法ReflectionClass::hasProperty—检查是否定义了属性ReflectionClass::implementsInterface—接口反射类的实现s::inNamespace—检查它是否在命名空间中ReflectionClass::isAbstract—检查一个类是否是抽象的ReflectionClass::isAnonymous—检查一个类是否是匿名的ReflectionClass::isCloneable—返回一个类是否是可克隆的ReflectionClass:isFinal—检查类是否声明为finalReflectionClass::isInstance—检查类的实例ReflectionClass::isInstantiable—检查类是否可实例化ReflectionClass::isInterface—检查类是否为接口ReflectionClass::isInternal—检查类是否由扩展或核心在内部定义ReflectionClass::isIterable—检查此类是否可迭代ReflectionClass::isIterateable—检查是否可迭代—从指定的参数创建一个新的类实例ReflectionClass::newInstanceArgs—从给定的参数创建一个新的类实例■ReflectionClass::newInstanceWithoutConstructor—创建类的新实例而不调用其构造函数ReflectionClass::setStaticPropertyValue—设置静态属性的值ReflectionClass::__toString—返回ReflectionClass对象的字符串表示形式。知道了反射类的基本方法之后,我们来实现一个不需要new对应类就可以实例化对象的方法:functionmake($class){//创建该类的反射类$reflect=newReflectionClass($class);//获取构造函数$constructor=$reflect->getConstructor();//没有构造函数,直接返回实例if(is_null($constructor)){return$reflect->newInstance();}//获取构造函数参数$parameters=$constructor->getParameters();//传入参数实例化类上面的$std和$std2对象是一样的。但是我们还没有实现依赖注入,所以让我们做一些改变。...类应用程序{受保护的$数据库;公共函数__construct(Oracle$database){$this->database=$database;}publicfunctioninsertLog(){$this->database->insert();}}functionmake($class){$reflect=newReflectionClass($class);$constructor=$reflect->getConstructor();如果(is_null($constructor)){返回$reflect->newInstance();}//返回构造函数$instances=getParameters($constructor);返回$reflect->newInstanceArgs($instances);}functiongetParameters($constructor){$parameters=$constructor->getParameters();$dependencies=[];foreach($parametersas$parameter){//调用make方法递归获取实例//$parameter->getClass()->name获取类型提示类名,如functionTest(App$app),这是App$dependencies[]=make($parameter->getClass()->name);}返回$dependencies;}$app=make('App');$app->insertLog();上面程序中的IoC容器自动实例化了Oracle类,并注入到App类中,所以我们不需要手动传入,我们可以称之为依赖注入解耦。上面的代码还没有完全实现解耦。如果在很多页面的结构中注入了Mysql,但有一天系统会换成Oracle来记录日志,那我们岂不是要把所有的页面都换成Oracle?而且,在之前的课程聊thinkphp的服务容器(https://www.gog5.cn/archives/6/)中,我们还没有实现IoC和DI。所以我们结合上一节讲thinkphp的服务容器来实现依赖注入。接口数据库{publicfunctioninsert();}classMysqlimplementsDatabase{publicfunctioninsert(){echo'Mysqlinsert';}}classOracle实现数据库{publicfunctioninsert(){echo'Oracleinsert';}}classApp{protected$database;公共函数__construct(数据库$database){$this->database=$database;}publicfunctioninsertLog(){$this->database->insert();}}classContainer{protected$bind=[];/***@paramstring$abstract类标准、接口*@paramstring$concrete要绑定的类*/publicfunctionbind($abstract,$concrete){$this->bind[$abstract]=$this->建造($混凝土);}publicfunctionmake($abstract){if(!isset($this->bind[$abstract])){$this->bind($abstract,$abstract);}返回$this->bind[$abstract];}//相对于之前的make方法publicfunctionbuild($abstract){$reflect=newReflectionClass($abstract);$constructor=$reflect->getConstructor();如果(is_null($constructor)){返回$reflect->newInstance();}$instances=$this->getParameters($constructor);返回$reflect->newInstanceArgs($instances);}保护函数getParameters($constructor){$parameters=$constructor->getParameters();$dependencies=[];foreach($parametersas$parameter){$dependencies[]=$this->make($parameter->getClass()->name);}返回$dependencies;}}$container=newContainer();$container->bind('Database','Mysql');$app=$container->make('App');$app->insertLog();我们将App构建的类提示由Oracle类改为Database接口,让容器自己决定注入数据库类:publicfunction__construct(Database$database)如何让容器决定注入的类,在bind方法,首先实例化Mysql类,然后将其映射到Database接口。里面。$container->bind('数据库','Mysql');以后需要换数据库的时候,只需要在容器绑定的地方替换映射类,所有页面都会生效。$container->bind('数据库','Oracle');这是容器、依赖注入和控制反转的结束。