PHP中反射怎样理解?一文带你看懂反射
反射
PHP 具有完整的反射 API,增加了内省类、接口、函数、方法和扩展的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。
反射在当今几乎所有的 PHP 框架或者工具中都占用非常重要的角色,就比如 Laravel 的容器,容器对于 Laravel 架构来说极其重要
Laravel 的核心类 Illuminate\Foundation\Application 就是继承自 Illuminate\Container\Container 类。
PHP反射API
对于反射,我们会接触到四个类,分别是:
- ReflectionClass
- ReflectionFunction
- ReflectionMethod
- ReflectionParameter
PHP中的反射API均以Reflection
开头(接口Reflector
除外),重点在函数和类两种结构。而函数可以看成类的成员函数(多一个隐式的this参数)或者静态成员函数(public
类型),所以了解反射API可从类信息的ReflectionClass
开始。
ReflectionClass提供了以下获取类基本信息的接口:
getProperties
:获取成员变量/属性,返回一个ReflectionProperty
数组;ReflectionProperty
类中有对属性详细说明的API:是否默认属性(isDefault),是否私有属性(isPrivate)等。同时ReflectionClass
还提供获取特定类别属性的API:getDefaultProperties
,getStaticProperties
;getConstants
:获取类中定义的常量;getMethods
:获取类中定义的方法,返回一个ReflectionMethod数组;ReflectionMethod将在下文讲解;getInterfaces
:获取类实现的接口;getParentClass
:获取父类的ReflectionClass实例。
在反射中,类、接口、特性不分家,所以ReflectionClass
提供类型判定API:isInterface
、isTrait
。
ReflectionClass
(包括ReflectionMethod/ReflectionFunction
)还提供了一些不可思议的能力:
getDocComment
:获取类的文档注释信息;getFilename
:获取类定义的文件;getStartLine
: 获取类定义的起始行号;getEndLine
: 获取类定义的结束行号;getModifiers
:获取类定义的修饰符,其意义名字可通过Reflection::getModifierNames得到,例如:abstract,final
如果说前述的类结构信息可以通过现有的API获取(method_exits/property_exits
等),
上面列出的功能基本上只能通过反射API获取(PHP文件中定义的类并且知道定义文件,可以利用token_get_all
得到相同结果,但是实现非常复杂)。
这些行为发生在运行期间。由此可见反射API在分析类结构信息功能上的强大。
除了ReflectionClass
,ReflectionMethod
和ReflectionFunction
是另外反射中另外两个重要的类。
函数(function
)定义在类外部,
方法(method
)定义在类内部,两者其实同源,在反射API中有共同的父类:ReflectionFunctionAbstract
。ReflectionFunctionAbstract
有两者的大部分API,并且基本上是最重要的API。
其中最值得关注的是其参数信息的API:getParameters
。其获取函数的参数信息,返回一个ReflectionParameter
数组。结合getParameters
和ReflectionParameter
,函数(方法)的结构基本上就清晰了。
API操作
反射让代码感知自身结构,有什么好处呢?反射API提供了三种在运行时对代码操作的能力:
- 设置访问控制权:
setAccessible
。可获取私有的方法/属性。注意:setAccessible
只是让方法/成员变量可以invoke/getValue/setValue
,并不代表类定义的访问存取权限改变; - 调用函数/方法:
invoke/invokeArgs
。配合获取函数参数的API,可以安全的传参和调用函数,call_user_func(_array)
的增强版; - 不依赖构造函数生成实例:
newInstanceWithoutConstructor
。
一个简单的例子
class Printer
{
}
class Student
{
private $name;
private $year;
public function __construct($name, $year)
{
$this->name = $name;
$this->year = $year;
}
public function getValue()
{
return $this->name;
}
public function setBase(Printer $printer, $name, $year = 10)
{
$this->name = $name;
$this->year = $year;
}
}
上面我们声明了 2 个类 Printer 和 Student,测试如下:
$refl_class = new ReflectionClass(Student::class);
$object = $refl_class->newInstanceArgs(["obama", 100]);
echo get_class($object) . "\n";
echo $object->getValue();
控制台打印结果如下:
首先打印的是 Student 这个类的类名,然后打印了 obama
解释:
new ReflectionClass (Student::class) 创建了一个代表 Student 类的 ReflectionClass 对象,这个类的对象有一个方法 newInstanceArgs,这个方法可以创建 Student::class 类的对象,
它的参数为 Student 类构造函数所需要的参数,我们的参数为 obama 和 100,
紧接着调用 get_class 方法获取刚才创建对象的类,也就是我们打印的第一行 Student,印证了我们上面所说的。因为 $object 就是 Student 类的对象,所以我们可以调用 getValue 方法,返回值就是 name,结果为 obama
再来看一个:
$refl_method = $refl_class->getMethod("setBase");
echo get_class($refl_method) . "\n";
$parameters = $refl_method->getParameters();
foreach ($parameters as $parameter) {
echo $parameter->getName() . "\n";
if ($parameter->getClass() != null) {
echo $parameter->getClass()->getName() . "\n";
}
if ($parameter->isDefaultValueAvailable()) {
echo $parameter->getDefaultValue() . "\n";
}
}
上面的代码运行结果如下
之前我们创建了 ReflectionClass 类的对象为 $refl_class,
这里我们调用它的方法 getMethod,这个 getMethod 会返回当前 Student 类的 setBase,但是它是一个 ReflectionMethod 类的实例,这里的 get_class ($refl_method) 就会打印出 $refl_method 的类为 ReflectionMethod,也就是控制台的第一行结果,
ReflectionMethod 类有一个方法叫做 getParameters,这个方法会返回 ReflectionMethod 所对应方法(这里就是 setBase)的所有参数构成的数组,
这个数组的每一个元素都是 ReflectionParameter 类的对象,接下来的 foreach 遍历所有的参数,
首先检查 $parameter->getClass () 的返回值,ReflectionParameter 类的 getClass 方法返回的是这个参数所对应的类,也就是说 getClass 返回的是 ReflectionClass 类的对象,ReflectionClass 类的对象有一个 getName 方法,这个方法返回类的名字,
ReflectionParameter 类还有一个比较常用的方法,就是 isDefaultValueAvailable,他检查这个参数是否有默认的值,如果这个参数有默认的值的话,那么 getDefaultValue 方法就可以获取到这个默认值。
上面分析了 ReflectionMethod 和 ReflectionParameter 两个类,还有一个要讲,如下:
function display($a, $b, Printer $printer)
{
echo "called" . "\n";
}
$refl_function = new ReflectionFunction("display");
$parameters = $refl_function->getParameters();
foreach ($parameters as $parameter) {
echo $parameter->getName() . "\n";
if ($parameter->getClass() != null) {
echo $parameter->getClass()->getName() . "\n";
}
if ($parameter->isDefaultValueAvailable()) {
echo $parameter->getDefaultValue() . "\n";
}
}
代码运行结果如下:
首先我定义了名为 display 的方法,它有三个参数,
紧接着看,我们创建了一个 ReflectionFunction 类的对象,它的参数为函数名,也就是说 ReflectionFunction 是对函数的封装。
ReflectionFunction 类也有一个名为 getParameters 的方法,他返回的值和 ReflectionMethod 的 getParameters 方法的返回值是一样的,都是
ReflectionMethod 类的数组,接下来的 foreach 便利操作和上面讲解 ReflectionMethod 类的操作的时候是一模一样的,不再详述。
更多代码放在gitee了php-base-container: php如何实现基本的容器操作 (gitee.com)
反射API和函数式API对比
整理一下反射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 反射的基本使用
PHP中反射怎样理解?一文带你看懂反射
版权声明:
作者:linrux
链接:https://www.tot7.cn/technology/php/444.html
来源:阿信博客
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论