【PHP小课堂】一起学习PHP中的反射(三)
一起学习PHP中的反射(三)
在反射相关的对象操作中,我们已经学习了属性、扩展相关的知识,今天我们要学习的是整个 ReflectionClass 对象中剩余的一些方法以及对于反射的类中的方法相关的操作。
反射类的命名空间信息
命名空间对于现代化的 PHP 开发来说是非常重要的一个能力。在反射相关的操作,针对命名空间相关的方法函数也是必备的内容之一。在此之前,我们还是要先改造我们的测试类。
namespace Test;
interface iA{
}
trait tA{
}
trait tB{
function traitBInfo(){
echo 'This is trait B. function is traitBInfo.';
}
}
class A{
/**
* This is ONE DOC.
*/
const ONE = 'number 1';
// This is TWO DOC.
private const TWO = 'number 2';
/**
* This is a DOC.
*/
private $a = '1';
// This is b DOC.
protected $b = '2';
/* This is c DOC. */
public $c = '3';
public $d;
static $e = '5';
static $f;
public function __construct($a = 1, $b = 2){
echo 'This is Class A, function Constructor.', PHP_EOL;
$this->argsA = $a;
$this->argsB = $b;
}
public function __destruct(){
}
private function testA(){
echo 'This is class A, function testA', PHP_EOL;
}
protected function testB(){
echo 'This is class A, function testB', PHP_EOL;
}
public function testC(){
echo 'This is class A, function testC', PHP_EOL;
}
public function testD($a, $b){
echo $a + $b, PHP_EOL;
}
static function testE($a, $b){
echo $a * $b, PHP_EOL;
}
}
/**
* This is Class B ,extends from A.
*
* @author zyblog
*/
class B extends A {
use tA, tB { tB::traitBInfo as info;}
public function testC(){
echo 'This is Class B, function testC', PHP_EOL;
}
}
abstract class C implements iA{
abstract function testF();
final function testG(){}
private function __clone(){}
}
在测试类中,我们增加了命名空间以及其它一些今天要学习到的内容。
$objA = new \ReflectionClass('Test\\A');
$objB = new \ReflectionClass('Test\\B');
$objC = new \ReflectionClass('Test\\C');
var_dump($objA->getName()); // string(6) "Test\A"
var_dump($objA->getShortName()); // string(1) "A"
var_dump($objA->getNamespaceName()); // string(4) "Test"
var_dump($objA->inNamespace()); // bool(true)
对于命名空间来说,我们需要通过原来学习过的 getName() 方法来进行比对。从代码中可以看出,如果直接使用 getName() 方法,返回的是带命名空间的反射类名称。而 getShortName() 返回的则是不带命名空间的原始的类名。getNamespaceName() 很明显就是返回的单独的命名空间名称。从这里可以看出,getName() 方法实际上是 getNamespaceName() 方法和 getShortName() 方法的汇总组合。
isNamespace() 用于判断当前这个反射类是否在命名空间中。
反射类的特性信息
对于特性 Trait 来说,也是我们现在会经常使用的一个功能。在反射相关的功能中也是有相关的获取特性信息的方法的。
var_dump($objB->getTraits());
// array(2) {
// ["Test\tA"]=>
// object(ReflectionClass)#3 (1) {
// ["name"]=>
// string(7) "Test\tA"
// }
// ["Test\tB"]=>
// object(ReflectionClass)#4 (1) {
// ["name"]=>
// string(7) "Test\tB"
// }
// }
var_dump($objB->getTraitAliases());
// array(1) {
// ["info"]=>
// string(19) "Test\tB::traitBInfo"
// }
var_dump($objB->getTraitNames());
// array(2) {
// [0]=>
// string(7) "Test\tA"
// [1]=>
// string(7) "Test\tB"
// }
getTraits() 用于获得这个反射类中所有使用的特性信息列表。getTraitNames() 则是以字符串的方式返回特性名称的列表。getTraitAliases() 返回的是我们定义了特性别名的特性信息列表。可以看出,没有设置别名的特性不会在 getTraitAliases() 中返回。
反射类中的设置及判断相关的方法
另外在反射类中,还有许多小的方法用于判断类的一些状态以及可以设置反射类中的一些功能。在这里我们就不一一解释了。
// 设置静态属性值
$objA->setStaticPropertyValue('f', 'fff');
echo A::$f, PHP_EOL; // fff
// 是否实现了接口
var_dump($objA->implementsInterface('Test\\iA')); // bool(false)
var_dump($objC->implementsInterface('Test\\iA')); // bool(true)
// 是否是抽象类
var_dump($objA->isAbstract()); // bool(false)
var_dump($objC->isAbstract()); // bool(true)
// 是否是特性
var_dump((new \ReflectionClass('Test\\tA'))->isTrait()); // bool(true)
// 是否是匿名类
var_dump($objA->isAnonymous()); // bool(false)
$class = new class {};
var_dump((new \ReflectionClass($class))->isAnonymous()); // bool(true)
// 是否可以调用 __clone
var_dump($objA->isCloneable()); // bool(true)
var_dump($objC->isCloneable()); // bool(false)
// 是否是不可修改类
var_dump($objA->isFinal()); // bool(false)
final class finalClass{}
var_dump((new \ReflectionClass('Test\\finalClass'))->isFinal()); // bool(true)
// 判断反射的类是否是指定对象
$a = new A();
$b = new B;
var_dump($objA->isInstance($a)); // bool(true)
var_dump($objA->isInstance($b)); // bool(true)
var_dump($objA->isInstance(new \stdClass)); // bool(false)
// 判断是否可以实例化
var_dump($objA->isInstantiable()); // bool(true)
var_dump($objC->isInstantiable()); // bool(false)
// 判断是否是接口
var_dump((new \ReflectionClass('Test\\iA'))->isInterface()); // bool(true)
// 判断是否是原生或者扩展提供的类
var_dump($objA->isInternal()); // bool(false)
var_dump((new \ReflectionClass('PDO'))->isInternal()); // bool(true)
// 判断是否是用户定义的类
var_dump($objA->isUserDefined()); // bool(true)
var_dump((new \ReflectionClass('PDO'))->isUserDefined()); // bool(false)
class IteratorClass implements \Iterator{
public function key() { }
public function current() { }
function next() { }
function valid() { }
function rewind() { }
}
// 判断类是否可以遍历 foreach
var_dump($objA->isIterable()); // bool(false)
var_dump((new \ReflectionClass('Test\\IteratorClass'))->isIterable()); // bool(true)
// 判断类是否是迭代类
var_dump($objA->isIterateable()); // bool(false)
var_dump((new \ReflectionClass('Test\\IteratorClass'))->isIterateable()); // bool(true)
// 判断反射的类是否是指定类的子类
var_dump($objB->isSubclassOf('Test\\A')); // bool(true)
var_dump($objB->isSubclassOf('Test\\B')); // bool(false)
在测试代码中都有对应的注释,大家可以一个一个地看一下它们的具体作用。其实大部分方法从名字就可以大概知道它的功能。
实例化一个反射类
实例化一个反射类是什么意思呢?其实就是我们反射了一个类得到了 ReflectionClass 对象之后,还可以通过这个对象再反过去实例化一个这个类的对象出来。
$objA1 = $objA->newInstance(11, 22);
var_dump($objA1);
// object(Test\A)#8 (6) {
// ["a":"Test\A":private]=>
// string(1) "1"
// ["b":protected]=>
// string(1) "2"
// ["c"]=>
// string(1) "3"
// ["d"]=>
// NULL
// ["argsA"]=>
// int(11)
// ["argsB"]=>
// int(22)
// }
$objA2 = $objA->newInstanceArgs([111, 222]);
var_dump($objA2);
// object(Test\A)#8 (6) {
// ["a":"Test\A":private]=>
// string(1) "1"
// ["b":protected]=>
// string(1) "2"
// ["c"]=>
// string(1) "3"
// ["d"]=>
// NULL
// ["argsA"]=>
// int(111)
// ["argsB"]=>
// int(222)
// }
有两个实例化的方法,不过它们的功能是一样的,只是参数不一样,newInstance() 方法会将构造参数一个一个地传递进去,而 newInstanceArgs() 则是可以用一个数组来传递实例化对象的构造参数。这个其实非常像 call_user_func() 和 call_user_func_array() ,还记得这两个家伙吗?不记得的小伙伴可以传送到 PHP中的函数相关处理方法学习https://mp.weixin.qq.com/s/Qx5dJRuyPDRmpPxs3gKiDg 这里复习一下。下面我们要学习到的反射方法类中也有类似的方法函数哦。
另外,我们也可以绕过构造函数来实例化一个反射类。
$objA3 = $objA->newInstanceWithoutConstructor([111, 222]);
var_dump($objA3);
// object(Test\A)#9 (4) {
// ["a":"Test\A":private]=>
// string(1) "1"
// ["b":protected]=>
// string(1) "2"
// ["c"]=>
// string(1) "3"
// ["d"]=>
// NULL
// }
当然,对于抽象类以及将构造函数设置为私有的类来说,是无法实例化的。这个就和普通的对象实例化一样,反射也是遵循最基本的规则的。
// $objC1 = $objC->newInstance(11, 22); // atal error: Uncaught Error: Cannot instantiate abstract class Test\C
反射类中的方法操作
就和之前的文章中讲过的属性以及扩展相关的操作一样,反射类中方法的操作也是类似的。
var_dump($objA->getMethods());
// array(4) {
// [0]=>
// object(ReflectionMethod)#5 (2) {
// ["name"]=>
// string(11) "__construct"
// ["class"]=>
// string(6) "Test\A"
// }
// [1]=>
// object(ReflectionMethod)#4 (2) {
// ["name"]=>
// string(5) "testA"
// ["class"]=>
// string(6) "Test\A"
// }
// [2]=>
// object(ReflectionMethod)#6 (2) {
// ["name"]=>
// string(5) "testB"
// ["class"]=>
// string(6) "Test\A"
// }
// [3]=>
// object(ReflectionMethod)#7 (2) {
// ["name"]=>
// string(5) "testC"
// ["class"]=>
// string(6) "Test\A"
// }
// }
var_dump($objA->getMethods(\ReflectionMethod::IS_PUBLIC));
// array(2) {
// [0]=>
// object(ReflectionMethod)#7 (2) {
// ["name"]=>
// string(11) "__construct"
// ["class"]=>
// string(6) "Test\A"
// }
// [1]=>
// object(ReflectionMethod)#6 (2) {
// ["name"]=>
// string(5) "testC"
// ["class"]=>
// string(6) "Test\A"
// }
// }
var_dump($objA->hasMethod('testA')); // bool(true)
var_dump($objA->getMethod('testA'));
// object(ReflectionMethod)#6 (2) {
// ["name"]=>
// string(5) "testA"
// ["class"]=>
// string(6) "Test\A"
// }
var_dump($objA->hasMethod('testAA')); // bool(false)
// var_dump($objA->getMethod('testAA')); // Fatal error: Uncaught ReflectionException: Method testAA does not exist
getMethods() 是获取反射类中所有方法的列表,它返回的列表对象是 ReflectionMethod 对象,也是我们后面马上要讲的内容。这个方法还可以有一个参数,用于指定返回某种访问修饰符的方法列表,比如我们在测试代码中返回 ReflectionMethod::IS_PUBLIC 这个参数的方法内容实际上就是返回整个反射类中所有的公共的方法内容信息。
hasMethod() 用于判断指定的方法是否存在,getMethod() 用于返回指定的单个 ReflectionMethod 对象。这两个就不多解释了,马上进入 ReflectionMethod 的学习。
ReflectionMethod
$objMethodA = $objA->getMethod('testA');
$objMethodB = $objA->getMethod('testB');
$objMethodC = $objA->getMethod('testC');
首先还是获取 A 类的几个方法的 ReflectionMethod 对象。当然,ReflectionMethod 也可以直接实例化,后面我们会看到直接 new 这个 ReflectionMethod 对象的应用,因为我们还要测试 B 类和 C 类中的一些方法。
$fun = $objMethodA->getClosure(new A());
$fun(); // This is class A, function testA
getClosure() 从名称就可以看出来,这个是将某个对象中的方法当成一个匿名回调函数返回回来。注意,这里我们是将 testA() 方法返回回来并且直接调用了,它可是一个 private 的方法哦!也就是说,这个 getClosure() 方法是可以用来调用对象中的非公共方法的。
var_dump($objMethodB->getDeclaringClass());
// object(ReflectionClass)#9 (1) {
// ["name"]=>
// string(6) "Test\A"
// }
用于获取 ReflectionMethod 对象所对应的那个类,返回的也是反射的 ReflectionClass 对象。
var_dump($objMethodA->getModifiers()); // int(1024)
var_dump($objMethodB->getModifiers()); // int(512)
var_dump($objMethodC->getModifiers()); // int(256)
var_dump($objMethodA->isPrivate()); // bool(true)
var_dump($objMethodA->isProtected()); // bool(false)
var_dump($objMethodA->isPublic()); // bool(false)
这些方法不用多解释了吧,返回和判断方法的访问修饰符的,和 ReflectionProperty 对象中的那些方法功能是一样的。
// var_dump($objMethodC->getPrototype()); // Fatal error: Uncaught ReflectionException: Method Test\A::testA does not have a prototype
var_dump((new \ReflectionMethod('Test\\B', 'testC'))->getPrototype());
// object(ReflectionMethod)#10 (2) {
// ["name"]=>
// string(5) "testC"
// ["class"]=>
// string(6) "Test\A"
// }
这个 getPrototype() 方法是什么意思呢?通过上面的测试代码大家能看出来端倪吗?Prototype 这个单词相信做过前端的同学都不会陌生,它是原型的意思。在 PHP 中,这个原型的意思就是这个方法是不是重写的父类的方法。由于 A 类没有父类,所以 A 类中的方法使用 getPrototype() 都会直接报错。而 B 类继承了 A 类,并且重写了一个 testC() 方法,所以我们可以获取到 B 类中 testC() 方法的原型信息。在返回的信息中就可以看出,这个方法的原型是 A 类中的 testC() 方法。
var_dump((new \ReflectionMethod('Test\\C', 'testF'))->isAbstract()); // bool(true)
var_dump((new \ReflectionMethod('Test\\C', 'testG'))->isFinal()); // bool(true)
var_dump((new \ReflectionMethod('Test\\A', '__construct'))->isConstructor()); // bool(true)
var_dump((new \ReflectionMethod('Test\\A', '__destruct'))->isDestructor()); // bool(true)
var_dump($objMethodA->isStatic()); // bool(false)
var_dump((new \ReflectionMethod('Test\\A', 'testE'))->isStatic()); // bool(true)
这几个判断相关的就放学了从名称也可以看出作用了吧,就不多做解释了。下面我们直接来看一下,如何直接通过反射调用方法。
(new \ReflectionMethod('Test\\A', 'testD'))->invoke(new A(), 2,3); // 5
(new \ReflectionMethod('Test\\A', 'testE'))->invoke(null, 2,3); // 6
(new \ReflectionMethod('Test\\A', 'testD'))->invokeArgs(new A(), [2,3]); // 5
(new \ReflectionMethod('Test\\A', 'testE'))->invokeArgs(null, [2,3]); // 6
invoke() 和 invokeArgs() 就是直接调用指定反射方法的功能函数。它们是不是和上面讲过的实例化反射类非常像,也是两个方法只是传递方法参数的方式不一样。与实例化对象不同的是,这里我们需要第一个参数是一个实例化对象,当然,这个对象也可以是一个 null 值。如果是 null 值的话,调用的是这个 ReflectionMethod 所对应的反射类的静态方法。
当然,对于私有的方法是不能直接调用的,这时我们有另外一个方法可以让这个私有的方法可以被调用。
// $objMethodA->invoke(new A()); // Fatal error: Uncaught ReflectionException: Trying to invoke private method Test\A::testA() from scope ReflectionMethod
$objMethodA->setAccessible(true);
$objMethodA->invoke(new A()); // This is class A, function testC
在 ReflectionProperty 中也是有这个 setAccessible() 方法可以让我们调用非公开的属性的,这一点和 ReflectionMethod 中的这个功能是完全一样的。
总结
至此,ReflectionClass 这个反射对象中相关的内容我们就全部学习完了。中间,我们还穿插着学习了 ReflectionClassConstant、ReflectionProperty 、 ReflectionExtension 以及 ReflectionMethod 这四个针对常量、属性、扩展以及方法的反射对象,这四个反射对象都是在 ReflectionClass 对象中有方法函数可以直接获取的。后面我们还有一篇文章继续学习反射中其它的内容,反射可是个大功能,千万不要半途而废哦!
测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/2021/05/source/3.%E4%B8%80%E8%B5%B7%E5%AD%A6%E4%B9%A0PHP%E4%B8%AD%E7%9A%84%E5%8F%8D%E5%B0%84%EF%BC%88%E4%B8%89%EF%BC%89.php
参考文档:
https://www.php.net/manual/zh/book.reflection.php