设为首页 收藏本站
查看: 425|回复: 0

[经验分享] PHP反射介绍及利用反射实现插件功能

[复制链接]

尚未签到

发表于 2017-4-9 07:58:00 | 显示全部楼层 |阅读模式
  反射是操纵面向对象范型中元模型的API,其功能十分强大,可帮助我们构建复杂,可扩展的应用。其用途如:自动加载插件,自动生成文档,甚至可用来扩充PHP 语言。php 反射api 由若干类组成,可帮助我们用来访问程序的元数据或者同相关的注释交互。借助反射我们可以获取诸如类实现了那些方法,创建一个类的实例(不同于用new 创建),调用一个方法(也不同于常规调用),传递参数,动态调用类的静态方法。
  反射api 是php 内建的oop 技术扩展,包括一些类,异常和接口,综合使用他们可用来帮助我们分析其它类,接口,方法,属性,方法和扩展。这些oop 扩展被称为反射,位于php 源码/ext/reflection目录下。可以使用反射api 自省反射api 本身(这可能就是反射最初的意思,自己“看”自己):
<?phpReflection::export(new ReflectionExtension('reflection'));?>  几乎所有的反射api 都实现了reflector 接口,所有实现该接口的类都有一个export方法,该方法打印出参数对象的相关信息。使用get_declared_classes()获取所有php 内置类,get_declared_interfaces();get_defined_functions();get_defined_vars();get_defined_constants();可获取php 接口,方法,变量,常量信息。
  反射初探:
<?php//定义一个自定义类classMyTestClass{public function testFunc($para0='defaultValue0'){}}//接下来反射它foreach(get_declared_classes() as $class){//实例化一个反射类$reflectionClass = new ReflectionClass($class);//如果该类是自定义类if($reflectionClass->isUserDefined()){//导出该类信息Reflection::export($reflectionClass);}}?>  描述数据的数据被称为元数据,用反射获取的信息就是元数据信息,这些信息用来描述类,接口方法等等。(元---》就是原始之意,比如元模型就是描述模型的模型,比如UML 元模型就是描述UML 结构的模型),元数据进一步可分为硬元数据(hardmatadata)和软元数据(softmetadata),前者由编译代码导出,如类名字,方法,参数等。后者是人为加入的数据,如phpDoc 块,php 中的属性等。
  现在商业软件很多都是基于插件架构的,比如eclipse,和visualstudio,netbeans等一些著名IDE 都是基于插件的GUI 应用。第三方或本方开发插件时,必须导入定义好的相关接口,然后实现这些接口,最后把实现的包放在指定目录下,宿主应用程序在启动时自动检测所有的插件实现,并加载它们。如果我们自己想实现这样的架构也是可以的。
<?php//先定义UI接口interface IPlugin {//获取插件的名字public staticfunction getName();//要显示的菜单项function getMenuItems();//要显示的文章function getArticles();//要显示的导航栏function getSideBars();}//以下是对插件接口的实现class SomePlugin implements IPlugin{public function getMenuItems() {//返回菜单项return null;}public function getArticles() {//返回我们的文章return null;}public function getSideBars() {//我们有一个导航栏return array('SideBarItem');}//返回插件名public staticfunction getName(){return "SomePlugin";}}?>  加载插件步骤:
  1.先使用get_declared_classes()获取所有已加载类。
  2.遍历所有类,判断其是否实现了我们自定义的插件接口IPlugin。
  3.获取所有的插件实现。
  4.在宿主应用中与插件交互
  下面这个方法帮助我们找到实现了插件接口的所有类:
  
