热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

PHP反射机制详解以及插件的架构实现(二)

PHP反射机制详解以及插件的架构实现(二)

反射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";
    }
}
?>
php中也有使用插件的解决方案,不像eclipse。

使用我们的插件:

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;
}
这里主要用到的反射方法实例的方法调用:
public mixed invoke(stdclass object, mixed args=null);
请一定搞清楚我们常规方法的调用是这种形式:


$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()
    {
        //构造自己的导航栏
        $sideBars= '
            
  • m1
  •                                 
                     
  • m2
  •                                                
                   
                   
    ';
            return$sideBars;
        }
        publicstaticfunctionGetName()
        {
            return'MyPlugIn';
        }
    }
      
    //第二个插件实现;
    classMyPlugIn2implementsIPlugIn
    {
        publicfunctiongetSidebars()
        {
            //构造自己的导航栏
             $sideBars= '
    mm1 mm2
    '; return $sideBars; } public static function GetName() { return 'MyPlugIn2'; } } //在宿主程序中使用插件 class HostApp { public function initAll() { // 初始化各个部分 echo "yiqing95."; $this->renderAll(); } //渲染GUI格部分 function renderAll(){ $rsltSidebars=""; foreach($this->integrateSidebarsOfPlugin() as $sidebarItem){ $rsltSidebars.=""; } $rsltSidebars.=" $sidebarItem "; echo $rsltSidebars; } /*加载所有的插件实现:*/ protected function findPlugins() { $plugins = array(); foreach (get_declared_classes() as $class) { $reflectiOnClass= new ReflectionClass($class); if ($reflectionClass->implementsInterface('IPlugin')) { $plugins[] = $reflectionClass; } } return $plugins; } /**加载组装所有插件实现***/ protected function 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= 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); 
        }
    }


    推荐阅读
    • 本文介绍了在Ubuntu 11.10 x64环境下安装Android开发环境的步骤,并提供了解决常见问题的方法。其中包括安装Eclipse的ADT插件、解决缺少GEF插件的问题以及解决无法找到'userdata.img'文件的问题。此外,还提供了相关插件和系统镜像的下载链接。 ... [详细]
    • Allegro总结:1.防焊层(SolderMask):又称绿油层,PCB非布线层,用于制成丝网印板,将不需要焊接的地方涂上防焊剂.在防焊层上预留的焊盘大小要比实际的焊盘大一些,其差值一般 ... [详细]
    • android ... [详细]
    • 知识图谱——机器大脑中的知识库
      本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
    • MACElasticsearch安装步骤及验证方法
      本文介绍了MACElasticsearch的安装步骤,包括下载ZIP文件、解压到安装目录、启动服务,并提供了验证启动是否成功的方法。同时,还介绍了安装elasticsearch-head插件的方法,以便于进行查询操作。 ... [详细]
    • Oracle分析函数first_value()和last_value()的用法及原理
      本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
    • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
    • Week04面向对象设计与继承学习总结及作业要求
      本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
    • 如何实现JDK版本的切换功能,解决开发环境冲突问题
      本文介绍了在开发过程中遇到JDK版本冲突的情况,以及如何通过修改环境变量实现JDK版本的切换功能,解决开发环境冲突的问题。通过合理的切换环境,可以更好地进行项目开发。同时,提醒读者注意不仅限于1.7和1.8版本的转换,还要适应不同项目和个人开发习惯的需求。 ... [详细]
    • 初探PLC 的ST 语言转换成C++ 的方法
      自动控制软件绕不开ST(StructureText)语言。它是IEC61131-3标准中唯一的一个高级语言。目前,大多数PLC产品支持ST ... [详细]
    • 本文介绍了brain的意思、读音、翻译、用法、发音、词组、同反义词等内容,以及脑新东方在线英语词典的相关信息。还包括了brain的词汇搭配、形容词和名词的用法,以及与brain相关的短语和词组。此外,还介绍了与brain相关的医学术语和智囊团等相关内容。 ... [详细]
    • 云原生边缘计算之KubeEdge简介及功能特点
      本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
    • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
    • 20209测试通过:eclipse安装svn插件
      网址不能用了,新的办法参考:https:quantum6.blog.csdn.netarticledetails117250800下载了最新的ecli ... [详细]
    • java io换行符_Java IO:为什么从stdin读取时,换行符的数字表示出现在控制台上?...
      只是为了更好地理解我在讲座中听到的内容(关于Java输入和输出流),我自己做了这个小程序:publicstaticvoidmain(String[]args)thro ... [详细]
    author-avatar
    手机用户2502926887
    这个家伙很懒,什么也没留下!
    PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
    Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有