ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PHP 8.0的get_debug_type函数比gettype好在哪

PHP 8.0的get_debug_type函数比gettype好在哪 前言写日志和抛异常时很多人会顺手用gettype()报一下实际类型于是错误信息长这样参数类型错误期望 int实际得到 double第一次看到这句话的人通常会愣一下——double是什么类型PHP 的类型声明里只有float没有double。类似的还有integer和boolean它们都是gettype()的历史遗留命名和 PHP 现在写在代码里的类型关键字对不上。更麻烦的是遇到对象时gettype()只告诉你object不告诉你是哪个类遇到资源时只告诉你resource不告诉你是流还是别的什么。PHP 8.0 引入了get_debug_type()正是为解决这类信息量不够和命名对不上的问题。它的返回值与 PHP 的类型声明语法保持一致int、float、bool、null对象直接返回类名资源会附带资源类型。本文先给出两个函数的逐项返回值对照表再讲清它们背后的差异原理然后给出可直接运行的演示脚本和几个容易踩的坑。先确认一下版本get_debug_type()是PHP 8.0 引入的7.x 上并不存在本文给出的所有示例都以 PHP 8.0 为最低版本。一、返回值逐项对照这张表是全文的核心两列放在一起差异一目了然。传入的值gettype()返回get_debug_type()返回nullNULLnulltruebooleanbool1integerint1.5doublefloatabcstringstring[]arrayarraynew stdClass()objectstdClass匿名类的实例objectclassanonymous闭包objectClosure打开的文件句柄resourceresource (stream)已关闭的资源resource (closed)resource (closed)实现了 __toString 的对象object该对象的类名从表里能读出三条规律命名对齐了类型声明。int、float、bool、null这些写法就是你写在参数类型位置上的关键字。对象给出具体类名而不是笼统的object。闭包也是对象所以得到Closure。资源带上了子类型resource (stream)比resource有用得多。二、为什么会有这些差别2.1 gettype 返回的是内部 zval 类型名gettype()从 PHP 4 时代就存在它返回的是解释器内部对变量类型的称呼也就是 zvalZend ValuePHP 内部表示变量的结构的类型名。当年float在内部就叫double沿用 C 语言的命名bool叫booleanint叫integer。这些字符串被gettype()原样暴露出来中途虽然 PHP 在类型声明上改用了float、bool、int但为了兼容老代码gettype()的返回值一直没有改。结果就是一个尴尬的局面同一个概念在语言里有两套名字。类型声明用floatgettype()说double类型声明用boolgettype()说boolean。2.2 get_debug_type 是为读错误信息设计的get_debug_type()的目标很单一产生一段可以直接贴进错误信息里的类型描述。所以它做了三件事使用与类型声明一致的关键字对象返回真实类名资源尝试给出更细的类型。这样一来日志里那句话就变成了参数类型错误期望 int实际得到 float2.3 它顺手替掉了 is_object 加 get_class 的组合在 PHP 8.0 之前想得到同样的效果标准写法是这样?php // 需要 PHP 7.4 function describe($value): string { // 旧写法先判断是不是对象再决定调用哪个函数 return is_object($value) ? get_class($value) : gettype($value); } var_dump(describe(new stdClass())); // stdClass var_dump(describe(1.5)); // double命名还是对不上这段代码还必须带is_object()判断因为PHP 8.0 起get_class()传入非对象会抛 TypeError7.x 上是返回 false 并发出警告。少写这个判断一句日志代码就能把请求打挂?php // 需要 PHP 8.0 // ❌ 传非对象时抛 TypeError日志代码反而成了故障源 // echo get_class($input); // ✅ 一步到位任何类型的值都安全 echo get_debug_type($input ?? null);三、和 PHP 8.0 联合类型配合使用get_debug_type()与 PHP 8.0 的联合类型是天然一对前者产生的字符串正好可以写进后者的报错信息里让人一眼看懂允许什么、给了什么。?php // 需要 PHP 8.0 declare(strict_types1); /** * param int|string $id */ function findUser($id): string { if (!is_int($id) !is_string($id)) { throw new InvalidArgumentException( 参数 $id 期望 int 或 string实际得到 . get_debug_type($id) ); } return user- . $id; } try { findUser([1, 2]); } catch (InvalidArgumentException $e) { echo $e-getMessage(), \n; }输出参数 $id 期望 int 或 string实际得到 array如果把这段代码里的get_debug_type()换成gettype()array这个结果是巧合一致的但换成浮点、布尔或对象就没法看了——错误信息里会冒出double、boolean、object这些读者无法与代码对照的名字。四、什么时候不该用它get_debug_type()是为输出给人看设计的不是为程序判断设计的。它有几个明确的使用边界场景该用不该用拼错误信息、写日志get_debug_type()——判断是不是整数is_int()比较get_debug_type()的字符串判断是不是某个类instanceof运算符比较类名字符串区分两个匿名类get_class()各自返回不同的内部标识get_debug_type()都返回同一个固定串在 PHP 7.x 环境里用加 polyfill或退回旧写法直接调用函数不存在致命错误第三列里的第一条尤其要注意用字符串比较判类型会把类型判断和类型名字的措辞绑在一起一旦 PHP 调整了命名逻辑就静默失效。代码实战一份类型对照脚本把下面这段保存成type_report.php直接运行它会把上表的结论在你的环境里复现出来两列并排显示差异一眼可见。脚本里刻意包含了匿名类、闭包、已关闭的资源这几种容易被忽略的情况。?php // 需要 PHP 8.0 declare(strict_types1); $handle fopen(php://memory, r); // 打开一个资源 $anon new class { public string $tag anon; }; $closure static fn (int $x): int $x * 2; $samples [ null null, bool true, int 42, float 3.14, string hello, array [1, 2, 3], 对象 new stdClass(), 匿名类 $anon, 闭包 $closure, 资源 $handle, 空数组 [], ]; printf(%-10s | %-18s | %s\n, 输入, gettype(), get_debug_type()); echo str_repeat(-, 56), \n; foreach ($samples as $label $value) { printf( %-10s | %-18s | %s\n, $label, gettype($value), get_debug_type($value) ); } // 关闭资源后再看一次两者都会给出 resource (closed) fclose($handle); printf(%-10s | %-18s | %s\n, 已关闭资源, gettype($handle), get_debug_type($handle)); // 用一行输出验证错误信息里应该出现的是 int / float / bool而不是 integer / double / boolean foreach ([1, 1.5, true, null] as $v) { printf( 期望 int实际得到 %s旧写法会说 %s\n, get_debug_type($v), gettype($v) ); }典型的输出形态如下具体到某些行会因环境而异重点看两列的差异输入 | gettype() | get_debug_type() -------------------------------------------------------- null | NULL | null bool | boolean | bool int | integer | int float | double | float string | string | string array | array | array 对象 | object | stdClass 匿名类 | object | classanonymous 闭包 | object | Closure 资源 | resource | resource (stream) 空数组 | array | array 已关闭资源 | resource (closed) | resource (closed)跑过一遍之后gettype()那列里integer、double、boolean与代码里int、float、bool对不上的问题就非常直观了。注意匿名类那一行两个函数给出的是固定标识并不能区分两个不同的匿名类——这正是第五节第一条坑点要讲的内容。常见坑点1. 用类型名字符串做判断❌if (get_debug_type($v) int) { ... }✅ 判断类型用is_int()、is_string()判断对象类型用instanceof。字符串比较会把业务逻辑和类型名字的写法绑死2. 直接把 gettype 全局替换成 get_debug_type❌ 项目里用查找替换把所有gettype(改成get_debug_type(就上线✅ 如果原有代码里有if (gettype($x) double)这类比较替换后判断永远不成立而且是静默失效。替换前先搜一遍有没有对返回值做比较的地方3. 忘了 get_class 在 PHP 8.0 的行为变化❌echo get_class($maybeObject);参数不是对象时PHP 8.0 起直接抛 TypeError✅ 用get_debug_type()它对任何值都安全确实需要类名时先is_object()判断4. 期望区分不同的匿名类❌ 用get_debug_type()的输出当缓存键或做分支判断结果所有匿名类实例都落到同一个分支✅ 需要区分匿名类时用get_class()它会返回带位置信息的内部标识更好的做法是避免用匿名类做这类区分5. 在 PHP 7.x 上直接调用❌ 代码里写了get_debug_type()部署到 7.x 环境报调用未定义函数的致命错误✅ 8.0 以下的环境可以引入 polyfill 包symfony/polyfill-php80提供了这个函数的低版本实现或者自己封装一层兼容函数6. 拿它当类型校验用❌if (get_debug_type($id) ! int) { throw ... }作为参数校验手段✅ 它只描述当前是什么不做校验也不做转换。参数校验请用显式的判断或类型声明让 PHP 在运行时拦截7. 误以为它会把子类报成父类❌ 认为get_debug_type()返回的是声明类型✅ 它返回的是运行时实际类型即对象真正的类名最具体的那个。声明类型的信息要靠反射或类型声明本身来拿8. 在用户可见的页面上直接输出❌ 把类型名和值一起打到前端页面上暴露内部类名与结构✅ 它适合写进日志和异常消息对外返回的错误信息应当做一次收敛只给出必要的提示总结对比项gettype()get_debug_type()引入版本PHP 4 起就有PHP 8.0返回值风格内部 zval 命名integer、double、boolean与类型声明一致int、float、bool对象一律object具体类名如stdClass、Closure资源一律resource带子类型如resource (stream)nullNULLnull适用场景需要兼容老代码的场合拼错误信息、写日志不能用来做反正都不该用它做类型判断类型判断、匿名类的区分get_debug_type()的价值可以概括成一句话它让日志里的类型名和代码里的类型声明说同一种语言。gettype()不是错的它只是太老了返回的是解释器内部沿用了二十多年的称呼。写错误信息和日志时换成get_debug_type()读者不用再在脑子里做一次double到float的翻译而真正的类型判断永远应该交给is_int()、instanceof这类运算符去做。
返回列表