PHP反射基础知识回顾


Posted in PHP onSeptember 10, 2020

反射是编程语言的高级特性,能在运行时让代码有感知代码的能力。PHP自5起支持反射机制,其是各种OOP框架底层实现的重要支撑。

反射

从一个简单的例子理解反射:人有五官四肢,但鲜有人清楚人体内部的经脉走向、骨骼构造。如果你修仙顺利,在丹田深处练出元婴,那么就通过元婴透析身体内部的构造。理解内部构造后,还可以让元婴指引体内真气在经脉的流向,早日修成正果。

如其名,反射是(从镜子里)照出自身。我们写代码,告诉代码怎么运行,事件发生在编译期。代码运行期间,代码如何知道自己的结构以及能力呢?反射机制相当于代码的元婴,使代码能够感知自身结构,并可(部分)改变运行行为。

与运行时类型信息(Runtime Type Informatiion, RTTI)不同,反射重点在运行时检测、感知、改变自身的结构和行为。反射是元编程(metaprogramming)的重要组成部分。

PHP反射API

反射不是语法分析,不操作表达式、代码语句。反射获取的是代码的结构,即函数、类这些构件的结构。PHP中的反射API均以Reflection开头(接口Reflector除外),重点在函数和类两种结构。而函数可以看成类的成员函数(多一个隐式的this参数)或者静态成员函数(public类型),所以了解反射API可从类信息的ReflectionClass开始。

ReflectionClass提供了以下获取类基本信息的接口:

  1. getProperties:获取成员变量/属性,返回一个ReflectionProperty数组;ReflectionProperty类中有对属性详细说明的API:是否默认属性(isDefault),是否私有属性(isPrivate)等。同时ReflectionClass还提供获取特定类别属性的API:getDefaultPropertiesgetStaticProperties
  2. getConstants:获取类中定义的常量;
  3. getMethods:获取类中定义的方法,返回一个ReflectionMethod数组;ReflectionMethod将在下文讲解;
  4. getInterfaces:获取类实现的接口;
  5. getParentClass:获取父类的ReflectionClass实例。

在反射中,类、接口、特性不分家,所以ReflectionClass提供类型判定API:isInterfaceisTrait

除了以上基本信息,ReflectionClass(包括ReflectionMethod/ReflectionFunction)还提供了一些不可思议的能力:

  1. getDocComment:获取类的文档注释信息;
  2. getFilename:获取类定义的文件;
  3. getStartLine: 获取类定义的起始行号;
  4. getEndLine: 获取类定义的结束行号;
  5. getModifiers:获取类定义的修饰符,其意义名字可通过Reflection::getModifierNames得到,例如:abstract,final。

如果说前述的类结构信息可以通过现有的API获取(method_exits/property_exits等),上面列出的功能基本上只能通过反射API获取(PHP文件中定义的类并且知道定义文件,可以利用token_get_all得到相同结果,但是实现非常复杂)。这些行为发生在运行期间。由此可见反射API在分析类结构信息功能上的强大。

除了ReflectionClassReflectionMethodReflectionFunction是另外反射中另外两个重要的类。函数(function)定义在类外部,方法(method)定义在类内部,两者其实同源,在反射API中有共同的父类:ReflectionFunctionAbstractReflectionFunctionAbstract有两者的大部分API,并且基本上是最重要的API。其中最值得关注的是其参数信息的API:getParameters。其获取函数的参数信息,返回一个ReflectionParameter数组。结合getParametersReflectionParameter,函数(方法)的结构基本上就清晰了。

API操作

知道人体构造和体内真气分布,你可以引导真气到手指,练成一阳指、六脉神剑、弹指神通、九阴白骨爪等;也可以让真气汇聚,冲破任督二脉,开辟洞天;还可以逆转全身经脉,练成蛤蟆功…内省的好处可见一斑。

反射让代码感知自身结构,有什么好处呢?反射API提供了三种在运行时对代码操作的能力:

  1. 设置访问控制权:setAccessible。可获取私有的方法/属性。注意:setAccessible只是让方法/成员变量可以invoke/getValue/setValue,并不代表类定义的访问存取权限改变;
  2. 调用函数/方法:invoke/invokeArgs。配合获取函数参数的API,可以安全的传参和调用函数,call_user_func(_array)的增强版;
  3. 不依赖构造函数生成实例:newInstanceWithoutConstructor