function findPlugins() {$plugins =array();foreach(get_declared_classes()as $class) {$reflectionClass= new ReflectionClass($class);//判断一个类是否实现了IPlugin 接口if($reflectionClass->implementsInterface('IPlugin')) {$plugins[]= $reflectionClass;}}return $plugins;}  注意到所有的插件实现是作为反射类实例返回的,而不是类名本身,或是类的实例。因为如果使用反射来调用方法还需要一些条件判断。判断一个类是否实现了某个方法使用反射类的hasMethod()方法。
  接下来我们把所有的插件菜单项放在一个菜单上。
  
function integratePlugInMenus() {$menu = array();//遍历所有的插件实现foreach(findPlugins()as $plugin) {//判断插件是否实现了getMenuItems方法if($plugin->hasMethod('getMenuItems')) {/*实例化一个方法实例(注意当你将类和方法看成概念时,它们就可以有实例,就像“人”这个概念一样),该方法返回的是ReflectionMethod 的实例*/$reflectionMethod= $plugin->getMethod('getMenuItems');//如果方法是静态的if($reflectionMethod->isStatic()){//调用静态方法,注意参数是null 而不是一个反射类实例$items = $reflectionMethod->invoke(null);} else {//如果方法不是静态的,则先实例化一个反射类实例所代表的类的实例。$pluginInstance= $plugin->newInstance();//使用反射api 来调用一个方法,参数是通过反射实例化的对象引用$items= $reflectionMethod->invoke($pluginInstance);}//合并所有的插件菜单项为一个菜单。$menu= array_merge($menu, $items);}}return $menu;}  这里主要用到的反射方法实例的方法调用:
  public mixed invoke(stdclass object, mixedargs=null);
  请一定搞清楚我们常规方法的调用是这种形式:$objRef->someMethod($argList...);
  因为使用了反射,这时你在想调用一个方法时形式变为:
  $reflectionMethodRef->invoke($reflectionClassRef,$argList...);
  如果使用反射调用方法,我们必须实例化一个反射方法的实例,如果是实例方法还要有一个实例的引用,可能还需传递必要的参数。当调用一个静态方法时,显式传入null 作为第一参数。对插件类实现的其他方法有类似的处理逻辑,这里不再敷述。
  以下是我的一个简单测试:
<?php/*** 定义一个插件接口* */interface IPlugIn{/*** getSidebars()** @return 返回侧导航栏*/public function getSidebars();/*** GetName()** @return 返回类名*/public staticfunction GetName();}/*下面是对插件的实现,其实应该放在不同的文件中,甚至是不同的包中*/class MyPlugIn implements IPlugIn{public function getSidebars(){//构造自己的导航栏$sideBars = '<div><ul ><li><ahref="">m1</a></li><li><ahref="">m2</a></li></ul></div>';return $sideBars;}public staticfunction GetName(){return 'MyPlugIn';}}//第二个插件实现;class MyPlugIn2 implements IPlugIn{public function getSidebars(){//构造自己的导航栏$sideBars = '<div><ul><li><ahref="">mm1</a></li><li><ahref="">mm2</a></li></ul></div>';return $sideBars;}public staticfunction GetName(){return 'MyPlugIn2';}}//在宿主程序中使用插件class HostApp{public function initAll(){// 初始化各个部分$this->renderAll();}//渲染GUI 格部分function renderAll(){$rsltSidebars="<table>";foreach($this->integrateSidebarsOfPlugin()as $sidebarItem){$rsltSidebars.="<tr><td>$sidebarItem</td></tr>";}$rsltSidebars.="</table>";echo $rsltSidebars;}/*加载所有的插件实现:*/protectedfunction findPlugins(){$plugins = array();foreach (get_declared_classes() as $class) {$reflectionClass = new ReflectionClass($class);if ($reflectionClass->implementsInterface('IPlugin')) {$plugins[] = $reflectionClass;}}return $plugins;}/**加载组装所有插件实现***/protectedfunction integrateSidebarsOfPlugin(){$sidebars = array();foreach ($this->findPlugins() as $plugin){if ($plugin->hasMethod('getSidebars')){$reflectionMethod = $plugin->getMethod('getSidebars');if ($reflectionMethod->isStatic()) {$items = $reflectionMethod->invoke(null);} else{$pluginInstance = $plugin->newInstance();$items =$reflectionMethod->invoke($pluginInstance) ;}}//$sidebars =array_merge($sidebars, $items);$sidebars[]=$items;}return $sidebars;}}//运行程序:$entryClass =new HostApp();$entryClass->initAll();
$reflectionClass = newReflectionClass("IPlugIn");echo  $reflectionClass->getDocComment();  这段代码可以帮助我们获取类的文档注释,一旦我们获取了类的注释内容我们就可以扩展我们的类功能,比如先获取注释,然后分析注释使用docblock tokenizer 『pecl 扩展』,或使用自带的Tokenizer类又或者使用正则表达式,字符串函数来解析注释文档,你可以在注释中加入任何东西,包括指令,在使用反射调用前可判断这些通过注释传递的指令或数据:
<?php//"分析相关的注释数据"analyse($reflectionClass->getDocComment());//analyse 是自己定义的!!!//根据分析的结果来执行方法,或者传递参数等if(xxxx){$reflectionMethod->invoke($pluginInstance);}?>  因为注释毕竟是字符串,可以使用任何字符串解析技术,提取有用的信息,再根据这些信息来调用方法,就是说程序的逻辑不光可由方法实现决定,还可能由注释决定(前提是你使用了反射,注释格式严格有要求)。
  反射api 和其他类一样可被继承和扩展,所以我们可以为这些api 添加自己的功能。结合自定义注释标记。就是以@开头的东东,标注(Java 中称为annotation),.net中称为属性attribute(或称为特性)。然后扩展Reflection 类,就可以实现强大的扩展功能了。值得一提的是工厂方法设计模式,也常使用反射来实例化对象,下面是示例性质的伪码:
  
Class XXXFactory{function getInstance($className){$reflectionClass=new ReflectionClass($className);return $reflectionClass->newInstance();}//使用接口的那个类实现,可能来自配置文件function getInstance(){$pathOfClass= "xxx/xx/XXXImplement.php";$className=Config->getItem($pathOfClass,'SomeClassName');return $this->getInstance($className);}}

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-362204-1-1.html 上篇帖子: PHP实现多web服务器共享SESSION数据 下篇帖子: [转]通过案例深入探讨PHP中的内存管理问题
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表