## 其它变更

### 核心变化

#### FFI

当 opcache.preload_user 是当前系统用户时，现在允许在预加载期间使用 FFI::load()。以前，如果设置了 opcache.preload_user 指令，则预加载期间无法调用 FFI::load()。

#### FPM

如果 socket 路径比操作系统支持的路径长度更长，则 FPM CLI 测试现在会失败。

#### Opcache

在 CLI 和 phpdbg SAPI 中，以 root 身份运行时，不再需要设置 opcache.preload_user 指令来进行预加载。在其它 SAPI 中，以 root 身份运行时需要此指令，因为在 SAPI 切换到非特权用户之前进行预加载。

#### Streams

如果在 socket 连接上有任何缓冲数据，而不是等待更多数据，fread() 阻塞会立即返回。

内存流在偏移量超过末尾时不会失败。而是在下次写入时内存会增加，并且旧末尾和偏移量之间的数据将使用零字节填充，类似于文件的工作方式。

现在，stat() 访问操作（像 file_exists() 及其类似这样）将使用真实路径而不是实际的 stream 路径。这与 stream 的打开方式保持一致。

### SAPI 模块的变更

#### CLI

`STDOUT`、`STDERR` 和 `STDIN` 流不再因资源销毁而关闭，这主要是在 CLI 完成时关闭。然而，仍然可以使用 fclose() 和类似函数明确关闭这些流。

### 函数变更

#### 核心

gc_status() 添加下列 8 个字段：

- `"running"` => bool
- `"protected"` => bool
- `"full"` => bool
- `"buffer_size"` => int
- `"application_time"` => float：总的应用时间，以秒为单位（包含 collector_time）
- `"collector_time"` => float：垃圾回收所花费的时间，单位为秒（包括 destructor_time 和 free_time）
- `"destructor_time"` => float：在垃圾回收期间执行析构方法的时间，单位为秒
- `"free_time"` => float：在垃圾回收期间花费在释放值上的时间，单位为秒

class_alias() 现在支持创建内部类的别名。

在运行时使用 `ini_set('open_basedir', ...);` 设置 open_basedir 时，不再接受包含父目录(`..`)的路径。以前只禁止以 `..` 开头的路径。这可以通过在路径前加上`./` 来轻松规避。

用户异常处理程序现在可以在关闭期间捕获异常。

highlight_string() 和 highlight_file() 的 HTML 结果已经改变。外部 HTML 标签之间的空格已移除。换行符和空格不再转换为 HTML 实体。整个 HTML 现在包装在 `<pre>` 标签中。外部的 `<span>` 标签已经与 `<code>` 标签合并。

#### Calendar

easter_date() 现在在 64 位系统上支持 1970 到 20 亿年，以前它只支持 1970 到 2037 年。

#### Curl

curl_getinfo() 现在支持两个新常量：`CURLINFO_CAPATH` 和 `CURLINFO_CAINFO`。如果 option 为 `null`，则会显示以下两个额外的键：`"capath"` 和 `"cainfo"`。

#### DOM

DOMCharacterData::appendData() 的返回值临时修改为 true。

DOMDocument::loadHTML()、DOMDocument::loadHTMLFile()、 和 DOMDocument::loadXML() 现在有了临时返回类型 bool。之前，返回类型是 `DOMDocument|bool`，但是自 PHP 8.0.0 起，DOMDocument 不再是静态可调用，所以不能返回。

#### Gd

imagerotate() 的签名已经更改。已经移除 `$ignore_transparent` 参数，因为自 PHP 5.5.0 起已忽略。

#### Intl

datefmt_set_timezone()（及其别名 IntlDateformatter::setTimeZone()）现在在成功时返回 `true`，以前返回 `null`。

IntlBreakiterator::setText() 现在在失败时返回 `false`，之前返回 `null`。现在在成功时返回 `true`，以前返回 `null`。

IntlChar::enumCharNames() 现在返回 bool。之前成功时返回 `null`，失败时返回 `false`。

