标题:【PHP小课堂】一起学习PHP中的反射(四)
一起学习PHP中的反射(四)
今天我们来学习的是反射最后的内容了,其实也就是一些除了类之外反射相关的一些操作,包括反射普通方法函数、获得函数的参数、生成器反射、对象反射之类的内容。话不多说,我们一个一个的来看一下。当然,在这之前,我们还是要准备一下我们的测试代码。
/**
* This is testA
*/
function testA(int $a = PHP_INT_MIN, $b){
echo 'Function A: ', $a + $b, PHP_EOL;
}
function testB(){
yield $i++;
return $i;
}
function testC(...$a) : int{
return 1;
}
function testD(){
return function(){
};
}
function &testE(){
}
class A {
function testClassA(Exception $a = null){
return function(){
};
}
}
反射函数 ReflectionFunction
之前我们已经学习过,反射一个类中的方法使用的是 ReflectionMethod 这个对象。然而对于普通的,不在类中的函数来说,我们也可以直接对它进行反射。
$refFuncA = new ReflectionFunction('testA');
$funcA = $refFuncA->getClosure();
$funcA(1, 2); // Function A: 3
$refFuncA->invoke(1, 2); // Function A: 3
$refFuncA->invokeArgs([1, 2]); // Function A: 3
从代码中可以看出,ReflectionFunction 用于普通函数的反射,它只需要一个参数也就是方法名就可以了,不需要类名之类的参数。同样,它也有 getClosure() 、 invoke() 、 invokeArgs() 这些方法。
// php.ini disable_functions
var_dump($refFuncA->isDisabled()); // bool(false)
var_dump((new ReflectionFunction('dl'))->isDisabled()); // bool(true)
isDisabled() 这个方法是 ReflectionMethod 中没有的,这是 ReflectionFunction 中所特有的一个方法。它的作用是判断函数是否可用,其实就是判断这个函数是否在 php.ini 中被禁用了,也就是在 php.ini 中的 disable_functions 中是否添加了这个函数。我们在本机的测试环境中增加了 dl 方法到 disable_functions 中,在这里使用 isDisabled() 就会返回 true 。
ReflectionFunctionAbstract
除了本身自带的方法之外,ReflectionFunction 和 ReflectionMethod 其实都是继承了 ReflectionFunctionAbstract 这个抽象类。对于这个类中的方法,上篇文章中并没有详细的说明,不过本身这个类中的很多方法也和 ReflectionClass 中的很多方法是类似的,比如:
var_dump($refFuncA->getStartLine()); // int(3)
var_dump($refFuncA->getEndLine()); // int(5)
var_dump($refFuncA->getDocComment());
// string(24) "/**
// * This is testA
// */"
具体的详细的方法列表大家可以参考相关的文档,我们就看看一些比较有意思的方法,而且是 ReflectionClass 中没有的。
var_dump($refFuncA->hasReturnType()); // bool(false)
var_dump((new ReflectionFunction('testB'))->hasReturnType()); // bool(false)
var_dump((new ReflectionFunction('testC'))->hasReturnType()); // bool(true)
hasReturnType() 方法用于判断反射的方法是否有返回值类型。对于 testC() 这个测试方法来说,我们指定了返回值必须是一个 int 类型,所以这个 hasReturnType() 就可以返回一个 true 。
var_dump($refFuncA->isVariadic()); // bool(false)
var_dump((new ReflectionFunction('testC'))->isVariadic()); // bool(true)
var_dump($refFuncA->isGenerator()); // bool(false)
var_dump((new ReflectionFunction('testB'))->isGenerator()); // bool(true)
isVariadic() 用于判断函数的参数是否可变长度的参数。isGenerator() 则用于判断函数是否为生成器函数。这两个也不用多做解释了,注意查看函数原型之间的区别。
var_dump((new ReflectionFunction((new A)->testClassA()))->getClosureScopeClass());
// object(ReflectionClass)#6 (1) {
// ["name"]=>
// string(1) "A"
// }
var_dump((new ReflectionFunction((new A)->testClassA()))->getClosureThis());
// object(A)#3 (0) {
// }
var_dump((new ReflectionFunction('testD'))->getClosureThis()); // NULL
getClosureScopeClass() 和 getClosureThis() 用于返回回调函数的作用域和回调函数中的 this 指向对象。一提到这两个东西,还是要用对象中的例子更形象些。从测试代码中可以看出,getClosureScopeClass() 返回的是作用域对象的信息,返回的是一个 ReflectionClass 反射出来的 A 类的信息。而 getClosureThis 直接返回的就是那个 A 类,也就是这个测试的回调函数真实的指向对象。这里的内容一定要理解这个 this 的指向问题,相信稍微接触过一点前端开发的同学都不会陌生,当然,我们 PHP 中的这个 this 指向问题也没有那么复杂。
当然,如果只是外部的普通全局函数的话,是没有这些作用域对象和 this 指向问题的,所以返回的都是 NULL 。
var_dump($refFuncA->returnsReference()); // bool(false)
var_dump((new ReflectionFunction('testE'))->returnsReference()); // bool(true)
returnsReference() 从名字就可以看出,它用于判断函数是否为引用型函数。注意看 testE() 函数的定义,我们在函数名前加了一个 & 符号,表明这是一个引用函数。具体的引用函数相关的知识大家可以自行查阅相关的资料。
var_dump($refFuncA->getParameters());
// array(2) {
// [0]=>
// object(ReflectionParameter)#3 (1) {
// ["name"]=>
// string(1) "a"
// }
// [1]=>
// object(ReflectionParameter)#4 (1) {
// ["name"]=>
// string(1) "b"
// }
// }
var_dump((new ReflectionFunction('testC'))->getParameters());
// array(1) {
// [0]=>
// object(ReflectionParameter)#3 (1) {
// ["name"]=>
// string(1) "a"
// }
// }
getParameters() 方法用于返回反射的函数中的参数信息。这里我们能看到,返回的列表中的数据是 ReflectionParameter 类型的对象,也是我们接下来就要学习的内容。注意,没有获取一个函数中单个参数的方法哦。如果需要获取某一个参数的话,我们需要直接实例化一个 ReflectionParameter 对象。
反射方法函数的参数 ReflectionParameter
$refParA = new ReflectionParameter('testA', 'a');
$refParClassA = new ReflectionParameter(['A', 'testClassA'], 'a');
通过直接实例化 ReflectionParameter 类,就可以获得一个函数方法中的参数信息。在这里我们两个测试对象分别是从普通函数和类中的方法中获得参数信息。获得类中的方法的参数直接给第一个参数传递一个数组就可以了,数组的第一个元素是类名,第二个元素是方法名。
var_dump($refParClassA->getClass());
// object(ReflectionClass)#5 (1) {
// ["name"]=>
// string(9) "Exception"
// }
var_dump($refParClassA->getDeclaringClass());
// object(ReflectionClass)#5 (1) {
// ["name"]=>
// string(1) "A"
// }
var_dump($refParClassA->getDeclaringFunction());
// object(ReflectionMethod)#5 (2) {
// ["name"]=>
// string(10) "testClassA"
// ["class"]=>
// string(1) "A"
// }
var_dump($refParA->getDeclaringFunction());
// object(ReflectionFunction)#5 (1) {
// ["name"]=>
// string(5) "testA"
// }
getClass() 获取到的是这个参数的类信息,注意它和 getDeclaringClass() 的区别。getDeclaringClass() 获取的是这个参数所属的方法所在的类的信息。一开始我也把它们两个给搞混了。getDeclaringFunction() 方法获取的是这个类所属的方法函数的信息,会根据当前这个方法函数在类中或是不在类中而返回对应的 ReflectionMethod 或 ReflectionFunction 信息。
var_dump($refParA->getDefaultValue()); // int(-9223372036854775808)
var_dump($refParA->getDefaultValueConstantName()); // string(11) "PHP_INT_MIN"
var_dump($refParA->getPosition()); // int(0)
var_dump((new ReflectionParameter('testA', 'b'))->getPosition()); // int(1)
getDefaultValue() 返回参数的默认值,getDefaultValueConstantName() 返回的则是默认值的常量名。getPosition() 返回的是参数的位置,其实就是我们第几个定义的这个函数。
var_dump($refParA->isArray()); // bool(false)
var_dump($refParA->isCallable()); // bool(false)
var_dump($refParA->isDefaultValueAvailable()); // bool(true)
var_dump($refParA->isDefaultValueConstant()); // bool(true)
var_dump($refParA->isOptional()); // bool(false)
var_dump($refParA->isPassedByReference()); // bool(false)
var_dump($refParA->isVariadic()); // bool(false)
这一堆判断方法也不用多解释了,不清楚的同学可以查阅一下相关的文档。接下来我们就继续看一下参数的类型信息。
ReflectionNamedType
var_dump($refParA->getType());
// object(ReflectionNamedType)#5 (0) {
// }
var_dump($refParA->hasType()); // bool(true)
var_dump((new ReflectionParameter('testA', 'b'))->getType()); // NULL
var_dump((new ReflectionParameter('testA', 'b'))->hasType()); // bool(false)
通过 getType() 可以获得一个参数的 ReflectionNamedType 对象信息。当然,前提是我们为这个参数指定了参数类型。在 testA() 方法中,a 参数指定了 int 类型,所以它可以返回一个 ReflectionNamedType 对象,而且 hasType() 也会返回 true 。而 b 参数则是没有指定参数类型的,所以这里 getType() 返回的是 NULL 并且 hasType() 也返回 false 。
var_dump($refParA->getType()->allowsNull()); // bool(false)
var_dump($refParClassA->getType()->allowsNull()); // bool(true)
ReflectionNamedType 对象中的方法比较简单,只有一个 allowsNull() 方法,就是判断这个参数类型是是否可以为 NULL 。
反射一个生成器对象 ReflectionGenerator
还记得生成器是个什么东西吗?不记得的小伙伴可以去 学习PHP生成器的使用https://mp.weixin.qq.com/s/92b626NWJSFjeQ0ad7tY7Q 这里看下哦,或者在公众号回复这个文章的标题也可以看到这篇文章。通过反射,我们也可以操作一个生成器函数,是不是很高大上。
$genRef = new ReflectionGenerator(testB());
var_dump($genRef->getExecutingFile()); // string(105) "/Users/zhangyue/MyDoc/博客文章/dev-blog/php/2021/05/source/4.一起学习PHP中的反射(四).php"
var_dump($genRef->getExecutingGenerator());
// object(Generator)#4 (0) {
// }
var_dump($genRef->getExecutingLine()); // int(11)
var_dump($genRef->getFunction());
// object(ReflectionFunction)#7 (1) {
// ["name"]=>
// string(5) "testB"
// }
var_dump($genRef->getThis()); // NULL
直接将一个生成器函数放到 ReflectionGenerator 对象的实例化参数中,就可以获得一个 ReflectionGenerator 对象。
getExecutingFile() 用于获得这个生成器所在的代码文件,getExecutingGenerator() 获得的是这个生成器的原始函数信息,getExecutingLine() 获取的是这个生成器的执行代码行数,也就是 yield 关键字调用的行数。getFunction() 返回的是生成器函数的 ReflectionFunction 反射对象。getThis() 返回的就是这个函数的 this 指向,我们的测试函数不在任何类中,所以这个方法返回的就是一个 NULL 。
当然,生成器的反射还有更好玩的东西。
function testBB(){
yield from testB();
}
$b = testBB();
$b->valid();
var_dump((new ReflectionGenerator($b))->getTrace());
// array(1) {
// [0]=>
// array(4) {
// ["file"]=>
// string(105) "/Users/zhangyue/MyDoc/博客文章/dev-blog/php/2021/05/source/4.一起学习PHP中的反射(四).php"
// ["line"]=>
// int(181)
// ["function"]=>
// string(5) "testB"
// ["args"]=>
// array(0) {
// }
// }
// }
getTrace() 方法可以返回生成器函数的调用栈信息,大家可以自己多套几层来测试一下,结果非常详细哦!
反射对象 ReflectionObject
之前我们重点学习的 ReflectionClass 是针对类的反射,其实,我们也可以直接反射一个已经实例化的对象并获得它的反射信息。
$a = new A();
$objRef = new ReflectionObject($a);
$a->f = 'aaa';
var_dump($objRef->getProperties());
// array(1) {
// [0]=>
// object(ReflectionProperty)#11 (2) {
// ["name"]=>
// string(1) "f"
// ["class"]=>
// string(1) "A"
// }
// }
var_dump((new ReflectionClass('A'))->getProperties());
// array(0) {
// }
ReflectionObject 的方法和 ReflectionClass 中的方法是没有什么区别的,它们继承和实现的接口都是一样的,唯一不同的,它是针对对象的反射,所以我们动态定义的属性之类的内容也是可以马上就反射出来的。
反射异常处理 ReflectionException
最后,我们再来看一下专门针对反射相关操作的异常处理类 ReflectionException 。整个反射功能都是以面向对向的形式提供的,所以它的错误处理当然也全部都是以异常进行抛出的。我们在做反射相关的操作时,如果有需要,可以使用这个指定的异常处理类来进行相关的异常处理。
try{
new ReflectionClass('oooo');
}catch(ReflectionException $e){
var_dump($e->getMessage());
}
// string(25) "Class oooo does not exist"
总结
总算完成了,又是一个大的模块结束了。反射在现代化的框架开发中非常常用,也非常好用。它的许多功能特性都可以帮助我们在动态的代码执行过程中进行类的识别、分析、修改、调用的,不仅在 PHP 中,在任何语言中都会有这个强大的反射功能的存在,大家可千万不要忽视哦,完全可以作为我们向更高级别迈进的一个重要台阶。
测试代码:
参考文档: