PHP 反射

Posted 知其黑、受其白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP 反射相关的知识,希望对你有一定的参考价值。

阅读目录

参考文档

官网:
https://www.php.net/manual/zh/book.reflection.php
https://www.php.net/manual/zh/class.reflection.php
https://www.qycn.com/xzx/article/405.html

Laravel 通过类的反射, 对类的私有属性赋值

TestController 类

<?php

namespace App\\Http\\Controllers\\Services;

use App\\CommentModel;
use App\\Http\\Controllers\\Controller;

class TestController extends Controller

    public function getList()
    
        $data = ["good_id"=>1,"name"=>"商品名"];
        $good = $this->ins(TwoTestController::class ,$data);
        dd($good);
    
    /**
     * @param $class
     * @param Array $data
     * @return object
     * @throws \\ReflectionException
     */
    public function ins($class , $data)
    
        $classObj = new \\ReflectionClass($class);// 反射 TwoTestController 类
        $classMethods = $classObj->getMethods(\\ReflectionMethod::IS_PUBLIC); // 获取公共方法
        $class_instantce = $classObj->newInstance(); // 通过反射对象创建类的实例

        foreach ($classMethods as $method)
//            echo $method->getName().PHP_EOL; // 获取公共方法的方法名
            # 过滤出set开头的公共方法(我这里只有一个setGoodId方法)
            if (preg_match("/^set(\\w+)/" ,$method->getName() ,$matches)) 
//                echo $matches[1].PHP_EOL; // 输出的是GoodId
//                echo $this->filterMethod($matches[1]).PHP_EOL; // 输出的是good_id
                $this->filterMethod($matches[1] ,$classObj ,$data, $class_instantce);
            
        
        return $class_instantce;
    

    function filterMethod($name ,\\ReflectionClass $classObj ,$data , &$class_instance)
        // GoodId 转换成 Good_Id , 左边是小写才添加_ , aGoodId 转换成a_Good_Id
        $filter_name =  strtolower(preg_replace("/(?<=[a-z])([A-Z])/","_$1" ,$name));
        $props = $classObj->getProperties(\\ReflectionProperty::IS_PRIVATE); //获取私有属性
        foreach ($props as $prop)
            if ($prop->getName() == $filter_name)
                $method = $classObj->getMethod("set".$name);
                $args = $method->getParameters(); // 获取方法的参数
                // 参数数量为1 , 并且数据中存在对应的值
                if (count($args)==1 && isset($data[$filter_name]))
                    // 反射执行方法
                    $method->invoke($class_instance, $data[$filter_name]);
                
            
        
    

TwoTestController 类

<?php

namespace App\\Http\\Controllers\\Services;

use Illuminate\\Http\\Request;
use App\\Http\\Controllers\\Controller;

class TwoTestController extends Controller

    private $good_id;
    private $name;

    public function setGoodId($good_id)
    
        $this->good_id = $good_id;
    

    public function getGoodId()
    
        return $this->good_id;
    

    public function setName($name)
    
        $this->name = $name;
    

    public function getName()
    
        return $this->name;
    

预览

PHP中的反射理解

从一个简单的例子理解反射:
人有五官四肢,但鲜有人清楚人体内部的经脉走向、骨骼构造。
如果你修仙顺利,在丹田深处练出元婴,那么就通过元婴透析身体内部的构造。
理解内部构造后,还可以让元婴指引体内真气在经脉的流向,早日修成正果。

如其名,反射是(从镜子里)照出自身。
我们写代码,告诉代码怎么运行,事件发生在编译期。
代码运行期间,代码如何知道自己的结构以及能力呢?
反射机制相当于代码的元婴,使代码能够感知自身结构,并可(部分)改变运行行为。

与运行时类型信息(Runtime Type Informatiion, RTTI)不同,反射重点在运行时检测、感知、改变自身的结构和行为。

反射是元编程(metaprogramming)的重要组成部分。

PHP反射API均以 Reflection 开头

反射不是语法分析,不操作表达式、代码语句。

反射获取的是代码的结构,即函数、类这些构件的结构。

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

以单例来说一下反射API的功能,单例类代码如下:

#foo.php
<?php

class Foo

    private static $id;
    private static $instance;

    private function __construct()
    
        ++self::$id;
        fwrite(STDOUT, "construct, instance id: " . self::$id . "\\n");
    

    public static function getSingleton()
    
        if (self::$instance === null) 
            self::$instance = new self();
        
        return self::$instance;
    


在 Foo 类中,构造函数是私有,获取实例只能通过 getSingleton 方法,并且获取到的是单例。