以单例来说一下反射API的功能,单例类代码如下:

# foo.php
class Foo {
 private static $id;
 private static $instance;

 private function __construct() {
 ++ self::$id;
 fwrite(STDOUT, "construct, instance id: " . self::$id . "\n");
 }

 public static function getSingleton() {
 if (self::$instance === null) {
 self::$instance = new self();
 }
 return self::$instance;
 }
}

Foo类中,构造函数是私有,获取实例只能通过getSingleton方法,并且获取到的是单例。但在反射API加持下,能获取多个实例:

$instance1 = Foo::getSingleton();
var_dump($instance1);

$class = new ReflectionClass("Foo");
$constructor = $class->getConstructor();
if ((ReflectionProperty::IS_PUBLIC & $constructor->getModifiers()) === 0) {
 $constructor->setAccessible(true);
}
$instance2 = $class->newInstanceWithoutConstructor();
$constructor->invoke($instance2);
var_dump($instance2);

# 脚本执行结果
construct, instance id: 1
object(Foo)#1 (0) {
}
construct, instance id: 2
object(Foo)#4 (0) {
}

我们成功的生成了两个实例,并调用构造函数完成对象初始化。如果没有反射API,这几乎是不可能完成的工作。

除了这三种操作,反射API几乎已无在运行时动态改变代码的行为。但作为动态语言,PHP内置了将数据转换成代码执行的能力(例如create_function/eval、动态函数名调用)。而PHP的好基友JavaScript则可以随时在运行时改变任意函数的行为:

PHP反射基础知识回顾

PHP作为最好的语言,理应能做到在运行时动态增减/改变函数定义。这就需要用到另一个PHP核心开发者“Dmitry Zenovich”打造的大杀器:runkit拓展。这部分内容不属于反射,加之本人了解不深,不再详述。

对比

整理一下反射API和函数式API在功能上的差异:

功能 函数式API 反射API
函数是否存在 function_exists ReflectionFunction
类是否存在 class_exits ReflectionClass
方法是否存在 method_exits ReflectionMethod
变量/属性是否存在 property_exits ReflectionProperty
获取类变量 get_class_vars ReflectionClass::getProperties
获取类方法 get_class_methods ReflectionClass::getMethods
获取类常量 ReflectionClass::RegetReflectionConstant(s)
获取函数/方法参数信息 ReflectionFunction/Method::getParameters
获取函数/方法返回值 ReflectionFunction/Method::getReturnType
类使用的特性 class_uses ReflectionClass::getTraits
获取父类 class_parents ReflectionClass::getParentClass
获取类实现的接口 class_implements ReflectionClass::getInterfaceNames
获取类所在名字空间 __NAMESPACE__ ReflectionClass::getNamespaceName
函数调用 call_user_func(_array) ReflectionMethod(Function)::invoke(Args)
获取类名 __CLASS__/::class ReflectionClass::getName
获取函数名 __METHOD__/__FUNCTION__ ReflectionFunction/Method::getName
获取类/常量/变量/方法修饰符 ReflectionClass/Constant/Property/Method::getModifiers
获取所在文件 __FILE__ ReflectionClass/Constant/Function/Method::getFileName
获取所在行(范围) ReflectionClass/Function/Method::getStartLine/getEndLine
获取文档 ReflectionClass/Function/Method::getDocComment
extension_loaded ReflectionZendExtension
拓展 get_loaded_extensions ReflectionExtension
get_extension_funcs

从上表可以看出反射API较函数式API能提供更全面的信息。还需要注意到__FILE__这类魔术常量是编译期的工作,不是运行时的能力。

同时给出RTTI的函数式API和反射API在功能上的差异:

功能 函数式API 反射API
类型判断 is_int/is_bool/is_array等
获取对象的类名 get_class ReflectionObject::getName
获取对象父类 get_parent_class ReflectionObject::getParentClass
类型/继承检测 instanceof/is_a/is_subclass_of ReflectionObject::isInstance/isSubclassOf
生成器 ReflectionGenerator

总结

本文对PHP中的反射机制做了简要总结,并与在运行时获取代码信息的函数式API做了对比。即使你token_get_all用得再熟练,preg_match等文本操作用得再顺手,反射API仍有其独到一面,值得了解。如本人之前博文“PHP中的重载”所言,有了反射,function_exits/class_exitscall_user_func这些函数应该可以退休。但是考虑到兼容、使用便利、运行效率等因素,许多框架仍然依赖这些API。

