kornrunner/php-token-reflection

此包已被废弃,不再维护。作者建议使用popsul/php-token-reflection包。

使用标记化源代码模拟PHP内部反射的库。

v1.5.6 2016-03-17 00:55 UTC

This package is auto-updated.

Last update: 2022-02-01 12:55:07 UTC


README

Build Status

简而言之,此库使用标记化的PHP源代码模拟PHP反射模型。

基本概念是,任何反射都可以处理描述反射元素的特定部分的标记数组。它还能够找出是否存在任何子元素(例如,类反射能够找出源中的方法定义),创建它们的反射并将适当的标记数组部分传递给它们。

这个概念允许我们保持解析器代码相对简单且易于维护。我们能够一次性创建所有反射。这对库的性能至关重要。

所有反射实例都保存在一个TokenReflection\Broker实例中,所有反射都知道创建它们的Broker。这非常重要,因为例如,类反射持有其内部实例化的所有常量、方法和属性反射,但它对父类或实现的接口一无所知。它只知道它们的完全限定名。所以当你调用$reflectionClass->getParentClass();时,类反射会向Broker请求一个按名称的类反射并返回它。

当定义了父类但它未被处理时(换句话说,你请求Broker一个它不知道的类),它仍然会返回一个反射!是的,我们确实有不存在类的反射!太酷了!

有文件 (*), 文件-命名空间 (*), 命名空间,类,函数/方法,常量,属性和参数的反射。你通常不会接触到带星号的那些,但它们在内部使用。

ReflectionFile是我们反射树中的顶级结构。它获取整个标记化源并尝试在其中找到命名空间。如果找到了,它会创建ReflectionFileNamespace实例并将适当的标记数组部分传递给它们。如果没有找到,它会创建一个单例伪命名空间(称为no-namespace)并将整个标记化源传递给它。

ReflectionFileNamespace从文件中获取命名空间定义,找出其名称,其他别名命名空间并尝试找到定义的任何常量、函数和类。如果找到了,它会创建它们的反射并将适当的标记数组部分传递给它们。

ReflectionNamespace在名称上相似(但意义上有很大不同)。它为每个命名空间是唯一的结构,并在此特定命名空间内部持有所有常量、函数和类。实际上,它是一个简单的容器。它也不是直接由任何父反射创建的,而是Broker创建的。

为什么需要两个独立的类?因为命名空间可以分成许多文件,并且每个文件可以有单独的命名空间别名。在解析父类/接口名称时必须考虑这些别名。这意味着为每个文件中的每个命名空间创建一个ReflectionFileNamespace,它解析其内容,解析所有类的完全限定名、它们的父类和接口。之后,Broker将所有同一命名空间的ReflectionFileNamespace实例合并成一个ReflectionNameaspace实例。

ReflectionClassReflectionFunctionReflectionMethodReflectionParameterReflectionProperty 与它们的内部反射同名功能相同。

ReflectionConstants 是我们对反射模型的补充。它能做的事情不多 - 可以返回其名称、值(我们将在后面讨论值)以及如何定义。

几乎所有反射类都有一个共同的基类,该基类定义了一些共同的功能和接口。这意味着我们的反射模型比内部模型更加统一。

存在针对标记化源代码(如上面提到的)的反射,但也存在内部反射的派生类,它们实现了我们的附加功能(它们都使用相同的接口)。它们代表了 PHP 的内部类、函数等。所以当你向代理请求内部类时,它返回一个封装内部反射功能并添加我们功能的 TokenReflection\Php\ReflectionClass 实例。还有一个 TokenReflection\Php\ReflectionConstant 类,在内部反射模型中没有父类。

备注

从一开始,我们就试图尽可能与内部反射兼容(包括像以相同 - 很奇怪 - 的顺序返回接口列表这样的东西)。然而,有些情况下这是不可能的(例如,我们更倾向于一致性,而不是与内部反射兼容,并且不会将 这个错误 引入到库中)。

我们在处理常量值以及属性和参数默认值方面受到限制。当定义为常量时,我们会尽力解析其值(在解析和内部常量中),并使用它。这最终是通过组合 var_export()eval() 来完成的。是的,这很糟糕,但这是没有更好的方法。此外,引用的常量可能不存在。在这种情况下,它将被替换为 ~~NOT RESOLVED~~ 字符串。

不支持运行时常量。

当库遇到重复的类、函数或常量名称时,它会将先前创建的反射转换为“无效反射”实例。这意味着解析器无法区分这样的类,它无法构建正确的类树,例如。它将抛出一个异常。当你捕获这个异常并继续使用代理实例工作时,重复的类、函数或常量将只有一个反射,并且它将是 Invalid\ReflectionClassInvalid\ReflectionFunctionInvalid\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 版本以及实际的主干上进行测试。