当设置了无效的区域，IntlDateFormatter::__construct() 将抛出 `U_ILLEGAL_ARGUMENT_ERROR`。

#### MBString

mb_strtolower() 和 mb_convert_case() 实现了对希腊字母 sigma 的条件转换规则，条件转换只适用于 `MB_CASE_LOWER` 和 `MB_CASE_TITLE` 模式，不适用于 `MB_CASE_LOWER_SIMPLE` 和 `MB_CASE_TITLE_SIMPLE`。

mb_decode_mimeheader() 根据 RFC 2047 的要求解释 QPrint 编码的 MIME 编码单词中的下划线，它们将转换为空格。在这样的 MIME 编码单词中，下划线必须被编码为 `"=5F"`。

在极少数情况下，mb_encode_mimeheader() 会对输入字符串进行传输编码，在 PHP 8.2 中会将输入字符串作为原始 ASCII 传递。

当输入字符串使用 QPrint 编码时，mb_encode_mimeheader() 不再丢弃 NUL（零）字节。之前会导致某些文本编码的字符串，特别是 UTF-16 和 UTF-32，会被 mb_encode_mimeheader 破坏。

mb_detect_encoding() 现在的的“非严格”模式行为如文档中所述。之前，如果在所有候选编码中输入字符串的相同字节（比如，第一个字节）是无效的，将会返回 `false`。一般来说，当看到无效字节时，将会从考虑（consideration）中剔除候选编码，如果相同的输入字节剔除了考虑中的所有剩余编码，将返回 `false`。另一方面，如果所有的候选编码一个个都剔除了，它将返回最后剔除的那个，而不考虑字符串中可能遇到的编码错误。这与文档中描述的行为不同，文档中说：“如果 strict 设置为 false，将返回最接近的匹配编码”。

#### mysqli

当 `$constructor_args` 参数不为空且类没有构造方法时，mysqli_fetch_object() 现在抛出 ValueError 而不是 Exception。

当都没有传递 `$read` 和 `$error` 参数时，mysqli_poll() 现在会抛出 ValueError。

mysqli_field_seek() 和 mysqli_result::field_seek() 现在返回类型指定为 true 而不是 bool。

#### ODBC

odbc_autocommit() 现在接受 `$enable` 参数为 `null`。传递 `null` 与只传递 1 个参数的行为相同，即表明是否启用自动提交功能。

#### PGSQL

当 `$constructor_args` 参数不为空且类没有构造方法时，pg_fetch_object() 现在抛出 ValueError 而不是 Exception。

当指定的表无效时，pg_insert() 现在抛出 ValueError 而不是 `E_WARNING`。

当字段的值/类型与 PostgreSQL 的类型不匹配时，pg_insert() 和 pg_convert() 会抛出 ValueError 或 TypeError，而不是 `E_WARNING`。

pg_fetch_result()、pg_field_prtlen() 和 pg_field_is_null() 的 `$row` 参数现在可以为 null。

#### Random

mt_srand() 和srand() 变更为不使用检查参数的数量的方式来确定是否应该使用随机种子。传递 `null` 将生成随机种子，`0` 将使用 0 作为种子。函数现在与 Random\Engine\Mt19937::__construct() 一致。

#### Reflection

ReflectionClass::getStaticProperties() 的返回类型不再可为 null。

#### 标准

unserialize() 所触发的 `E_NOTICE` 被提升为 `E_WARNING`。

如果输入包含未使用的字节，unserialize() 现在会抛出新的 `E_WARNING`。

array_pad() 现在只接受数组元素可拥有的最大数量限制，以前每次最多只能添加 1048576 个元素。

strtok() 在开始标切分时，如果没有提供 token，则引发 `E_WARNING`。

当生成 salt 失败时，password_hash() 现在将底层的 Random\RandomException 链到 ValueError 的 `$previous` Exception。

如果使用数组作为 proc_open() 的 `$command`，则现在必须至少有一个非空元素。否则会抛出 ValueError。

