LamentXU@blog:~$ cd ./php-dev

作为 php core developer,我从事 php 语言的内核维护和开发工作有一段时间了。一直有很多想法和经验总是忘记。遂写一篇类似与小记的博客记录一下。

php 内核贡献代码统计
-- 新增行数
-- 删除行数
-- Commits
正在加载统计...

DONE

以下是我对 PHP 做过或正在推进的用户可见行为变化(不包含重构)。(更新于 2026-07-01 00:00 CST)

PHP 8.6.x

RFC

  • trim / ltrim / rtrim / chop 支持换页符。(PR #20788, RFC, PHP.WATCH)
  • 为 intl 添加 Locale::getDisplayKeyword()(别名 locale_get_display_keyword()) 和 Locale::getDisplayKeywordValue() (别名 locale_get_display_keyword_value())。(PR #22264, RFC)
  • 弃用 metaphone 相关函数。(PR #21889, RFC)

安全漏洞

  • 等待披露
  • 等待披露

Bug 修复

  • ext/intl 修复 IntlIterator::current() 在无效迭代器上返回 UNKNOWN:0,现返回 NULL。(PR #22495)
  • ext/intl 修复 IntlDateFormatter::parse() / datefmt_parse()IntlDateFormatter::localtime() / datefmt_localtime() 现在在偏移量参数不是 int 类型时抛出 TypeError。(PR #22533)
  • ext/intl 修复 NumberFormatter::parse()NumberFormatter::parseCurrency() 拒绝超出 32 位范围的偏移量值,而不是静默截断。(PR #22572)
  • ext/intl 在 64 位平台上让 MessageFormatter::parse()PHP_INT_MIN 作为 int 返回。(PR #22131)
  • ext/soap 拒绝超出 XML Schema integer 范围的值。(PR #22178)
  • ext/intl 修复 UConverter::transcode() 替换字符长度截断。(PR #22199)
  • ext/intl 在 IntlNumberRangeFormatter::format() 中保留 64 位整数精度。(PR #22202)
  • ext/intl 修复 ResourceBundle 禁用回退时的错误信息。(PR #22236)
  • ext/standard phpcredits() 修复 QA 团队标题对齐问题。(PR #22501)
  • ext/intl 修复 IntlListFormatterformat() 失败后的对象错误状态。(从 PHP 8.5 合并上来;commit 946d687
  • ext/intl 修复日历日期/时间 API 中越界的参数位置。(从 PHP 8.5 合并上来;commit 10704f0
  • ext/intl 在 IntlDateFormatter 错误信息中使用 PATTERN 常量名。(从 PHP 8.5 合并上来;commit 2d78489
  • ext/intl 修复未初始化日历参数的参数位置。(从 PHP 8.5 合并上来;commit 27d593e
  • ext/soap 修复解码 SOAP 数组索引时的整数溢出。(从 PHP 8.5 合并上来;PR #21963
  • ext/phar 在 Phar::addEmptyDir() 中更正确地保留 .phar 魔术目录。(从 PHP 8.5 合并上来;PR #22011
  • ext/intl 为无效显示类型同步 IntlTimeZone 对象错误状态。(从 PHP 8.5 合并上来;commit 9e68cf8
  • ext/intl 保持 Spoofchecker 限制级别 API 的 ICU 53 版本保护。(从 PHP 8.5 合并上来;commit edde169
  • ext/opcache 修复有类型按值返回时未解开引用包装的问题。(从 PHP 8.5 合并上来;PR #21973
  • ext/intl 修复 transliterator_transliterate() 错误信息中的参数位置。(从 PHP 8.5 合并上来;commit d8e7418
  • ext/phar 拒绝格式错误的 ZIP 额外字段,避免剩余长度下溢。(从 PHP 8.5 合并上来;commit 657f0d6, PR #22330
  • ext/intl 让 Locale::lookup()locale_lookup() 在语言标签无效时返回 null,而不是回退到默认 locale。(从 PHP 8.5 合并上来;PR #22306
  • ext/intl 让 Collator::__construct()Spoofchecker::__construct() 在重复构造已初始化对象时抛出 Error,避免覆盖 ICU 句柄造成内存泄漏。(从 PHP 8.5 合并上来;PR #22386
  • ext/intl 让 IntlListFormatter::__construct() 在重复构造已初始化对象时抛出 Error,避免覆盖 ICU 句柄造成内存泄漏。(从 PHP 8.5 合并上来;PR #22394
  • ext/phar 修复以 .phar 为前缀但不是魔术目录的目录处理。(从 PHP 8.5 合并上来;PR #22372
  • base_convert() 修复较小缓冲区长度下的转换结果错误。(从 PHP 8.5 合并上来;PR #22406
  • ext/zip 修复 ZipArchive::getFromIndex() 忽略已删除条目的 FL_UNCHANGED 标志。(从 PHP 8.5 合并上来;PR #21706

其他重大改动

  • linkinfo('') 现在会抛出 ValueError。(PR #21793)
  • getenv()putenv() 拒绝包含 NUL 字节的参数。(PR #21817)
  • ext/zlib 在 deflate_init()inflate_init() 中使用更严格的整数选项处理。(PR #21841, PR #21860, PR #21909)
  • proc_open() 拒绝 $cwd 中的 NUL 字节。(PR #21871)
  • parse_str() 拒绝 $string 中的 NUL 字节。(PR #21942)
  • dl() 拒绝 $extension_filename 中的 NUL 字节。(PR #22358)
  • openlog() 拒绝 $prefix 中的 NUL 字节。(PR #22366)
  • ext/gmp 对 **<<>> 中会损失精度的浮点数右操作数发出弃用警告。(PR #21945)

其他微小改动

  • shmop_open() 拒绝超出 key_t 长度的值。(PR #22423)
  • ext/intl 在所有受支持的 ICU 版本上暴露 Spoofchecker 限制级别 API。(PR #22055, commit edde169)
  • pcntl_exec() 验证 $args 必须是列表数组。(PR #21951)
  • ext/intl 对不可转为字符串的时区对象抛出 TypeError。(PR #21990)
  • IntlBreakIterator::getLocale() 对无效 locale type 值抛出 ValueError。(PR #21997)。
  • shm_attach() 拒绝超出 key_t 长度的值。(PR #22423)
  • number_format() 拒绝过大的 $decimals 参数,避免 OOM。(PR #22435)
  • sleep()usleep() 现在拒绝超出平台上限的值,修复无符号整数溢出。(PR #22619)

性能优化

  • ext/intl 完成一批小优化。(PR #22069)
  • ext/zip 在已知数组大小时预先初始化数组。(PR #21572)
  • ext/uri 预先初始化错误数组。(PR #21560)
  • ext/uri 优化 RFC3986 解析中的字符串分配。(PR #21550)
  • ext/pgsql 追加换行时移除一次不必要的 NUL 字节复制。(PR #21597)
  • ext/json 优化美化输出时的缩进逻辑。(PR #21474)
  • ext/phar 优化 Phar 目录流中的临时字符串处理。(PR #22374)

PHP 8.5.x

Bug 修复

  • ext/intl 修复 IntlListFormatterformat() 失败后的对象错误状态。(commit 946d687)
  • ext/intl 修复日历日期/时间 API 中越界的参数位置。(从 PHP 8.4 合并上来;commit 10704f0
  • ext/intl 在 IntlDateFormatter 错误信息中使用 PATTERN 常量名。(从 PHP 8.4 合并上来;commit 2d78489
  • ext/intl 修复未初始化日历参数的参数位置。(从 PHP 8.4 合并上来;commit 27d593e
  • ext/soap 修复解码 SOAP 数组索引时的整数溢出。(从 PHP 8.4 合并上来;PR #21963
  • ext/phar 在 Phar::addEmptyDir() 中更正确地保留 .phar 魔术目录。(从 PHP 8.4 合并上来;commit 3064540
  • ext/intl 为无效显示类型同步 IntlTimeZone 对象错误状态。(从 PHP 8.4 合并上来;commit 9e68cf8
  • ext/intl 保持 Spoofchecker 限制级别 API 的 ICU 53 版本保护。(从 PHP 8.4 合并上来;commit edde169
  • ext/opcache 修复有类型按值返回时未解开引用包装的问题。(从 PHP 8.4 合并上来;PR #21973
  • ext/intl 修复 transliterator_transliterate() 错误信息中的参数位置。(从 PHP 8.4 合并上来;commit d8e7418
  • ext/phar 拒绝格式错误的 ZIP 额外字段,避免剩余长度下溢。(从 PHP 8.4 合并上来;commit 657f0d6, PR #22330
  • ext/intl 让 Locale::lookup()locale_lookup() 在语言标签无效时返回 null,而不是回退到默认 locale。(从 PHP 8.4 合并上来;PR #22306)
  • ext/intl 让 Collator::__construct()Spoofchecker::__construct() 在重复构造已初始化对象时抛出 Error,避免覆盖 ICU 句柄造成内存泄漏。(从 PHP 8.4 合并上来;PR #22386
  • ext/intl 让 IntlListFormatter::__construct() 在重复构造已初始化对象时抛出 Error,避免覆盖 ICU 句柄造成内存泄漏。(PR #22394)
  • ext/phar 修复以 .phar 为前缀但不是魔术目录的目录处理。(从 PHP 8.4 合并上来;PR #22372
  • base_convert() 修复较小缓冲区长度下的转换结果错误。(从 PHP 8.4 合并上来;PR #22406)
  • ext/zip 修复 ZipArchive::getFromIndex() 忽略已删除条目的 FL_UNCHANGED 标志。(从 PHP 8.4 合并上来;PR #21706)

PHP 8.4.x

Bug 修复

  • ext/intl 修复日历日期/时间 API 中越界的参数位置。(commit 10704f0)
  • ext/intl 在 IntlDateFormatter 错误信息中使用 PATTERN 常量名。(commit 2d78489)
  • ext/intl 修复未初始化日历参数的参数位置。(commit 27d593e)
  • ext/soap 修复解码 SOAP 数组索引时的整数溢出。(PR #21963)
  • ext/phar 在 Phar::addEmptyDir() 中更正确地保留 .phar 魔术目录。(commit 3064540)
  • ext/intl 为无效显示类型同步 IntlTimeZone 对象错误状态。(commit 9e68cf8)
  • ext/intl 保持 Spoofchecker 限制级别 API 的 ICU 53 版本保护。(commit edde169)
  • ext/opcache 修复有类型按值返回时未解开引用包装的问题。(PR #21973)
  • ext/intl 修复 transliterator_transliterate() 错误信息中的参数位置。(commit d8e7418)
  • ext/phar 拒绝格式错误的 ZIP 额外字段,避免剩余长度下溢。(commit 657f0d6, PR #22330)
  • ext/intl 让 Locale::lookup()locale_lookup() 在语言标签无效时返回 null,而不是回退到默认 locale。(PR #22306)
  • ext/intl 让 Collator::__construct()Spoofchecker::__construct() 在重复构造已初始化对象时抛出 Error,避免覆盖 ICU 句柄造成内存泄漏。(PR #22386)
  • ext/phar 修复以 .phar 为前缀但不是魔术目录的目录处理。(PR #22372)
  • base_convert() 修复较小缓冲区长度下的转换结果错误。(PR #22406)
  • ext/zip 修复 ZipArchive::getFromIndex() 忽略已删除条目的 FL_UNCHANGED 标志。(PR #21706)

TODO

Zend 内核

新 API

  • 添加 inline 的 helper function,分别是
    • zend_bin2hex()
    • zend_bin2hex_upper()
    • zend_bin2hex_str()

这是因为内核里有很多扩展依赖 ext/hash,这本身没什么问题,但是这个依赖仅仅是要调用 ext/hash/php_hashphp_hash_bin2hex。这些功能很简单。所以我们可以把这些 bin2hex 的东西做成 Zend 里内置的 API,直接给 ext 使用。这样可以避免很多 ext 依赖于 ext/hash

  • 添加 inline 的 helper function,分别是
    • zend_str_append_char_to_raw
    • zend_str_concat_to_raw
    • zend_str_concat3_to_raw

现在,我们有很多 zend_str_* 的 helper 可以完成 zend_string 的拼接。但是对于 C char,利用这些 helper 会多进行一次内存的 allocation,导致性能下降,因此,所有对于 C char 拼接的场景我们都是用原始的 memchr 方式。这样会导致代码的可读性降低。我们可以实现这些 helper 来帮助我们进行 C char 的拼接。

API 改进

  • 重写 zend_string_concat2zend_string_concat3。这两个 inline helper 被大量使用,却没有检查字符串长度是否超出边界。理想情况下我们应该重写这些函数,在拼接前检查一下长度之和是否超出字符串最大长度以防止溢出。
  • 对以下函数
    • ext/zlib deflate_init
    • ext/zlib inflate_init

zval_try_get_* 替换 zval_get_*zval_get_*系列的函数,比如 zval_get_long,会直接强制把 zval 类型的参数转化为 long。这个强转甚至是静默的。所以在我看来任何人都不应该用这个函数(它应该被 deprecated)。我们应当审计所有该函数的应用场景并换成更严格的 zval_try_get_*,以在强制转换时抛出 TypeError/ValueError。

性能优化

  • 对以下函数
    • 优化 ext/zip php_zip_glob
    • 优化 ext/zip php_zip_pcre
    • 优化 ext/uri fill_errors

php代码库中有很多地方采用 array_init 来初始化数组。这个函数会先计算参数的长度,分配内存再创建数组。很多情况下我们在初始化之前就知道数组的长度了。因此,可以使用 array_init_size 函数直接分配内存创建数组,省去计算长度的一部分以达到性能优化。

  • 对以下函数
    • 优化 ext/json php_json_pretty_print_indent
    • 优化 ext/uri php_uri_parser_rfc3986_path_read
    • 优化 ext/uri php_uri_parser_rfc3986_path_host

这些函数中都多多少少涉及到利用循环的方式动态扩容字符串/数组长度的行为。我们可以使用分配(pre-allocation)的方式一次性将长度计算完全后直接分配所有内存,避免在循环体中多次动态扩容,以在复杂场景(即,假设使用优化前版本需要循环很多次的场景)下带来巨量优化。

ext

  • php 的 password_verify 函数里的密码会被 NUL 截断。这主要是因为 php 的 bcrypt 实现是完全用 C char 写的。我们应该整个用 zend_string 重写一遍这个实现 (ps:巨大工作量)
  • 对以下函数进行如下描述中的改动
    • linkinfo
    • putenv
    • getenv
    • proc_open
    • parse_string

检查所有 ext 的函数在参数中包含空 and/or 含 NUL 的字符串时是否能正常工作。如果不能,抛出 ValueError。

  • 重写整个 RecursiveArrayIterator 的逻辑。目前该类会存储 bucket pointer。这是一种相当危险的行为(i.e. 导致静默的内存泄露),而且已经使 UAF 在任意版本中的存在。

  • OpenSSL 对于证书的处理用的是 libc 的 mktime,然后因为微软死了亲妈导致经典的 2038 问题,这个时间在 windows 系统上一旦超过 2038 年就会溢出。我们要重写这个实现避免使用微软的傻逼接口。

  • declare(encoding=...) 在 encoding 为 ASCII 或 UTF-8 这种不是 multibyte 的编码方式时,在 zend.multibyte=0 时仍会导致关于 multibyte 的 warning 出现。

RFC

  • deprecate 掉 open_basedir。(我知道听起来很疯狂。我懒得解释为什么…)
  • deprecate 掉 soundex 函数。
  • deprecate 掉 php://dechunk 的 filter。
  • php 的 UUID 扩展。

研究

  • __PHP_Incomplete_Class
  • Reflection