但在反射API加持下,能获取多个实例:

$instance1 = Foo::getSingleton();
var_dump($instance1);

PS E:\\PDF\\test> php .\\foo.php
construct, instance id: 1
object(Foo)#1 (0) 

PS E:\\PDF\\test>
# ReflectionClass 类报告了一个类的有关信息,建立Foo这个类的反射类
$class = new ReflectionClass("Foo");
# 获取类的构造函数
$constructor = $class->getConstructor();

// var_dump(ReflectionProperty::IS_PUBLIC);exit;int(1)
// var_dump($constructor->getModifiers());
// PS E:\\PDF\\test> php .\\foo.php
// int(4)
// construct, instance id: 1
// object(Foo)#3 (0) 
// 

// var_dump(ReflectionProperty::IS_PUBLIC & $constructor->getModifiers());exit;//0

# ReflectionProperty::IS_PUBLIC,检查属性是否为公共
# Gets the property modifiers,获取属性修饰符
if ((ReflectionProperty::IS_PUBLIC & $constructor->getModifiers()) === 0) 
    # 设置访问控制权:setAccessible 可获取私有的方法/属性。
    # 注意:setAccessible 只是让方法/成员变量可以 invoke/getValue/setValue,并不代表类定义的访问存取权限改变;
    $constructor->setAccessible(true);

# 创建一个新的类的实例而不调用它的构造函数。
$instance2 = $class->newInstanceWithoutConstructor();
# ReflectionMethod::invoke()函数是PHP中的内置函数,用于调用指定的反射方法并返回方法的结果。
$constructor->invoke($instance2);
var_dump($instance2);
# 脚本执行结果
construct, instance id: 1
object(Foo)#1 (0) 

construct, instance id: 2
object(Foo)#4 (0) 

我们成功的生成了两个实例,并调用构造函数完成对象初始化。
如果没有反射API,这几乎是不可能完成的工作。

反射API和函数式API在功能上的差异

功能函数式API反射API
函数是否存在function_existsReflectionFunction
类是否存在class_exitsReflectionClass
方法是否存在method_exitsReflectionMethod
变量/属性是否存在property_exitsReflectionProperty
获取类变量get_class_varsReflectionClass::getProperties
获取类方法get_class_methodsReflectionClass::getMethods
获取类常量ReflectionClass::RegetReflectionConstant(s)
获取函数/方法参数信息ReflectionFunction/Method::getParameters
获取函数/方法返回值ReflectionFunction/Method::getReturnType
类使用的特性class_usesReflectionClass::getTraits
获取父类class_parentsReflectionClass::getParentClass
获取类实现的接口class_implementsReflectionClass::getInterfaceNames
获取类所在名字空间__NAMESPACE__ReflectionClass::getNamespaceName
函数调用call_user_func(_array)ReflectionMethod(Function)::invoke(Args)
获取类名__CLASS__/::classReflectionClass::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_loadedReflectionZendExtension
拓展get_loaded_extensionsReflectionExtension
get_extension_funcs

函数式API和反射API在功能上的差异

功能函数式API反射API
类型判断is_int/is_bool/is_array等
获取对象的类名get_classReflectionObject::getName
获取对象父类get_parent_classReflectionObject::getParentClass
类型/继承检测instanceof/is_a/is_subclass_ofReflectionObject::isInstance/isSubclassOf
生成器ReflectionGenerator

php 反射类 ReflectionClass

<?php

class fuc

    //定义一个类
    public static function ec()
    
        echo'我是一个类';
    



$class = new ReflectionClass('fuc'); //建立 fuc这个类的反射类

echo $class; //输出这反射类

//相当于实例化 fuc 类
$fuc=$class->newInstance();  
//执行 fuc 里的方法ec
$fuc->ec(); 
PS E:\\PDF\\test> php .\\foo.php
Class [ <user> class fuc ] 
  @@ E:\\PDF\\test\\foo.php 3-11

  - Constants [0] 
  

  - Static properties [0] 
  

  - Static methods [1] 
    Method [ <user> static public method ec ] 
      @@ E:\\PDF\\test\\foo.php 6 - 9
    
  

  - Properties [0] 
  

  - Methods [0] 
  

我是一个类
PS E:\\PDF\\test>

示例1

<?php
/**
 * Created by PhpStorm.
 * User:  author
 * Date: 2017/6/12
 * Time: 14:34
 * 关于反射类的理解
 */
class Person 
    /**
     * The attributes that are mass assignable.
     * @type int
     * 属性注释
     */
    public  $datang = '123';
    public  $datang1 = '1234';
 
    private function getName()
        echo  $this->datang;
    
 
    public function getName1()
        echo $this->datang1;
    
 

 
