PHP反射机制

Published on:
Tags: php

PHP的反射机制提供了一套反射API,用来访问和使用类、方法、属性、参数和注释等。

比如可以通过一个对象知道这个对象所属的类,这个类包含哪些方法,这些方法需要传入什么参数,每个参数是什么类型等等。

不用创建类的实例也可以访问类的成员和方法,就算类成员定义为 private也可以在外部访问。

官方文档 提供了诸如 ReflectionClass、ReflectionMethod、ReflectionObject、ReflectionExtension 等反射类及相应的API,用得最多的是 ReflectionClass。

ReflectionClass 反射类#

通过 ReflectionClass 反射一个类,参数是类名或者类实例。

通过构造方法实例化类#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Test1 类
*/
class Test1
{
private $id;
public $name;

public function __construct(int $id,string $name)
{
$this->id = $id;
$this->name = $name;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}

}

Test1 类拥有一个 private 类型的 id 和 一个 public 类型的 name 。

还有一个构造方法和两个普通方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 初始化 ReflectionClass 类
function getReflectObject($class)
{
try {
return new ReflectionClass($class);
} catch (ReflectionException $e) {
throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
}
}

// 实例化类后调用方法
function invokeClass_1(string $class,array $args)
{
$reflect = getReflectObject($class);
// 创建一个类的新实例,给出的参数将传递到类的构造函数。
$object = $reflect->newInstanceArgs($args);

// 获取类的属性,返回ReflectionProperty 对象的数组。
$props = $reflect->getProperties();
foreach ($props as $prop) {
// 输出属性变量名和指定实例的属性值
echo $prop->getName() . "=".$prop->getValue($object)."\n";
}
// 类实例直接调用方法
$name = $object->getName();

return $name;
}

$res = invokeClass_1('Test1',[1,'Jerry']);
var_dump($res);

输出

1
2
3
id=1
name=Jerry
string(5) "Jerry"

通过单例模式实例化类#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* Test2 类
*/
class Test2
{
private $id;
public $name;
private static $_instance;

private function __construct(int $id,string $name)
{
$this->id = $id;
$this->name = $name;
}

// 把构造函数 __construct 改成 private,并增加 getInstance 方法
public static function getInstance(int $id,string $name)
{
if(empty(self::$_instance))
self::$_instance = new static($id,$name);

return self::$_instance;
}

public function getId()
{
return $this->id;
}

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

public static function sayHi($id,$name)
{
return "hello {$name}, your id={$id}.";
}

public static function index()
{
return "hello index";
}

}

Test2 类的构造方法是 private 的,因此外部需要通过静态方法 getInstance 来获得类的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 初始化 ReflectionClass 类
function getReflectObject($class)
{
try {
return new ReflectionClass($class);
} catch (ReflectionException $e) {
throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
}
}

// 调用静态方法
function invokeClass_2(string $class,array $args)
{
$reflect = getReflectObject($class);
$name = null;
// 检查类中是否存在指定的方法
if ($reflect->hasMethod('getInstance')) {
// 获取一个类方法的 ReflectionMethod。
$method = $reflect->getMethod('getInstance');
if ($method->isPublic() && $method->isStatic()) {
// 使用数组给方法传送参数,并执行他。
$object = $method->invokeArgs(null, $args);
$name = $object->getName();
}
}
return $name;
}

$res = invokeClass_2('Test2',[2,'Sam']);
var_dump($res);

输出

1
string(3) "Sam"

ReflectionMethod 反射类#

ReflectionMethod 类也具有反射一个类的作用。区别是参数需要传递类名和方法名。

调用有参数的方法#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 初始化 ReflectionMethod 类
function getReflectMethodObject($class,$name)
{
try {
return new ReflectionMethod($class,$name);
} catch (ReflectionException $e) {
throw new ClassNotFoundException('class or method not exists: ' . $class, $class, $e);
}
}

// 调用静态方法
function invokeMethod_1(string $class,string $name,array $args)
{
$method = getReflectMethodObject($class,$name);
// 获取方法定义的参数数目,包括可选参数
$num = $method->getNumberOfParameters();
if($num > 0)
// 使用数组给方法传送参数,并执行他。
$res = $method->invokeArgs(null,$args);
else
// 执行一个反射的方法。
$res = $method->invoke(null);

return $res;
}
$res = invokeMethod_1('Test2','sayHi',[2,'Sam']);
var_dump($res);

输出

1
string(21) "hello Sam, your id=2."

以上功能作用与

1
$res = call_user_func_array([__NAMESPACE__.'Test2','sayHi'],[2,'Sam']); 

相似。

调用无参数的方法#

1
2
$res = invokeMethod_1('Test2','index',[]);
var_dump($res);

输出

1
string(11) "hello index"

以上功能作用与

1
$res = call_user_func([__NAMESPACE__.'Test2','index']);

相似。

ReflectionFunction 反射类#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 一个处理数据的方法
function processUserData($name, $age, $job = "", $hobbie = "")
{
$msg = "Hello $name. You have $age years old";
if (!empty($job)) {
$msg .= ". Your job is $job";
}

if (!empty($hobbie)) {
$msg .= ". Your hobbie is $hobbie";
}

return $msg . ".";
}

$refFunction = new ReflectionFunction('processUserData');
$valuesToProcess = [
'name' => 'Anderson Lucas Silva de Oliveira',
'age' => 21,
'hobbie' => 'Play games'
];
$res = $refFunction->invoke(...$valuesToProcess);
var_dump($res);

输出

1
string(89) "Hello Anderson Lucas Silva de Oliveira. You have 21 years old. Your hobbie is Play games."

一个demo#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 通过反射调用函数或者类方法
function invoke($class,$function,$args)
{
if(function_exists($function))
{
$refFunction = new ReflectionFunction($function);
$res = $refFunction->invoke(...$args);
}else{
$reflect = getReflectObject($class);
$constructor = $reflect->getConstructor();
// 构造函数是公有的
if($constructor->isPublic())
{
$object = $reflect->newInstanceArgs($args);
}else if($reflect->hasMethod('getInstance')){
// 当构造函数为 private ,则通过 getInstance 方法获取类的实例
$method = $reflect->getMethod('getInstance');
if ($method->isPublic() && $method->isStatic())
// 使用数组给方法传送参数,并执行他。
$object = $method->invokeArgs(null, $args);

}
$res = $object->$function();
}
return $res;
}

$valuesToProcess = [
'name' => 'Anderson Lucas Silva de Oliveira',
'age' => 21,
'hobbie' => 'Play games'
];

$res = invoke(null,'processUserData',$valuesToProcess);
// $res = invoke('Test1','getName',[2,'Sam']);
// $res = invoke('Test2','getName',[1,'Jerry']);

var_dump($res);

invoke 函数接收3个参数,分别是:类名,方法名/函数名,传参数组。

invoke 函数会首先判断是否存在函数,存在的话则直接传递参数,执行函数;

1
$res = invoke(null,'processUserData',$valuesToProcess);

若不存在对应的函数,则通过反射类反射指定的类。
首先判断构造函数的权限是否为 public ,是的话则通过构造函数实例化类,最后再调用方法;

1
$res = invoke('Test1','getName',[2,'Sam']);

若构造函数的权限不为 public ,则通过判断是否存在 getInstance 方法,并且方法权限为 public 和 static , getInstance 方法体使用单例模式返回类的实例。调用 getInstance 方法得到类的实例,最后调用方法。

1
$res = invoke('Test2','getName',[1,'Jerry']);