PHP反射介绍及利用反射实现插件功能

反射是操纵面向对象范型中元模型的API,其功能十分强大,可帮助我们构建复杂,可扩展的应用。其用途如:自动加载插件,自动生成文档,甚至可用来扩充PHP 语言。php 反射api 由若干类组成,可帮助我们用来访问程序的元数据或者同相关的注释交互。借助反射我们可以获取诸如类实现了那些方法,创建一个类的实例(不同于用new 创建),调用一个方法(也不同于常规调用),传递参数,动态调用类的静态方法。

反射api 是php 内建的oop 技术扩展,包括一些类,异常和接口,综合使用他们可用来帮助我们分析其它类,接口,方法,属性,方法和扩展。这些oop 扩展被称为反射,位于php 源码/ext/reflection目录下。可以使用反射api 自省反射api 本身(这可能就是反射最初的意思,自己“看”自己):

<?php Reflection::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); } }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值