感谢阅读,欢迎指正!

以上就是PHP反射知识回顾的详细内容,更多关于PHP 反射的资料请关注三水点靠木其它相关文章!

PHP 相关文章推荐
用libtemplate实现静态网页生成
Oct 09 PHP
PHP下利用header()函数设置浏览器缓存的代码
Sep 01 PHP
php错误级别的设置方法
Jun 17 PHP
php_screw安装使用教程(另一个PHP代码加密实现)
May 29 PHP
PHP临时文件的安全性分析
Jul 04 PHP
php查询mssql出现乱码的解决方法
Dec 29 PHP
PHP判断一个字符串是否是回文字符串的方法
Mar 23 PHP
PHP之正则表达式捕获组与非捕获组(详解)
Jul 29 PHP
PHP实现路由映射到指定控制器
Aug 13 PHP
php自定义截取中文字符串-utf8版
Feb 27 PHP
PHP 爬取网页的主要方法
Jul 13 PHP
php使用curl获取header检测开启GZip压缩的方法
Aug 15 PHP
PHP获取类私有属性的3种方法
Sep 10 #PHP
php实现图片压缩处理
Sep 09 #PHP
如何在PHP中读写文件
Sep 07 #PHP
PHP延迟静态绑定使用方法实例解析
Sep 05 #PHP
PHP autoload使用方法及步骤详解
Sep 05 #PHP
PHP数组访问常用方法解析
Sep 05 #PHP
XAMPP升级PHP版本实现步骤解析
Sep 04 #PHP
You might like
PHP学习之输出字符串(echo,print,printf,print_r和var_dump)
2011/04/17 PHP
php简单浏览目录内容的实现代码
2013/06/07 PHP
PHP实现导出excel数据的类库用法示例
2016/10/15 PHP
php中get_object_vars()在数组的实例用法
2021/02/22 PHP
在修改准备发的批量美化select+可修改select时,在非IE下发现了几个问题
2007/01/09 Javascript
浏览器的JavaScript引擎的识别方法
2013/10/20 Javascript
JavaScript实现向setTimeout执行代码传递参数的方法
2015/04/16 Javascript
javascript学习指南之回调问题
2016/04/23 Javascript
js从外部获取图片的实现方法
2016/08/05 Javascript
bootstrap日历插件datetimepicker使用方法
2016/12/14 Javascript
在JS中a标签加入单击事件屏蔽href跳转页面
2016/12/16 Javascript
js模仿微信朋友圈计算时间显示几天/几小时/几分钟/几秒之前
2017/04/27 Javascript
pm2 部署 node的三种方法示例
2017/10/20 Javascript
JavaScript中Array方法你该知道的正确打开方法
2018/09/11 Javascript
微信小程序页面传多个参数跳转页面的实现方法
2019/05/17 Javascript
selenium 反爬虫之跳过淘宝滑块验证功能的实现代码
2020/08/27 Javascript
Python中max函数用法实例分析
2015/07/17 Python
python使用tkinter实现简单计算器
2018/01/30 Python
Python模块WSGI使用详解
2018/02/02 Python
django使用xlwt导出excel文件实例代码
2018/02/06 Python
给Python学习者的文件读写指南(含基础与进阶)
2020/01/29 Python
python 实现字符串下标的输出功能
2020/02/13 Python
Python如何实现线程间通信
2020/07/30 Python
使用Python中tkinter库简单gui界面制作及打包成exe的操作方法(二)
2020/10/12 Python
Canvas与Image互相转换示例代码
2013/08/09 HTML / CSS
用HTML5制作烟火效果的教程
2015/05/12 HTML / CSS
德国知名健康零食网上商店:Seeberger
2017/07/27 全球购物
英国鞋网:Rubber Sole
2020/03/03 全球购物
幼师自我鉴定范文
2013/10/01 职场文书
初中生评语大全
2014/04/24 职场文书
高二学生评语大全
2014/04/25 职场文书
员工廉洁自律承诺书
2014/05/26 职场文书
2015年中学团委工作总结
2015/07/22 职场文书
django学习之ajax post传参的2种格式实例
2021/05/14 Python
python和anaconda的区别
2022/05/06 Python
pytorch实现加载保存查看checkpoint文件
2022/07/15 Python