andrewsville / php-token-reflection
使用标记化源代码模拟PHP内部反射的库。
Requires
- php: >=5.3.0
- ext-tokenizer: *
This package is not auto-updated.
Last update: 2024-09-14 13:09:59 UTC
README
简而言之,这个库通过标记化的PHP源代码模拟PHP反射模型。
基本概念是,任何反射都可以处理描述反射元素的特定标记数组部分。它还可以找出是否存在任何子元素(例如,类反射可以在源中找到方法定义),创建它们的反射并将适当的标记数组部分传递给它们。
这个概念允许我们保持解析代码相对简单且易于维护。我们能够一次性创建所有反射。这对库的性能至关重要。
所有反射实例都保存在一个TokenReflection\Broker实例中,所有反射都知道创建它们的Broker。这非常重要,因为例如类反射持有其内部实例化的所有常量、方法和属性反射,但它对父类或实现的接口一无所知。它只知道它们的完全限定名。所以当你调用$reflectionClass->getParentClass();
时,类反射会向Broker请求一个按名称的类反射并返回它。
当定义了父类但未处理时(换句话说,你请求Broker一个它不认识的类),它仍然返回一个反射!是的,我们确实有不存在类的反射!太酷了!
存在文件(*)、文件命名空间(*)、命名空间、类、函数/方法、常量、属性和参数的反射。你通常不会接触那些带有星号的标记,但它们在内部使用。
ReflectionFile是我们反射树中最顶层的结构。它获取整个标记化源,并尝试在其中找到命名空间。如果找到,它创建ReflectionFileNamespace实例,并将适当的标记数组部分传递给它们。如果没有找到,它创建一个单例伪命名空间(称为无命名空间),并将整个标记化源传递给它。
ReflectionFileNamespace从文件中获取命名空间定义,找出其名称、其他别名命名空间,并尝试找到定义的任何常量、函数和类。如果找到,它创建它们的反射并将适当的标记数组部分传递给它们。
ReflectionNamespace与名称类似,但在意义上却截然不同。它是每个命名空间的唯一结构,它持有特定命名空间中的所有常量、函数和类。实际上,它是一个简单的容器。它也不是由任何父反射直接创建的,而是Broker创建的。
为什么我们需要两个独立的类?因为命名空间可以分成许多文件,每个文件都可以有单独的命名空间别名。在解析父类/接口名称时必须考虑这些别名。这意味着为每个文件中的每个命名空间创建一个ReflectionFileNamespace,并解析其内容,解析所有类的完全限定名称、它们的父类和接口。然后,Broker将相同命名空间的所有ReflectionFileNamespace实例合并为一个ReflectionNameaspace实例。
ReflectionClass、ReflectionFunction、ReflectionMethod、ReflectionParameter 和 ReflectionProperty 与它们的内部反射同名功能的工作方式相同。
ReflectionConstants 是我们对反射模型的扩展。它所能做的事情不多——它可以返回其名称、值(我们将在后面讨论值)以及如何定义。
(几乎)所有的反射类都共享一个公共基类,它定义了一些公共功能和接口。这意味着我们的反射模型比内部模型更加统一。
存在针对标记源(上述提到的那些)的反射,但也存在实现我们附加功能的内部反射的后代(它们都使用相同的接口)。它们代表 PHP 的内部类、函数等。所以当你向代理请求一个内部类时,它返回一个封装内部反射功能并添加我们功能的 TokenReflection\Php\ReflectionClass 实例。还有一个没有在内部反射模型中父类的 TokenReflection\Php\ReflectionConstant 类。
备注
从一开始,我们尽量与内部反射保持最大兼容性(包括像以相同——相当奇怪——的顺序返回接口列表这样的东西)。然而,有些情况下这是不可能的(例如,我们更倾向于一致性而不是与内部反射兼容,并且不会将 此错误 引入库中)。
我们在处理常量值和属性、参数默认值方面有限制。当作为常量定义时,我们尽最大努力解析其值(在解析和内部常量中)并使用它。这最终是通过 var_export()
和 eval()
的组合来实现的。是的,这很糟糕,但这是没有更好的方法。此外,引用的常量可能不存在。在这种情况下,它被替换为 ~~NOT RESOLVED~~
字符串。
不支持运行时常量。
当库遇到重复的类、函数或常量名称时,它将之前创建的反射转换为“无效反射”实例。这意味着解析器无法区分此类类,它无法为例如构建适当的类树。它将抛出异常。当你捕获此异常并继续使用代理实例工作时,重复的类、函数或常量将只有一个反射,它将是 Invalid\ReflectionClass、Invalid\ReflectionFunction 或 Invalid\ReflectionConstant 的实例。
用法
要能够与反射一起工作,你必须先让库解析源代码。这是 TokenReflection\Broker 所做的。它遍历给定的目录,标记 PHP 源,并缓存反射对象。此外,你不能直接实例化一个反射类。你必须向代理请求反射。一旦你有了反射实例,一切就会像预期的那样工作:)
<?php namespace TokenReflection; $broker = new Broker(new Broker\Backend\Memory()); $broker->processDirectory('~/lib/Zend_Framework'); $class = $broker->getClass('Zend_Version'); // returns a TokenReflection\ReflectionClass instance $class = $broker->getClass('Exception'); // returns a TokenReflection\Php\ReflectionClass instance $class = $broker->getClass('Nonexistent'); // returns a TokenReflection\Dummy\ReflectionClass instance $function = $broker->getFunction(...); $constant = $broker->getConstant(...);
需求
库需要启用 tokenizer 扩展 的 PHP 5.3。如果你想处理 PHAR 存档,你还需要启用相应的 扩展。
当前状态
当前版本应支持 PHP 内部反射的大多数功能,并添加更多功能。
每个版本都使用我们的测试包(几个 PHP 框架和其他库)进行测试,并在 5.3 和 5.4 分支的所有 PHP 版本以及实际的主分支上进行兼容性测试。