资源变量是zend内核中指向外部句柄(如file*、socket)的整数id,操作的是“门牌号”而非真实数据;其生命周期由内核资源表管理,需显式释放或依赖脚本结束自动清理,不可序列化或直接输出。

资源变量不是普通变量,而是外部句柄的 ID
PHP 的 resource 类型本质上不是数据,而是一个整数 ID(比如 resource(5) of type (stream) 中的 5),它指向 Zend 内核维护的一个全局资源表(&EG(regular_list))里的真实句柄,比如 FILE* 指针、MySQL 连接结构体、GD 图像资源等。你操作的从来不是资源本身,只是它的“门牌号”。
- 调用
fopen()返回的不是文件内容,而是这个 ID;fclose()传入的也只是这个 ID,内核靠它查表、释放底层 C 级资源 -
is_resource($fp)判断的是该 ID 是否在表中且类型匹配,不是检查变量里存了什么“对象” - 资源一旦被
fclose()或脚本结束自动清理,对应 ID 就失效——但变量本身仍存在,只是变成“悬空资源”,var_dump()仍显示resource(5),但再用会出错
为什么不能直接 echo 或 json_encode 资源变量
因为资源没有可序列化的值:它背后是 C 层的非内存数据(如操作系统文件描述符、网络 socket、图形上下文),PHP 的 echo、json_encode()、print_r() 都只处理 PHP 层能直接读取的值(字符串、数组、数字等),遇到 IS_RESOURCE 类型就只能退化为打印类型标识,或直接报错。
-
echo $fp;→ Warning: Object of class stdClass could not be converted to string(如果误当对象)或空输出 -
json_encode(['file' => $fp]);→null(资源被静默跳过) - 真正要“导出”资源状态?得用配套函数,比如
stream_get_meta_data($fp)提取流信息,而不是试图 dump 资源本身
手动创建资源类型(扩展开发场景)的关键三步
如果你在写 PHP 扩展并需要封装自定义外部句柄(比如 Redis 连接池、硬件设备句柄),必须走标准注册流程,否则 PHP 内核无法识别、无法自动回收。
- 先注册资源类型名:
zend_register_list_destructors_ex(),返回一个全局唯一的my_resource_descriptor整数 - 创建资源时用
ZEND_REGISTER_RESOURCE(res, fp, my_resource_descriptor),把你的 C 指针和类型 ID 一起登记进&EG(regular_list) - 务必提供析构器(第二个参数),哪怕只是
free()或close(),否则脚本结束时资源泄漏——PHP 不知道怎么销毁你的句柄 - 漏掉析构器或注册失败,会导致
var_dump()显示Unknown类型,或触发段错误(SIGSEGV)
资源变量的生命周期和常见误用
资源变量遵循 PHP 变量作用域规则,但其底层资源的生命周期由内核统一管理:脚本结束时遍历 &EG(regular_list) 自动调用对应析构器。这带来两个典型陷阱:
立即学习“PHP免费学习笔记(深入)”;
- 把资源赋值给静态变量或全局数组(如
$GLOBALS['db'] = $pdo;),看似“复用”,实则延长了资源存活时间,可能撑爆连接池或文件描述符上限 - 在循环里反复
fopen()却忘记fclose(),即使变量名被覆盖,旧资源 ID 仍在表中未释放,最终触发Warning: fopen(): Unable to open file: Too many open files - 用
unset($fp)只是删 PHP 变量,不触发析构——必须显式调用fclose($fp),或依赖脚本结束时的自动清理(不推荐依赖)
最危险的其实是“以为自己管好了”,比如封装成类但忘了在 __destruct() 里关资源,或者用 try/finally 但 catch 里吞掉了异常导致 finally 不执行。