如果 `$command` 数组是无效命令，proc_open() 将返回 `false`，而不是随后产生警告的资源对象。 Windows 系统已经是这种行为，但如果使用 posix_spawn 实现（大多数 Linux、BSD 和 MacOS 平台），也会是这种行为。 在一些旧平台上，由于不支持 posix_spawn，因此这种行为不会改变。

array_sum() 和 array_product() 现在会在数组中的值不能转换为 int/float 时发出警告。之前会忽略数组和对象，而其他所有值都被强制转换为 int。此外，定义了数字转换的对象（例如 GMP）现在会强制转换，而不是忽略。

现在 number_format() 的 `$decimals` 可以正确处理负整数。`$decimals` 为负数时，`$num` 值会四舍五入到小数点前 `$decimals` 位，而之前的 `$decimals` 会忽略负数，并且四舍五入到小数点后 0 位。

在 strrchr() 中增加了新的参数 `$before_needle`，它的行为与 strstr() 或 stristr() 函数中的对应参数类似。

对于未包含关闭引号的最后一个字段，str_getcsv() 和 fgetcsv() 现在返回空字符串，而不是带单个 null 字节的字符串。

### 其它扩展变更

#### 核心

在类型 bool 的值上使用 自增/减运算符（`++`/`--`）将会抛出警告。这是因为虽然目前没有效果，但是将来行为将是 `$bool += 1`。

在类型 null 的值上使用 自减运算符（`--`）将会抛出警告。这是因为虽然目前没有效果，但是将来行为将是 `$null -= 1`。

内部对象实现 _IS_NUMBER 转换，但不是重写加减法的 do_operator 处理程序，现在可以像 `$o += 1` 或 `$o -= 1` 一样递增和递减。

#### DOM

DOM 的生命周期机制已经被重写，这样默默删除的节点仍然可以获取，之前这样做会导致异常。

#### SQLite3

SQLite3 类现在抛出 SQLite3Exception（继承 Exception）而不是 Exception。

SQLite 错误代码现在在 exception error code 中传递，而不是包含在错误消息中。

### INI 文件处理更变

- 已弃用 `assert.*` INI 设置。这包括以下 INI 设置：

  - assert.active
  - assert.bail
  - assert.callback
  - assert.exception
  - assert.warning
  如果设置的值等于默认值，则不会发出弃用通知。应该使用 zend.assertions INI 设置。

- zend.max_allowed_stack_size 是新的 INI 指令，用于设置允许的最大栈大小。可能的值是 `0`（检测进程或线程的最大栈大小）、-1（无限制）或正数的单位为字节的值。默认值为 `0`。当无法检测到进程或线程的最大的栈大小时，使用已知的系统默认值。设置的值过高效果等同于不限制栈的大小。Fiber 使用 fiber.stack_size 作为允许的最大栈大小。当进程调用栈超过 zend.max_allowed_stack_size-zend.reserved_stack_size 个字节时，将抛出 Error，以防止栈溢出引发分段错误，目的是使调试更容易。在没有控制递归调用内部函数或者魔术方法 __toString()、__clone()、 __sleep()、__destruct() 期间，栈的大小会增加。这与栈缓冲区溢出无关，也不是安全特性。

- zend.reserved_stack_size 是新的 INI 指令，用于设置预留的栈大小（以字节为单位）。当检查栈大小时，会从最大允许的栈大小中减去，作为缓冲区。

### 性能

#### DOM

现在遍历 DOMNodeList 时使用了缓存，因此默认情况下请求元素不再需要花费两次的时间。

从节点获取文本内容现在避免了分配，从而提高了性能。

DOMChildNode::remove() 现在运行性能是 O(1)。

#### 标准

file() flag 错误检查现在快了大约 7%。

#### SPL

RecursiveDirectoryIterator 现在循环目录时执行更少的 I/O。

`↑` and `↓` to navigate • `Enter` to select • `Esc` to close • `/` to open