反射api是php内建的oop技术扩展,包括一些类,异常和接口,
综合使用他们可用来帮助我们分析其它类,接口,方法,属性,方法和扩展。
这些oop扩展被称为反射,位于php源码/ext/reflection目录下。
可以使用反射api自省反射api本身(这可能就是反射最初的意思,自己“看”自己):
1
2
3
|
Reflection::export(newReflectionExtension('reflection'));
?>
|
几乎所有的反射api都实现了reflector接口,所有实现该接口的类都有一个export方法,
该方法打印出参数对象的相关信息。
使用get_declared_classes()获取所有php内置类,
get_declared_interfaces();
get_defined_functions();
get_defined_vars();
get_defined_constants();
可获取php接口,方法,变量,常量信息。
反射初探:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//定义一个自定义类
classMyTestClass{
publicfunctiontestFunc($para0='defaultValue0'){
}
}
//接下来反射它
foreach(get_declared_classes()as$class){
//实例化一个反射类
$reflectiOnClass=newReflectionClass($class);
//如果该类是自定义类
if($reflectionClass->isUserDefined()){
//导出该类信息
Reflection::export($reflectionClass);
}
}
?>
|
描述数据的数据被称为元数据,用反射获取的信息就是元数据信息,这些信息用来描述类,
接口方法等等。(元---》就是原始之意,比如元模型就是描述模型的模型,
比如UML元模型就是描述UML结构的模型),
元数据进一步可分为硬元数据(hard matadata)和软元数据(soft metadata),
前者由编译代码导出,如类名字,方法,参数等。
后者是人为加入的数据,如phpDoc块,php中的属性等。
现在商业软件很多都是基于插件架构的,比如eclipse,和visual studio,netbeans等一些著名IDE都是基于插件的GUI应用。
第三方或本方开发插件时,必须导入定义好的相关接口,然后实现这些接口,最后把实现的包放在指定目录下,
宿主应用程序在启动时自动检测所有的插件实现,并加载它们。如果我们自己想实现这样的架构也是可能的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
//先定义UI接口
interfaceIPlugin
{
//获取插件的名字
publicstaticfunctiongetName();
//要显示的菜单项
functiongetMenuItems();
//要显示的文章
functiongetArticles();
//要显示的导航栏
functiongetSideBars();
}
//一下是对插件接口的实现
classSomePluginimplementsIPlugin
{
publicfunctiongetMenuItems()
{
//返回菜单项
returnnull;
}
publicfunctiongetArticles()
{
//返回我们的文章
returnnull;
}
publicfunctiongetSideBars()
{
//我们有一个导航栏
returnarray('SideBarItem');
}
//返回插件名
publicstaticfunctiongetName()
{
return"SomePlugin";
}
}
?>
|
使用我们的插件:
1.先使用get_declared_classes()获取所有已加载类。
2.遍历所有类,判断其是否实现了我们自定义的插件接口IPlugin。
3.获取所有的插件实现。4.在宿主应用中与插件交互
下面这个方法帮助我们找到实现了插件接口的所有类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
functionfindPlugins()
{
$plugins=array();
foreach(get_declared_classes()as$class)
{
$reflectiOnClass=newReflectionClass($class);
//判断一个类是否实现了IPlugin接口
if($reflectionClass->implementsInterface('IPlugin'))
{
$plugins[] =$reflectionClass;
}
}
return$plugins;
}
|
注意到所有的插件实现是作为反射类实例返回的,而不是类名本身,或是类的实例。
因为如果使用反射来调用方法还需要一些条件判断。
判断一个类是否实现了某个方法使用反射类的hasMethod()方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
functionintegratePlugInMenus()
{
$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;
}
|
$objRef->someMethod($argList...);
因为使用了反射,这时你在想调用一个方法时形式变为:
$reflectionMethodRef->invoke($reflectionClassRef,$argList...);
如果使用反射调用方法,我们必须实例化一个反射方法的实例,
如果是实例方法还要有一个实例的引用,可能还需传递必要的参数。
当调用一个静态方法时,显式传入null作为第一参数。
对插件类实现的其他方法有类似的处理逻辑,这里不再敷述。
以下是我的一个简单测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
/**
* 定义一个插件接口
* */
interfaceIPlugIn
{
/**
* getSidebars()
*
* @return 返回侧导航栏
*/
publicfunctiongetSidebars();
/**
* GetName()
*
* @return 返回类名
*/
publicstaticfunctionGetName();
}
/*下面是对插件的实现,其实应该放在不同的文件中,甚至是不同的包中*/
classMyPlugInimplementsIPlugIn
{
publicfunctiongetSidebars()
{
//构造自己的导航栏
return$sideBars;
}
publicstaticfunctionGetName()
{
return'MyPlugIn';
}
}
//第二个插件实现;
classMyPlugIn2implementsIPlugIn
{
publicfunctiongetSidebars()
{
//构造自己的导航栏
$sideBars= '
|
$reflectiOnClass= new ReflectionClass("IPlugIn");
echo $reflectionClass-> getDocComment();
这段代码可以帮助我们获取类的文档注释,一旦我们获取了类的注释内容我们就可以扩展我们的类功能,比如先获取注释,然后分析注释使用docblock tokenizer 『pecl扩展』,或使用自带的Tokenizer类又或者使用正则表达式,字符串函数来解析注释文档,你可以在注释中加入任何东西,包括指令,在使用反射调用前可判断这些通过注释传递的指令或数据:
1
2
3
4
5
6
7
8
9
|
//"分析相关的注释数据"
analyse($reflectionClass-> getDocComment());//analyse是自己定义的!!!
//根据分析的结果来执行方法,或者传递参数等
if(xxxx)
{
$reflectionMethod->invoke($pluginInstance) ;
}
?>
|
因为注释毕竟是字符串,可以使用任何字符串解析技术,提取有用的信息,再根据这些信息来调用方法,
就是说程序的逻辑不光可由方法实现决定,还可能由注释决定(前提是你使用了反射,注释格式严格有要求)。
反射api和其他类一样可被继承扩展,所以我们可以为这些api添加自己的功能。结合自定义注释标记。
就是以@开头的东东,标注(Java中称为annotation),.net中称为属性attribute(或称为特性)。
然后扩展Reflection类,就可以实现强大的扩展功能了。
值得一提的是工厂方法设计模式(GOF之一),也常使用反射来实例化对象,下面是示例性质的伪码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Class XXXFactory
{
functiongetInstance($className)
{
$reflectiOnClass=newReflectionClass($className);
return$reflectionClass->newInstance();
}
//使用接口的那个类实现,可能来自配置文件
functiongetInstance()
{
$pathOfCOnfig="xxx/xx/XXXImplement.php";
$className= Config->getItem($pathOfClass,'SomeClassName');
return$this->getInstance($className);
}
}
|