//建立 Person这个类的反射类
$class = new ReflectionClass('Person');
 
//打印所有属性名,包含private,protected,public
$properties = $class->getProperties();
foreach ($properties as $key => $value) 
    var_dump($value->getName());


/*exit;
PS E:\\PDF\\test> php .\\foo.php
string(6) "datang"
string(7) "datang1"
*/

//打印所有属性的注释并正则,包含private,protected,public
foreach($properties as $property) 
    $docblock = $property->getDocComment();
    //只能打印多行注释
    var_dump($docblock);
    preg_match('/ type\\=([a-z_]*) /', $docblock, $matches);
    var_dump($matches);



/* exit;
string(100) "
     * The attributes that are mass assignable.
     * @type int
     * 属性注释
     *
array(0) 

bool(false)
array(0) 

*/
 
//打印所有方法名
foreach ($class->getMethods() as $key => $value) 
    var_dump($value->getName());


/*exit;
string(7) "getName"
string(8) "getName1"
*/
 
/** 如何执行方法 */
//相当于实例化Person 类
$instance  = $class->newInstanceArgs();
//执行getName1方法,方法必须是public属性的,否则会报fatal error
$instance->getName1();
 
//获取Person getName1方法
$ec=$class->getmethod('getName1');
//执行getName1方法,方法必须是public属性的,否则会报fatal error
$ec->invoke($instance);

简单的学习反射

<?php
/*
 *   简单的学习反射
 */
class Reflectionstudy

    /** name 姓名*/
    private $name;

    /*
     * 反射
     */
    public static function ref1()
    
        $ref   = new ReflectionClass('Redis'); //获得Redis的一个反射实例
        $const = $ref->getConstants(); //返回所有常量名和值
        echo "----------consts---" . PHP_EOL;
        array_walk($const, function ($val, $key) 
            echo $key, ':', $val, PHP_EOL;
        );
        $props = $ref->getDefaultProperties(); //返回类中所有属性
        echo "----------props---" . PHP_EOL;
        array_walk($props, function ($val, $key) 
            echo $key, ':', $val, PHP_EOL;
        );
        $methods = $ref->getMethods(); //返回类中所有方法
        echo "----------methods---" . PHP_EOL;
        array_walk($methods, function ($val) 
            echo $val, PHP_EOL;
        );

        //当想获取具体的方案有哪些参数时,我们需要进一步反射
        echo "----------params---" . PHP_EOL;
        echo $ref->getMethod('bitpos'); //传入方法名,会调用$reflectMetho                                                                                        d->__tostring() 返回可打印的数组
    
    /**
     * get name of person
     *@param $v string name
     *@return $name
     */
    public function getName()
    
        return self::$name;
    
    public function setName($v)
    
        self::$name = $v;
    


1、用户获取对象相关信息 ReflectionObject

/*
1、用户获取对象相关信息ReflectionObject
*/
$ro = new ReflectionObject(new Reflectionstudy);
print_r($ro->getMethods());
PS E:\\PDF\\test> php .\\foo.php
Array
(
    [0] => ReflectionMethod Object
        (
            [name] => ref1
            [class] => Reflectionstudy
        )

    [1] => ReflectionMethod Object
        (
            [name] => getName
            [class] => Reflectionstudy
        )

    [2] => ReflectionMethod Object
        (
            [name] => setName
            [class] => Reflectionstudy
        )

)
PS E:\\PDF\\test>

2、反射某个方法以及注释信息

/*
2、反射某个方法以及注释信息
*/
$rm = new ReflectionMethod('Reflectionstudy','getName');
echo $rm->isPublic(),PHP_EOL;//确认方法是否公有,返回BOOL值
echo $rm->getDocComment(),PHP_EOL;//返回文档注释
PS E:\\PDF\\test> php .\\foo.php
1
/**
     * get name of person
     *@param $v string name
     *@return $name
     */
PS E:\\PDF\\test>

3、ReflectionParameter获取函数或方法参数的相关信息

/*
3、ReflectionParameter获取函数或方法参数的相关信息
*/

$p = new ReflectionParameter(['Reflectionstudy','setName'],0);
echo $p->以上是关于PHP 反射的主要内容,如果未能解决你的问题,请参考以下文章

PHP通过反射来得到类,以及一些基本的应用

PHP通过反射来得到类,以及一些基本的应用

PHP中的反射

php反射

043-PHP简单获得一个类对应的反射信息

PHP 反射的实现