PHP回顾之反射

栏目: PHP · 发布时间: 6年前

内容简介:转载请注明文章出处:反射是编程语言的高级特性,能在运行时让代码有从一个简单的例子理解反射:人有五官四肢,但鲜有人清楚人体内部的经脉走向、骨骼构造。如果你修仙顺利,在丹田深处练出元婴,那么就通过元婴透析身体内部的构造。理解内部构造后,还可以让元婴指引体内真气在经脉的流向,早日修成正果。

转载请注明文章出处: https://tlanyan.me/php-review-reflection

PHP回顾系列目录

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

反射

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

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

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

反射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。

感谢阅读,欢迎指正!

参考

  1. http://php.net/manual/en/book.reflection.php
  2. https://en.wikipedia.org/wiki/Reflection_(computer_programming)
  3. http://zenovich.blogspot.com/2015/10/runkit-1.0.4.html

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Hit Refresh

Hit Refresh

Satya Nadella、Greg Shaw / HarperBusiness / 2017-9-26 / USD 20.37

Hit Refresh is about individual change, about the transformation happening inside of Microsoft and the technology that will soon impact all of our lives—the arrival of the most exciting and disruptive......一起来看看 《Hit Refresh》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具