PHP C 扩展移植到 PHP 8.6:UPGRADING.INTERNALS 里的内部 API 移除与替换
维护 PHP C 扩展时,把源码移植到 8.6 需要处理的核心问题是:8.6 移除或改签了一批内部 API,旧代码会直接编译失败,或者编译通过但行为出错。php-src 仓库根目录的 UPGRADING.INTERNALS 是这份清单的权威来源,它按「破坏性变更、移除、修改、新增」四类列出 8.6 的内部 API 变化,并逐项给出替换写法。
适配路径:准备 8.6 开发构建环境 → 对照移除清单逐项修改扩展代码 → 重新构建 → make test 验证。
动手前:定位调用点与构建验证
在改代码之前,先按符号名在扩展源码里检索一遍,把调用点列全,再对照 UPGRADING.INTERNALS 分类处理,避免漏改某个分支里的调用。扩展侧的常规构建验证流程:
phpize
./configure --enable-your-ext
make -j"$(nproc)"
make test
函数名、宏名、参数类型、结构字段类型这几类变化会在编译阶段暴露出来;make test 则用来覆盖运行期路径,确认改动没有改变原有行为。
移除项与替换写法
| 移除项 | 8.6 的替换写法 | 原因 |
|---|---|---|
ZVAL_IS_NULL() | 使用 Z_ISNULL() | 原命名有误(misnamed) |
下划线前缀的流函数(_php_stream_cast()、_php_stream_free()、_php_stream_seek()、_php_stream_tell()、_php_stream_read()、_php_stream_write() 等 29 个) | 去掉前缀的宏现在是正式函数名,直接调用 | 原为宏/函数双形态 |
zend_save_error_handling() | 移除 | — |
zend_parse_parameter() | 使用某个 zend_parse_arg_TYPE() API | — |
几处需要注意的细节:
ZVAL_IS_NULL()属于命名问题,替换为Z_ISNULL()后语义不变。- 29 个下划线前缀的流函数整体去掉了前缀,无下划线的名字现在是正式函数名,原来写在扩展里的
_php_stream_*调用需要逐个改名。 zend_save_error_handling()没有替换项,调用点要连同它周围的错误处理逻辑一起删掉。zend_parse_parameter()改用对应的zend_parse_arg_TYPE()系列 API,具体用哪个取决于原来解析的参数类型。
改名与结构变化
ZEND_AST_METHOD_REFERENCE改名为ZEND_AST_TRAIT_METHOD_REFERENCE。AST 节点枚举名变更,按节点类型做分派的代码要同步改。ZSTR_INIT_LITERAL()、zend_string_starts_with_literal()、zend_string_starts_with_literal_ci()现在支持含 NUL 字节的字符串,但不再接受非字面量char*:传入的必须是字面量,运行期拼出来的char*不再被接受。zend_function.arg_info现在总是zend_arg_info*;此前内部函数是zend_internal_arg_info(除非设置了ZEND_ACC_USER_ARG_INFO)。读取arg_info时不再需要按函数类型分支处理。EG(in_autoload)改名为EG(autoload_current_classnames),且从指针变为直接内嵌的 HashTable 结构,访问方式要跟着调整。zend_argument_error_variadic()新增'function'参数;php_verror()移除了param参数。php_stream_wrapper_log_error()签名从(wrapper, options, fmt, ...)变为(wrapper, context, options, severity, terminating, code, fmt, ...)。要保持旧行为的参数取值为:context传NULL、severity 传E_WARNING、code 传ZEND_ENUM_StreamErrorCode_Generic、terminating仅当错误会中止操作时传true。zend_create_closure()、zend_create_fake_closure()、zend_create_partial_closure()现在接收绑定对象为zend_object*(原来是zval*);相应地zend_get_closure_this_ptr()返回zend_object*,未绑定时为NULL(原来是IS_UNDEF的zval*)。拿到返回值后的判空条件要一并改。
模块内部符号
Removed/Changed 只覆盖核心内部 API。如果扩展依赖 date、intl、mbstring、mysqlnd、session、standard、uri、xml 等内置模块的内部符号,第 3 节 “Module changes” 里有各自的改动,需要单独核对。
例如 session 的 PS_OPEN_ARGS 现在定义为 void **mod_data, zend_string *save_path, zend_string *session_name;_php_error_log() 改为接收 zend_string* 且带 zend_result 返回类型。这类改动分散在各模块名下,只跑一遍核心清单很容易漏掉。
新增 API
8.6 同时新增了一批接口:zend_hash_str_lookup()、zend_bin2hex() / zend_bin2hex_str()、zval_try_get_double()、zend_ast_dup(),以及 中的 php_io_copy() 等。替换旧实现时,在 “New” 一节确认新接口的准确名称和用途,再决定是否把手写的等价逻辑换掉。