kornrunner/ php-token-reflection
README
简而言之,此库使用标记化的PHP源代码模拟PHP反射模型。
基本概念是,任何反射都可以处理描述反射元素的特定部分的标记数组。它还能够找出是否存在任何子元素(例如,类反射能够找出源中的方法定义),创建它们的反射并将适当的标记数组部分传递给它们。
这个概念允许我们保持解析器代码相对简单且易于维护。我们能够一次性创建所有反射。这对库的性能至关重要。
所有反射实例都保存在一个TokenReflection\Broker实例中,所有反射都知道创建它们的Broker。这非常重要,因为例如,类反射持有其内部实例化的所有常量、方法和属性反射,但它对父类或实现的接口一无所知。它只知道它们的完全限定名。所以当你调用$reflectionClass->getParentClass();
时,类反射会向Broker请求一个按名称的类反射并返回它。
当定义了父类但它未被处理时(换句话说,你请求Broker一个它不知道的类),它仍然会返回一个反射!是的,我们确实有不存在类的反射!太酷了!
有文件 (*), 文件-命名空间 (*), 命名空间,类,函数/方法,常量,属性和参数的反射。你通常不会接触到带星号的那些,但它们在内部使用。
ReflectionFile是我们反射树中的顶级结构。它获取整个标记化源并尝试在其中找到命名空间。如果找到了,它会创建ReflectionFileNamespace实例并将适当的标记数组部分传递给它们。如果没有找到,它会创建一个单例伪命名空间(称为no-namespace)并将整个标记化源传递给它。
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 版本以及实际的主干上进行测试。