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操作SVN版本服务器类代码
Nov 27 PHP
php二维数组排序方法(array_multisort usort)
Dec 25 PHP
PHP 魔术变量和魔术函数详解
Feb 25 PHP
php实现过滤UBB代码的类
Mar 12 PHP
php使用gzip压缩传输js和css文件的方法
Jul 29 PHP
开启PHP Static 关键字之旅模式
Nov 13 PHP
PHP上传文件参考配置大文件上传
Dec 16 PHP
PHP 7.1新特性的汇总介绍
Dec 16 PHP
php实现等比例压缩图片
Jul 26 PHP
php如何利用pecl安装mongodb扩展详解
Jan 09 PHP
一文掌握PHP Xdebug 本地与远程调试(小结)
Apr 23 PHP
Laravel框架实现简单的学生信息管理平台案例
May 07 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实现高效获取图片尺寸的方法
2014/12/12 PHP
PHP使用pcntl_fork实现多进程下载图片的方法
2014/12/16 PHP
表单提交错误后返回内容消失问题的解决方法(PHP网站)
2015/10/20 PHP
php编译安装php-amq扩展简明教程
2016/06/25 PHP
PHP Socket网络操作类定义与用法示例
2017/08/30 PHP
JavaScript-世界上误解最深的语言分析
2007/08/12 Javascript
cnblogs csdn 代码运行框实现代码
2009/11/02 Javascript
jQuery Lightbox 图片展示插件使用说明
2010/04/25 Javascript
Textarea与懒惰渲染实现代码
2012/01/04 Javascript
JavaScript修改css样式style动态改变元素样式
2013/12/16 Javascript
extjs_02_grid显示本地数据、显示跨域数据
2014/06/23 Javascript
JavaScript常用的弹出广告及背投广告实现方法
2015/02/06 Javascript
jquery右下角自动弹出可关闭的广告层
2015/05/08 Javascript
jQuery实时显示鼠标指针位置和键盘ASCII码
2016/03/28 Javascript
Vue.js常用指令之循环使用v-for指令教程
2017/06/27 Javascript
webpack开发跨域问题解决办法
2017/08/03 Javascript
jQuery中库的引用方法
2018/01/06 jQuery
vue2.0 自定义组件的方法(vue组件的封装)
2018/06/05 Javascript
微信小程序支付PHP代码
2018/08/23 Javascript
Vue 中 template 有且只能一个 root的原因解析(源码分析)
2020/04/11 Javascript
JS实现前端动态分页码代码实例
2020/06/02 Javascript
js实现滚动条自动滚动
2020/12/13 Javascript
解决python3捕获cx_oracle抛出的异常错误问题
2018/10/18 Python
韩国休闲女装品牌网站:ANAIS
2016/08/24 全球购物
美国家用电器和电子产品商店:Abt
2016/09/06 全球购物
英国最大的电子产品和家电零售企业:Currys PC World
2016/09/24 全球购物
日本非常有名的内衣丝袜品牌:GUNZE
2017/01/06 全球购物
美国马匹用品和骑马配件购物网站:Horse.com
2018/01/08 全球购物
New Era英国官网:美国棒球帽品牌
2018/03/21 全球购物
Antonioli美国在线商店:时尚前卫奢华
2019/07/29 全球购物
意大利在线高尔夫商店:Online Golf
2021/03/09 全球购物
秸秆管理实施方案
2014/03/15 职场文书
高校师德师风自我剖析材料
2014/09/29 职场文书
维稳工作承诺书
2015/01/20 职场文书
入党个人总结范文
2015/03/02 职场文书
2015年党员岗位承诺书
2015/04/27 职场文书