小白学编程

简单学习PHP中的反射

2019-05-06  本文已影响2人  正义的程序员

和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_dumpprint_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对象的方式有几种,

  1. 可以从ReflectionClass::getMethods获得ReflectionMethod对象的数组
  2. 要得到特定的类方法,可以使用ReflectionClass::getMethod(),把函数名作为参数传过去
  3. 直接使用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

why is better

现在我们来简单学习下,这个包如何使用。

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);

}

获取ReflectionMethodReflectionParameterReflectionProperty也是一样一样的:


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)]

如何使用这这些Locator

  1. 使用composerautoload加载

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

  1. 使用定义类的字符串加载

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

  1. 通过指定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

欢迎关注我的公众号:


正义的程序猿
上一篇 下一篇

猜你喜欢

热点阅读