发现 Reflection 扩展的变化
Reflection 扩展 用于对对象、类、方法和函数等进行反省。ReflectionClass
和 ReflectionObject
分别生成类或对象实例的信息。ReflectionFunction
提供程序级函数的信息。此外,Reflection
扩展还有一组由刚才提到的主类产生的辅助类。这些辅助类包括由 ReflectionClass::getMethod()
生成的 ReflectionMethod
、由 ReflectionClass::getProperty()
生成的 ReflectionProperty
等。
你可能会问 谁会使用这个扩展?答案是:任何需要对外部类集进行分析的应用程序。这可能包括执行自动代码生成、测试或文档生成的软件。执行 hydration(从数组中填充对象)的类也可以从 Reflection
扩展中受益。
本书没有足够的篇幅介绍每一个 |
现在让我们来看看 Reflection
扩展的使用示例。
反射扩展的使用
现在,我们将展示一个代码示例,演示如何使用 Reflection
扩展来生成 docblock(docblock
是一种 PHP 注释,它使用一种特殊的语法来表示方法的目的、输入参数和返回值)。下面是实现这一点的步骤:
-
我们首先定义一个
__construct()
方法,该方法用于创建目标类的ReflectionClass
实例,如下所示:// /repo/src/Services/DocBlockChecker.php namespace Services; use ReflectionClass; class DocBlockChecker { public $target = ''; // class to check public $reflect = NULL; // ReflectionClass instance public function __construct(string $target) { $this->target = $target; $this->reflect = new ReflectionClass($target); }
-
然后,我们定义了一个
check()
方法,该方法可抓取所有类方法,并返回一个ReflectionMethod
实例数组,如下所示:public function check() { $methods = []; $list = $this->reflect->getMethods();
-
然后,我们在所有方法中循环,并使用
getDocComment()
检查docblock
是否已经存在,如下所示:foreach ($list as $refMeth) { $docBlock = $refMeth->getDocComment();
-
如果
docblock
不存在,我们就新建一个docblock
,然后调用getParameters()
,返回一个ReflectionParameter
实例数组,如下代码所示:if (!$docBlock) { $docBlock = "/**\n * " . $refMeth->getName() . "\n"; $params = $refMeth->getParameters();
-
如果有参数,我们会收集信息以便显示,具体如下:
if ($params) { foreach ($params as $refParm) { $type = $refParm->getType() ?? 'mixed'; $type = (string) $type; $name = $refParm->getName(); $default = ''; if (!$refParm->isVariadic() && $refParm->isOptional()) { $default=$refParm->getDefaultValue(); } if ($default === '') { $default = "(empty string)"; } $docBlock .= " * @param $type " . "\${$name} : $default\n"; } }
-
然后,我们设置返回类型,并将
docblock
赋值给$methods
数组,然后返回,如下所示:if ($refMeth->isConstructor()) $return = 'void'; else $return = $refMeth->getReturnType() ?? 'mixed'; $docBlock .= " * @return $return\n"; $docBlock .= " */\n"; } $methods[$refMeth->getName()] = $docBlock; } return $methods; } }
-
现在,新的
docblock
检查类已经完成,我们可以定义一个调用程序,如下面的代码片段所示。调用程序的目标是 /repo/src/Php7/Reflection/Test.php 类(此处未显示)。该类包含多个带参数和返回值的方法:// //repo/ch07/php7_reflection_usage.php $target = 'Php7\Reflection\Test'; require_once __DIR__ . '/../src/Server/Autoload/Loader.php'; use Server\Autoload\Loader; use Services\DocBlockChecker; |$autoload = new Loader(); $checker = new DocBlockChecker($target); var_dump($checker->check());
调用程序的输出如下所示:
root@php8_tips_php7 [ /repo/ch07 ]#
php php7_reflection_usage.php
/repo/ch07/php7_reflection_usage.php:10:
array(4) {
'__construct' => string(75)
"/**
* __construct
* @param PDO $pdo : (empty string)
* @return void
*/"
'fetchAll' => string(41)
"/**
* fetchAll
* @return Generator
*/"
'fetchByName' => string(80)
"/**
* fetchByName
* @param string $name : (empty string)
* @return array
*/"
'fetchLastId' => string(38)
"/**
* fetchLastId
* @return int
*/"
}
正如您所看到的,该类构成了潜在的自动文档或代码生成应用程序的基础。
现在让我们来看看 Reflection
扩展的改进。
了解反射扩展改进
此外,Reflection
扩展还做了一些改进,您可能有必要了解一下。请记住,尽管使用 Reflection
扩展的开发人员数量有限,但总有一天您可能会发现自己正在处理使用该扩展的代码。如果您在 PHP 8 升级后发现了奇怪的行为,本节所涉及的内容将为您的故障排除过程提供一个良好的开端。
反射类型修改
在 PHP 8 中,ReflectionType
类现在是抽象类。当您使用 ReflectionProperty::getType()
或 ReflectionFunction: :getReturnType()
方法时,您可能会注意到返回的是一个 ReflectionNamedType
实例。这一变化不会影响程序代码的正常运行,除非您依赖返回的 ReflectionType
实例。不过,ReflectionNamedType
扩展了 ReflectionType
,因此任何 instanceof
操作都不会受到影响。
值得注意的是,isBuiltIn()
方法已从 ReflectionType
移至 ReflectionNamedType
。同样,由于 ReflectionNamedType
扩展了 ReflectionType
,因此在您当前的代码中不会出现任何向后兼容的中断。
增强的 ReflectionParameter::*DefaultValue* 方法
在 PHP 早期版本中,与默认值有关的 ReflectionParameter
方法无法反映 PHP 内部函数。在 PHP 8 中,这种情况有所改变。以下 ReflectionParameter
方法现在也能从内部函数返回默认值信息:
-
getDefaultValue()
-
getDefaultValueConstantName()
-
isDefaultValueAvailable()
-
isDefaultValueConstant()
从列表中可以看出,这些方法的名称不言自明。现在我们将展示一个利用这些增强功能的代码示例。下面是实现这一功能的步骤:
-
首先,我们定义一个函数,该函数接受一个
ReflectionParameter
实例,并返回一个包含参数名称和默认值的数组,如下所示:// /repo/ch07/php8_reflection_parms_defaults.php $func = function (ReflectionParameter $parm) { $name = $parm->getName(); $opts = NULL; if ($parm->isDefaultValueAvailable()) $opts = $parm->getDefaultValue();
-
接下来,我们定义一个
switch()
语句来清理选项,如下所示:switch (TRUE) { case (is_array($opts)) : $tmp = ''; foreach ($opts as $key => $val) $tmp .= $key . ':' . $val . ','; $opts = substr($tmp, 0, -1); break; case (is_bool($opts)) : $opts = ($opts) ? 'TRUE' : 'FALSE'; break; case ($opts === '') : $opts = "''"; break; default : $opts = 'No Default'; } return [$name, $opts]; };
-
然后,我们确定要反映哪个函数,并提取其参数。在下面的示例中,我们反映
setcookie()
:$test = 'setcookie'; $ref = new ReflectionFunction($test); $parms = $ref->getParameters();
-
然后,我们在
ReflectionParameter
实例数组中循环,并产生输出,如下所示:$patt = "%18s : %s\n"; foreach ($parms as $obj) vprintf($patt, $func($obj));
下面是在 PHP 7 中运行的输出结果:
root@php8_tips_php7 [ /repo/ch07 ]#
php php8_reflection_parms_defaults.php
Reflecting on setcookie
Parameter : Default(s)
------------ : ------------
name : No Default
value : No Default
expires : No Default
path : No Default
domain : No Default
secure : No Default
httponly : No Default
结果总是 "无默认值",因为在 PHP 7 及更早版本中,Reflection
扩展无法读取 PHP 内部函数的默认值。而 PHP 8 的输出则准确得多,我们可以在这里看到:
root@php8_tips_php8 [ /repo/ch07 ]#
php php8_reflection_parms_defaults.php
Reflecting on setcookie
Parameter : Default(s)
------------ : ------------
name : No Default
value : ''
expires_or_options : No Default
path : ''
domain : ''
secure : FALSE
httponly : FALSE
从输出结果可以看出,PHP 8 中的 Reflection
扩展能够准确报告内部函数的默认值!
现在让我们看看 Reflection
扩展的其他变化。
其他反射扩展变更
在 PHP 8 之前的 PHP 版本中,ReflectionMethod::isConstructor()
和 ReflectionMethod::isDestructor()
无法反映接口中定义的魔法方法。在 PHP 8 中,这两个方法现在可以返回接口中定义的相应魔法方法的 TRUE
。
使用 ReflectionClass::getConstants()
或 ReflectionClass::getReflectionConstants()
方法时,新增了 $filter
参数。通过该参数,您可以按可见性级别过滤结果。因此,新参数可以接受以下任何一个新添加的预定义常量:
-
ReflectionClassConstant::IS_PUBLIC
-
ReflectionClassConstant::IS_PROTECTED
-
ReflectionClassConstant::IS_PRIVATE
现在你已经知道如何使用 Reflection
扩展,以及在 PHP 8 迁移后需要注意些什么。现在我们来看看在 PHP 8 中发生变化的其他扩展。