PHP中反射怎样理解?一文带你看懂反射

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

反射

PHP 具有完整的反射 API,增加了内省类、接口、函数、方法和扩展的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。

反射在当今几乎所有的 PHP 框架或者工具中都占用非常重要的角色,就比如 Laravel 的容器,容器对于 Laravel 架构来说极其重要

Laravel 的核心类 Illuminate\Foundation\Application 就是继承自 Illuminate\Container\Container 类。

PHP反射API

反射不是语法分析,不操作表达式、代码语句。反射获取的是代码的结构,即函数、类这些构件的结构

对于反射,我们会接触到四个类,分别是:

  1. ReflectionClass
  2. ReflectionFunction
  3. ReflectionMethod
  4. ReflectionParameter

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

一个简单的例子

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 方法就可以获取到这个默认值。

 

上面分析了 ReflectionMethodReflectionParameter 两个类,还有一个要讲,如下:

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 反射的基本使用

Dennis_Ritchie PHP,Golang,K8s,Docker,Rust,C/C++,数据库爱好者,linux操作系统爱好者
<

PHP中反射怎样理解?一文带你看懂反射

版权声明:
作者:linrux
链接:https://www.tot7.cn/technology/php/444.html
来源:阿信博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>
文章目录
关闭
目 录