简单学习PHP中的反射
和Java一样PHP中也提供了一套完整的反射API
,何为反射
?以前我们是先写类,再在类中添加各种方法和属性,最后实例化一个类对象调用属性和方法。那有我们没有办法只通过这个实例对象获取到关于这个类的全部信息呢,包括有哪些方法和属性它们分别在多少行?答案就是反射
。
几个常用的反射API的类

ReflectionClass
class People {
protected $name = 'Foo';
protected $age = 18;
public function __construct($name,$age)
{
$this->name = $name;
$this->age = $age;
}
public function func1()
{
echo 'fun1';
}
public function toString()
{
echo "Name: $this->name, Age: $this->age" . PHP_EOL;
}
}
class Student extends People {
public $major;
public function __construct($name, $age, $major)
{
$this->major = $major;
parent::__construct($name, $age);
}
public function toString()
{
echo "Name: $this->name, Age: $this->age, Major: $this->major" . PHP_EOL;
}
}
$reflect = new ReflectionClass('Student');
// 也可以传一个实例对象
// $reflect = new ReflectionClass(new Student('Jason',21,'CS'));
Reflection::export($reflect);
输出结果如下:
Class [ <user> class Student extends People ] {
@@ /…/demo1.php 18-31
- Constants [0] {
}
- Static properties [0] {
}
- Static methods [0] {
}
- Properties [3] {
Property [ <default> public $major ]
Property [ <default> protected $name ]
Property [ <default> protected $age ]
}
- Methods [2] {
Method [ <user, overwrites People, ctor> public method __construct ] {
@@ /…/demo1.php 21 - 25
- Parameters [3] {
Parameter #0 [ <required> $name ]
Parameter #1 [ <required> $age ]
Parameter #2 [ <required> $major ]
}
}
Method [ <user, overwrites People, prototype People> public method toString ] {
@@ /…/demo1.php 27 - 30
}
}
}
可以看到,Reflection::export
几乎提供了Student
类的所有信息,包括属性和方法的访问控制状态,每个方法需要的参数以及每个方法在脚本中的位置,和var_dump
相比,在使用var_dump
前总是需要实例化一个对象,而且无法提供那么多详细信息。
var_dump
输出如下:
object(Student)#1 (3) {
["major"]=>
string(2) "CS"
["name":protected]=>
string(5) "Jason"
["age":protected]=>
int(21)
}
虽然var_dump
和print_r
是打印数据的利器,但对于类和函数,反射API提供了更高层次的功能。
同时,ReflectionClass
还提供其他有用的方法:
// 获取构造函数
$reflect->getConstructor();
object(ReflectionMethod)#2 (2) {
["name"]=>
string(11) "__construct"
["class"]=>
string(7) "Student"
}
// 获取类名
$reflect->getName();
string(7) "Student”
// 判断接口类
$reflect->isInterface(); // false
// 判断抽象类
$reflect->isAbstract(); // false
// 类开始行
$reflect->getStartLine();
// 类结束行
$reflect->getEndLine();
详细的可以看官方文档: https://www.php.net/manual/en/class.reflectionclass.php
ReflectionMethod
ReflectionMethod
用于检查类中的方法,获得ReflectionMethod
对象的方式有几种,
- 可以从
ReflectionClass::getMethods
获得ReflectionMethod
对象的数组 - 要得到特定的类方法,可以使用
ReflectionClass::getMethod()
,把函数名作为参数传过去 - 直接使用
ReflectionMethod
创建,new ReflectionMethod('Student','toString’)
,第一个参数是类名或类实例,第二个是要获取的函数名
同样,ReflectionMethod
也提供了很多方法来检查类方法:
// 获取类名
$reflect_method->getName()
// 是不是公共的
$reflect_method->isPublic()
// 是不是私有的
$reflect_method->isPrivate()
// 类的开始行
$reflect_method->getStartLine()
// 类的结束行
$reflect_method->getEndLine()
与get_class_methods()
方法对比:
// ReflectionMethod::getParameters()
array(3) {
[0]=>
object(ReflectionMethod)#2 (2) {
["name"]=>
string(11) "__construct"
["class"]=>
string(7) "Student"
}
[1]=>
object(ReflectionMethod)#3 (2) {
["name"]=>
string(8) "toString"
["class"]=>
string(7) "Student"
}
[2]=>
object(ReflectionMethod)#4 (2) {
["name"]=>
string(5) "func1"
["class"]=>
string(6) "People"
}
}
// get_class_methods('Student')
array(3) {
[0]=>
string(11) "__construct"
[1]=>
string(8) "toString"
[2]=>
string(5) "func1"
}
从输出结果可以很明显看出,使用反射的方法输出的结果更详细,可以看到每个方法具体属于哪个类,而get_class_methods
是能打印出方法名。
详细的可以看官方文档: https://www.php.net/manual/en/class.reflectionmethod.php
ReflectionParameter
PHP中,在声明类方法时我们可以限制参数中对象的类型,因此检查方法的参数变得很有必要,反射API提供了ReflectionParameter
类来使用。和获得ReflectionMethod
对象数组的方式一样,我们可以使用ReflectionMethod::getParameters()
方法返回ReflectionParameter
对象数组。
$method = $reflect->getMethod('__construct');
array(3) {
[0]=>
object(ReflectionParameter)#3 (1) {
["name"]=>
string(4) "name"
}
[1]=>
object(ReflectionParameter)#4 (1) {
["name"]=>
string(3) "age"
}
[2]=>
object(ReflectionParameter)#5 (1) {
["name"]=>
string(5) "major"
}
}
Student
构造函数接受三个参数,我们可以知道具体的参数名,通过反射,我们还可以知道参数是否可以按引用传递,参数类型和是否接收空值作为参数等。
上面是我最近对PHP的反射api的简单学习。
发现一个有趣的package
Github
上有个项目叫[Roave](https://github.com/Roave)/[**BetterReflection**](https://github.com/Roave/BetterReflection)
,是这个包提供的方法,我们可以更方便的调用PHP的反射,现在已经有600+
star,地址如下:https://github.com/Roave/BetterReflection

现在我们来简单学习下,这个包如何使用。
1. 安装
$ composer require roave/better-reflection
2. 使用
创建ReflectionClass
use Roave\BetterReflection\Reflection\ReflectionClass;
$classInfo = ReflectionClass::createFromName(Student::class); // 通过类名创建
// 或者
$classInfo = ReflectionClass::createFromInstance(new Student('Daniel',18,'Math')); // 通过实例创建
上面的静态初始化方式就等于下面:
use Roave\BetterReflection\BetterReflection;
$classInfo = (new BetterReflection)->classReflector()->reflect(Student::class);
我们查看静态构造器createFromName
的实现,会发现也是先实例化一个BetterReflection
再进行反射的:
public static function createFromName(string $className) : self
{
return (new BetterReflection())->classReflector()->reflect($className);
}
获取ReflectionMethod
、ReflectionParameter
和ReflectionProperty
也是一样一样的:
use Roave\BetterReflection\Reflection\ReflectionMethod;
use Roave\BetterReflection\Reflection\ReflectionParameter;
use Roave\BetterReflection\Reflection\ReflectionProperty;
$methodInfo = ReflectionMethod::createFromName(Student::class,'toString');
$methodInfo = ReflectionMethod::createFromInstance($student,'toString');
…
3. BetterReflection
中的SourceLocator
SourceLocator
的作用是根据不同的源码类型(有通过字符串传来的类、composer自动加载的类、一个指定文件名的类等), 得到LocatedSource
对象的(用于AST),这个对象指向要反射的类,主要就以下几个方法:
class LocatedSource
{
/** @var string */
private $source;
/** @var string|null */
private $filename;
/**
* @throws InvalidArgumentException
* @throws InvalidFileLocation
*/
public function __construct(string $source, ?string $filename) {…}
public function getSource() : string {…}
public function getFileName() : ?string {…}
/**
* Is the located source in PHP internals?
*/
public function isInternal() : bool {…}
public function getExtensionName() : ?string {…}
/**
* Is the located source produced by eval() or \function_create()?
*/
public function isEvaled() : bool {…}
}
针对不同的情况,比如单个文件、源码字符串、闭包等,又衍生出了多种SourceLocator
:
[图片上传失败...(image-f8dd3b-1557121540941)]
- ComposerSourceLocator: 这应该是最常用的了,它使用
composer
自带的autoload
来定位 - SingleFileSourceLocator: 通过指定文件名来定位
- StringSourceLocator: 直接使用定义类的字符串
- AutoloadSourceLocator: 使用PHP内置的
autoloader
来定位,默认会使用这个定位器 - EvaledCodeSourceLocator: 用于
eval()
创建的代码 - PhpInternalSourceLocator: 用于PHP原生代码
- AnonymousClassObjectSourceLocator: 用于PHP中的匿名类
- ClosureSourceLocator: 用于闭包
- AggregateSourceLocator: 合并多个
SourceLocator
- FileIteratorSourceLocator: 用于可迭代的
SplFileInfo
实例数组 - DirectoriesSourceLocator: 用于文件夹的情况,包括子文件夹
如何使用这这些Locator
?
- 使用
composer
的autoload
加载
use Roave\BetterReflection\BetterReflection;
use Roave\BetterReflection\Reflector\ClassReflector;
use Roave\BetterReflection\SourceLocator\Type\ComposerSourceLocator;
$classLoader = require 'vendor/autoload.php';
$astLocator = (new BetterReflection())->astLocator();
$reflector = new ClassReflector(new ComposerSourceLocator($classLoader, $astLocator));
$reflectionClass = $reflector->reflect('Foo\Bar\MyClass');
echo $reflectionClass->getShortName(); // MyClass
echo $reflectionClass->getName(); // Foo\Bar\MyClass
echo $reflectionClass->getNamespaceName(); // Foo\Bar
- 使用定义类的字符串加载
use Roave\BetterReflection\BetterReflection;
use Roave\BetterReflection\Reflector\ClassReflector;
use Roave\BetterReflection\SourceLocator\Type\StringSourceLocator;
$code = '<?php class Foo {};';
$astLocator = (new BetterReflection())->astLocator();
$reflector = new ClassReflector(new StringSourceLocator($code, $astLocator));
$reflectionClass = $reflector->reflect('Foo');
echo $reflectionClass->getShortName(); // Foo
- 通过指定PHP文件名来加载
use Roave\BetterReflection\BetterReflection;
use Roave\BetterReflection\Reflector\ClassReflector;
use Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator;
$astLocator = (new BetterReflection())->astLocator();
$reflector = new ClassReflector(new SingleFileSourceLocator('path/to/MyApp/MyClass.php', $astLocator));
$reflectionClass = $reflector->reflect('MyApp\MyClass');
echo $reflectionClass->getShortName(); // MyClass
echo $reflectionClass->getName(); // MyApp\MyClass
echo $reflectionClass->getNamespaceName(); // MyApp
这种方式虽然传的是一个文件,但是在SingleFileSourceLocator::createLocatedSource()
方法中会使用file_get_contents
转换成字符串。
protected function createLocatedSource(Identifier $identifier) : ?LocatedSource
{
return new LocatedSource(
file_get_contents($this->fileName),
$this->fileName
);
}
其他的几个locator就不多介绍了。最后说一下如何反射一个闭包:
$myClosure = function () {
echo "Hello world!\n";
};
$functionInfo = ReflectionFunction::createFromClosure($myClosure);
$functionInfo->isClosure(); // true
欢迎关注我的公众号:
