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 相关文章推荐
PHP读写文件的方法(生成HTML)
Nov 27 PHP
require(),include(),require_once()和include_once()区别
Mar 27 PHP
php格式化工具Beautify PHP小小BUG
Apr 24 PHP
PHP+SQL 注入攻击的技术实现以及预防办法
Dec 29 PHP
php中将指针移动到数据集初始位置的实现代码[mysql_data_seek]
Nov 01 PHP
浅析php中jsonp的跨域实例
Jun 21 PHP
php过滤XSS攻击的函数
Nov 12 PHP
PHP的反射类ReflectionClass、ReflectionMethod使用实例
Aug 05 PHP
CI框架给视图添加动态数据
Dec 01 PHP
php关闭warning问题的解决方法
May 17 PHP
PHP网页安全认证的实例详解
Sep 28 PHP
PHP 图片合成、仿微信群头像的方法示例
Oct 25 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
日本收入最高的漫画家:海贼王作者版税年收入高达8.45亿元
2020/03/04 日漫
SONY ICF-SW7600的电路分析
2021/03/02 无线电
PHP聚合式迭代器接口IteratorAggregate用法分析
2017/12/28 PHP
不错的新闻标题颜色效果
2006/12/10 Javascript
javascript下利用arguments实现string.format函数
2010/08/24 Javascript
原生javascript实现无间缝滚动示例
2014/01/28 Javascript
jQuery焦点控制图层展示延迟隐藏的方法
2015/03/09 Javascript
jQuery CSS3自定义美化Checkbox实现代码
2016/05/12 Javascript
JS Ajax请求如何防止重复提交
2016/06/13 Javascript
RequireJS多页面应用实例分析
2016/06/29 Javascript
bootstrap导航栏、下拉菜单、表单的简单应用实例解析
2017/01/06 Javascript
Vue.js鼠标悬浮更换图片功能
2017/05/17 Javascript
原生JS实现网页手机音乐播放器 歌词同步播放的示例
2018/02/02 Javascript
JS改变页面颜色源码分享
2018/02/24 Javascript
Vue实现动态添加或者删除对象和对象数组的操作方法
2018/09/21 Javascript
详解@angular/cli 改变默认启动端口两种方式
2018/11/29 Javascript
详解javascript replace高级用法
2019/02/17 Javascript
微信jssdk踩坑之签名错误invalid signature
2020/05/19 Javascript
Javascript实现单选框效果
2020/12/09 Javascript
[01:25]DOTA2超级联赛专访iG 将调整状态找回自己
2013/06/05 DOTA
python中实现php的var_dump函数功能
2015/01/21 Python
基于Python的接口测试框架实例
2016/11/04 Python
Linux RedHat下安装Python2.7开发环境
2017/05/20 Python
python3使用smtplib实现发送邮件功能
2018/05/22 Python
tensorflow 中对数组元素的操作方法
2018/07/27 Python
Python 通过requests实现腾讯新闻抓取爬虫的方法
2019/02/22 Python
Python PO设计模式的具体使用
2019/08/16 Python
Django实现将views.py中的数据传递到前端html页面,并展示
2020/03/16 Python
日本PLST在线商店:日本时尚杂志刊载的人气服装
2016/12/10 全球购物
Manduka官网:瑜伽垫、瑜伽毛巾和服装
2018/07/02 全球购物
大整数数相乘的问题
2012/07/22 面试题
家长给学校的建议书
2014/05/15 职场文书
共产党员公开承诺践诺书
2014/05/28 职场文书
高考升学宴答谢词
2015/01/20 职场文书
投标售后服务承诺书
2015/04/29 职场文书
浅谈MySQL表空间回收的正确姿势
2021/10/05 MySQL