PHP Conference Japan 2024

向后不兼容的更改

PHP 核心

字符串到数字的比较

现在,数字和非数字字符串之间的非严格比较通过将数字转换为字符串并比较字符串来工作。数字和数字字符串之间的比较继续像以前一样工作。值得注意的是,这意味着 0 == "not-a-number" 现在被认为是 false。

比较 之前 之后
0 == "0" true true
0 == "0.0" true true
0 == "foo" true false
0 == "" true false
42 == " 42" true true
42 == "42foo" true false

其他不兼容的更改

  • match 现在是保留关键字。

  • mixed 现在是保留字,因此不能用于命名类、接口或特征,并且也不允许在命名空间中使用。

  • 断言失败现在默认抛出。如果需要旧的行为,可以在 INI 设置中设置 assert.exception=0

  • 与类同名的函数不再被解释为构造函数。应该改用 __construct() 方法。

  • 已删除静态调用非静态方法的能力。因此,当检查具有类名的非静态方法时,is_callable() 将失败(必须使用对象实例进行检查)。

  • 已删除 (real)(unset) 转换。

  • 已删除 track_errors ini 指令。这意味着 php_errormsg 不再可用。可以使用 error_get_last() 函数代替。

  • 已删除定义不区分大小写的常量的能力。define() 的第三个参数不再可以是 true

  • 已删除使用 __autoload() 函数指定自动加载程序的能力。应改用 spl_autoload_register()

  • errcontext 参数将不再传递给使用 set_error_handler() 设置的自定义错误处理程序。

  • create_function() 已被删除。可以使用匿名函数代替。

  • each() 已被删除。应改用 foreachArrayIterator

  • 已删除使用 Closure::fromCallable()ReflectionMethod::getClosure() 从方法创建的闭包中解除绑定 this 的能力。

  • 还删除了从包含 this 用法的正确闭包中解除绑定 this 的能力。

  • 已删除使用 array_key_exists() 与对象的能力。可以使用 isset()property_exists() 代替。

  • 已使 array_key_exists() 关于 key 参数类型的行为与 isset() 和普通数组访问保持一致。所有键类型现在都使用通常的强制转换,并且数组/对象键会抛出 TypeError

  • 任何以数字 n 作为其第一个数字键的数组都将对下一个隐式键使用 n+1,即使 n 为负数。

  • 默认的 error_reporting 级别现在是 E_ALL。以前它排除了 E_NOTICEE_DEPRECATED

  • display_startup_errors 现在默认启用。

  • 在没有父类的类中使用 parent 现在将导致致命编译时错误。

  • @ 运算符将不再抑制致命错误 (E_ERRORE_CORE_ERRORE_COMPILE_ERRORE_USER_ERRORE_RECOVERABLE_ERRORE_PARSE)。应调整预期 @ 使用时 error_reporting0 的错误处理程序以改用掩码检查

    <?php
    // 替换
    function my_error_handler($err_no, $err_msg, $filename, $linenum) {
    if (
    error_reporting() == 0) {
    return
    false;
    }
    // ...
    }

    // 使用
    function my_error_handler($err_no, $err_msg, $filename, $linenum) {
    if (!(
    error_reporting() & $err_no)) {
    return
    false;
    }
    // ...
    }
    ?>

    此外,应注意在生产环境中不显示错误消息,这可能导致信息泄漏。请确保将 display_errors=Off 与错误日志一起使用。

  • #[ 不再被解释为注释的开始,因为此语法现在用于属性。

  • 由于不兼容的方法签名(LSP 违规)导致的继承错误现在将始终生成致命错误。以前在某些情况下会生成警告。

  • 连接运算符的优先级相对于位移和加法以及减法发生了变化。

    <?php
    echo "Sum: " . $a + $b;
    // 之前被解释为:
    echo ("Sum: " . $a) + $b;
    // 现在被解释为:
    echo "Sum:" . ($a + $b);
    ?>

  • 具有默认值且在运行时解析为**null**的参数将不再隐式地将参数类型标记为可为空。必须改用显式可为空类型或显式**null**默认值。

    <?php
    // 替换
    function test(int $arg = CONST_RESOLVING_TO_NULL) {}
    // 为
    function test(?int $arg = CONST_RESOLVING_TO_NULL) {}
    // 或
    function test(int $arg = null) {}
    ?>

  • 一些警告已转换为Error异常。

    • 尝试写入非对象的属性。之前,这会为 null、false 和空字符串隐式创建 stdClass 对象。
    • 尝试向数组追加元素,而该数组已使用 PHP_INT_MAX 键。
    • 尝试使用无效类型(数组或对象)作为数组键或字符串偏移量。
    • 尝试写入标量值的数组索引。
    • 尝试解包非数组/可遍历对象。
    • 尝试访问未定义的非限定常量。之前,非限定常量访问会导致警告,并被解释为字符串。
    • 向非可变内置函数传递错误数量的参数将抛出ArgumentCountError
    • count()传递无效的可计数类型将抛出TypeError

    一些通知已转换为警告。

    • 尝试读取未定义的变量。
    • 尝试读取未定义的属性。
    • 尝试读取未定义的数组键。
    • 尝试读取非对象的属性。
    • 尝试访问非数组的数组索引。
    • 尝试将数组转换为字符串。
    • 尝试使用资源作为数组键。
    • 尝试使用 null、布尔值或浮点数作为字符串偏移量。
    • 尝试读取超出范围的字符串偏移量。
    • 尝试将空字符串分配给字符串偏移量。

  • 尝试将多个字节分配给字符串偏移量现在将发出警告。

  • 源文件中的意外字符(例如字符串之外的 NUL 字节)现在将导致ParseError异常,而不是编译警告。

  • 未捕获的异常现在将经过“干净关闭”,这意味着析构函数将在未捕获的异常之后被调用。

  • 编译时致命错误“Only variables can be passed by reference”已延迟到运行时,并转换为“Argument cannot be passed by reference”Error异常。

  • 一些“Only variables should be passed by reference”通知已转换为“Argument cannot be passed by reference”异常。

  • 匿名类的生成名称已更改。它现在将包含第一个父类或接口的名称。

    <?php
    new class extends ParentClass {};
    // -> ParentClass@anonymous
    new class implements FirstInterface, SecondInterface {};
    // -> FirstInterface@anonymous
    new class {};
    // -> class@anonymous
    ?>

    上面显示的名称后面仍然跟着一个 NUL 字节和一个唯一的后缀。

  • 现在要求特性别名适配中的非绝对特性方法引用是明确的。

    <?php
    class X {
    use
    T1, T2 {
    func as otherFunc;
    }
    function
    func() {}
    }
    ?>

    如果 T1::func()T2::func() 都存在,则此代码之前会被静默接受,并且 func 被假定为引用 T1::func。现在它将改为生成致命错误,并且需要显式写入 T1::funcT2::func

  • 现在将根据实现类的方 法检查特性中定义的抽象方法的签名。

    <?php
    trait MyTrait {
    abstract private function
    neededByTrait(): string;
    }

    class
    MyClass {
    use
    MyTrait;

    // 错误,因为返回类型不匹配。
    private function neededByTrait(): int { return 42; }
    }
    ?>

  • 现在禁用函数的处理方式与不存在的函数完全相同。调用禁用函数将将其报告为未知,并且现在可以重新定义禁用函数。

  • data:// 流包装器不再可写,这与文档记录的行为相匹配。

  • 算术和位运算符 +-*/**%<<>>&|^~++-- 现在将在其中一个操作数为arrayresource或非重载object时始终抛出TypeError。唯一的例外是数组 + 数组合并操作,它仍然受支持。

  • 浮点数到字符串的转换现在将始终以与区域设置无关的方式运行。

    <?php
    setlocale
    (LC_ALL, "de_DE");
    $f = 3.14;
    echo
    $f, "\n";
    // 之前:3,14
    // 现在:3.14
    ?>

    有关自定义数字格式的方法,请参阅printf()number_format()NumberFormatter()

  • 已删除对用于偏移量访问的已弃用花括号的支持。

    <?php
    // 而不是:
    $array{0};
    $array{"key"};
    // 写:
    $array[0];
    $array["key"];
    ?>

  • 现在,除非该方法是构造函数,否则在私有方法上应用 final 修饰符将产生警告。

  • 如果对象的构造函数exit(),则将不再调用对象的析构函数。这与构造函数抛出异常时的行为相匹配。

  • 命名空间名称不再包含空格:虽然 Foo\Bar 将被识别为命名空间名称,但 Foo \ Bar 不会。相反,现在允许使用保留关键字作为命名空间段,这也可能会更改代码的解释:new\x 现在与 constant('new\x') 相同,而不是 new \x()

  • 嵌套的三元运算符现在需要显式括号。

  • debug_backtrace()Exception::getTrace()将不再提供对参数的引用。无法通过回溯更改函数参数。

  • 数字字符串处理已更改为更直观且更不容易出错。现在,为了与处理前导空格的方式保持一致,数字字符串中允许使用尾随空格。这主要影响

    • is_numeric()函数。
    • 字符串到字符串的比较。
    • 类型声明。
    • 递增和递减操作。

    “前导数字字符串”的概念已基本取消;保留此概念的案例是为了简化迁移。发出E_NOTICE“A non well-formed numeric value encountered”的字符串现在将发出E_WARNING“A non-numeric value encountered”,并且所有发出E_WARNING“A non-numeric value encountered”的字符串现在将抛出TypeError。这主要影响

    • 算术运算。
    • 位运算。

    将此E_WARNING更改为TypeError也会影响非法的字符串偏移量导致的E_WARNING“Illegal string offset 'string'”。从字符串到 int/float 的显式转换行为没有改变。

  • 如果魔术方法声明了参数和返回值类型,现在将对其进行检查。签名应与以下列表匹配

    • __call(string $name, array $arguments): mixed
    • __callStatic(string $name, array $arguments): mixed
    • __clone(): void
    • __debugInfo(): ?array
    • __get(string $name): mixed
    • __invoke(mixed $arguments): mixed
    • __isset(string $name): bool
    • __serialize(): array
    • __set(string $name, mixed $value): void
    • __set_state(array $properties): object
    • __sleep(): array
    • __unserialize(array $data): void
    • __unset(string $name): void
    • __wakeup(): void

  • call_user_func_array() 数组键现在将被解释为参数名称,而不是被静默忽略。

  • 在命名空间内声明名为assert()的函数不再允许,并会发出E_COMPILE_ERROR错误。 assert() 函数受引擎的特殊处理,在定义具有相同名称的命名空间函数时,可能会导致不一致的行为。

资源到对象的迁移

一些资源已迁移到对象。使用is_resource()进行返回值检查应替换为检查false

COM 和 .Net(Windows)

已删除从类型库导入不区分大小写的常量功能。 com_load_typelib() 的第二个参数不再可以为 false;com.autoregister_casesensitive 不再可以禁用;com.typelib_file 中的不区分大小写标记将被忽略。

CURL

CURLOPT_POSTFIELDS 不再接受对象作为数组。要将对象解释为数组,请执行显式(array)转换。这同样适用于其他接受数组作为参数的选项。

日期和时间

mktime()gmmktime() 现在至少需要一个参数。可以使用time() 获取当前时间戳。

DOM

已删除 DOM 扩展中未实现的类,这些类没有行为并且包含测试数据。这些类在最新版本的 DOM 标准中也已被删除

  • DOMNameList
  • DomImplementationList
  • DOMConfiguration
  • DomError
  • DomErrorHandler
  • DOMImplementationSource
  • DOMLocator
  • DOMUserDataHandler
  • DOMTypeInfo
  • DOMStringExtend

已删除 DOM 扩展中未实现的方法,这些方法没有行为

  • DOMNamedNodeMap::setNamedItem()
  • DOMNamedNodeMap::removeNamedItem()
  • DOMNamedNodeMap::setNamedItemNS()
  • DOMNamedNodeMap::removeNamedItemNS()
  • DOMText::replaceWholeText()
  • DOMNode::compareDocumentPosition()
  • DOMNode::isEqualNode()
  • DOMNode::getFeature()
  • DOMNode::setUserData()
  • DOMNode::getUserData()
  • DOMDocument::renameNode()

Enchant

Exif

read_exif_data() 函数已被移除;应改用 exif_read_data()

过滤器

GD

GMP

gmp_random() 函数已被移除。应改用 gmp_random_range()gmp_random_bits() 之一。

Iconv

不再支持在错误情况下无法正确设置 errno 的 iconv 实现。

IMAP

国际化函数

LDAP

MBString

OCI8

ODBC

OpenSSL

正则表达式(Perl 兼容)

传递无效的转义序列时,它们不再被解释为字面量。此行为以前需要 X 修饰符,该修饰符现在被忽略。

PHP 数据对象

  • 默认错误处理模式已从“静默”更改为“异常”。有关详细信息,请参阅 错误和错误处理

  • 一些 PDO 方法的签名已更改

    • PDO::query(string $query, ?int $fetchMode = null, mixed ...$fetchModeArgs)
    • PDOStatement::setFetchMode(int $mode, mixed ...$args)

PDO ODBC

已移除 php.ini 指令 pdo_odbc.db2_instance_name

PDO MySQL

PDO::inTransaction() 现在报告连接的实际事务状态,而不是 PDO 维持的近似值。如果执行了受“隐式提交”影响的查询,PDO::inTransaction() 随后将返回 false,因为事务不再处于活动状态。

PostgreSQL

  • 不再支持使用多个参数而不是连接字符串的已弃用 pg_connect() 语法。

  • 不再支持将连接作为最后一个参数传递的已弃用 pg_lo_import()pg_lo_export() 签名。连接应改为作为第一个参数传递。

  • 对于行数为零的结果集,pg_fetch_all() 现在将返回空数组,而不是 false

Phar

与 phar 关联的元数据将不再自动反序列化,以修复由于对象实例化、自动加载等导致的潜在安全漏洞。

反射

  • 方法签名

    • ReflectionClass::newInstance($args)
    • ReflectionFunction::invoke($args)
    • ReflectionMethod::invoke($object, $args)

    已更改为

    • ReflectionClass::newInstance(...$args)
    • ReflectionFunction::invoke(...$args)
    • ReflectionMethod::invoke($object, ...$args)

    必须与 PHP 7 和 PHP 8 都兼容的代码可以使用以下签名来兼容这两个版本

    • ReflectionClass::newInstance($arg = null, ...$args)
    • ReflectionFunction::invoke($arg = null, ...$args)
    • ReflectionMethod::invoke($object, $arg = null, ...$args)

  • ReflectionType::__toString() 方法现在将返回类型的完整调试表示形式,并且不再弃用。特别是,结果将包含可空类型的可空性指示符。返回值的格式不稳定,可能会在 PHP 版本之间发生变化。

  • 已删除 Reflection 的 export() 方法。取而代之的是,反射对象可以转换为字符串。

  • ReflectionMethod::isConstructor()ReflectionMethod::isDestructor() 现在也针对接口的 __construct()__destruct() 方法返回 true。以前,这仅适用于类和特性的方法。

  • ReflectionType::isBuiltin() 方法已移至 ReflectionNamedTypeReflectionUnionType 没有此方法。

套接字

标准 PHP 库 (SPL)

标准库

  • assert() 将不再评估字符串参数,而是将其视为任何其他参数。应使用 assert($a == $b) 而不是 assert('$a == $b')assert.quiet_eval ini 指令以及 ASSERT_QUIET_EVAL 常量也已删除,因为它们将不再有任何作用。

  • parse_str() 不再可以在不指定结果数组的情况下使用。

  • 已删除 string.strip_tags 过滤器。

  • strpos()strrpos()stripos()strripos()strstr()strchr()strrchr()stristr()needle 参数现在将始终被解释为字符串。以前,非字符串的 needle 被解释为 ASCII 代码点。可以使用对 chr() 的显式调用来恢复以前的行为。

  • strpos()strrpos()stripos()strripos()strstr()stristr()strrchr()needle 参数现在可以为空。

  • substr()substr_count()substr_compare()iconv_substr()length 参数现在可以是 nullnull 值的行为方式就像没有提供 length 参数一样,因此将返回字符串的其余部分而不是空字符串。

  • array_splice()length 参数现在可以是 nullnull 值的行为与省略参数相同,从而删除从 offset 到数组末尾的所有内容。

  • vsprintf()vfprintf()vprintf()args 参数现在必须是数组。以前接受任何类型。

  • 不再支持 password_hash()'salt' 选项。如果使用 'salt' 选项,则会生成警告,提供的 salt 将被忽略,并改为使用生成的 salt。

  • quotemeta() 函数现在将在传递空字符串时返回空字符串。以前返回 false

  • 已删除以下函数

  • 已删除 FILTER_SANITIZE_MAGIC_QUOTES

  • 不再支持以相反顺序 ($pieces, $glue) 调用 implode() 的参数。

  • parse_url() 现在将区分不存在和空的查询和片段

    • http://example.com/foo → query = null, fragment = null
    • http://example.com/foo? → query = "", fragment = null
    • http://example.com/foo# → query = null, fragment = ""
    • http://example.com/foo?# → query = "", fragment = ""
    以前所有情况都导致 query 和 fragment 为 null

  • var_dump()debug_zval_dump() 现在将使用 serialize_precision 而不是 precision 打印浮点数。在默认配置中,这意味着这些调试函数现在将以完全精度打印浮点数。

  • 如果 __sleep() 返回的数组包含不存在的属性,则这些属性现在会被静默忽略。以前,此类属性将被序列化,就像它们的值为 null 一样。

  • 启动时的默认语言环境现在始终为 "C"。默认情况下,不会从环境继承任何语言环境。以前,LC_ALL 设置为 "C",而 LC_CTYPE 从环境继承。但是,某些函数在没有显式 setlocale() 调用时,不会尊重继承的语言环境。如果应将语言环境组件从默认值更改,则现在始终需要显式 setlocale() 调用。

  • 已删除 crypt() 中弃用的 DES 回退。如果将未知的 salt 格式传递给 crypt(),则该函数将失败并显示 *0,而不是回退到弱 DES 哈希。

  • 指定 SHA256/SHA512 crypt() 的超出范围的轮数现在将失败并显示 *0,而不是限制到最接近的限制。这与 glibc 行为匹配。

  • 如果数组包含比较结果相等的元素,则排序函数的结果可能已更改。

  • 任何接受回调且未明确指定接受按引用传递的参数的函数,现在如果使用具有引用参数的回调,都会发出警告。例如,包括 array_filter()array_reduce()。以前大多数(但不是全部)函数已经是这种情况。

  • HTTP 流包装器(如 file_get_contents() 等函数使用的)现在默认广告宣传 HTTP/1.1 而不是 HTTP/1.0。这不会改变客户端的行为,但可能会导致服务器以不同的方式响应。要保留旧的行为,请设置 'protocol_version' 流上下文选项,例如:

    <?php
    $ctx
    = stream_context_create(['http' => ['protocol_version' => '1.0']]);
    echo
    file_get_contents('http://example.org', false, $ctx);
    ?>

  • 不再支持在不使用显式 salt 的情况下调用 crypt()。如果您想使用自动生成的 salt 生成强哈希,请改用 password_hash()

  • substr()mb_substr()iconv_substr()grapheme_substr() 现在一致地将越界偏移量限制在字符串边界内。以前,在某些情况下会返回 false 而不是空字符串。

  • 在 Windows 上,使用 shell 的程序执行函数(proc_open()exec()popen() 等)现在一致地执行 %comspec% /s /c "$commandline",这与执行 $commandline(不带额外引号)的效果相同。

Sysvsem

  • sem_get()auto_release 参数已更改为接受布尔值而不是整数。

Tidy

Tokenizer

XMLReader

XMLReader::open()XMLReader::XML() 现在是静态方法。它们仍然可以作为实例方法调用,但继承类需要将它们声明为静态方法,如果它们覆盖了这些方法。

XML-RPC

XML-RPC 扩展已移至 PECL,不再是 PHP 发行版的一部分。

Zip

ZipArchive::OPSYS_Z_CPM 已被移除(此名称为拼写错误)。请改用 ZipArchive::OPSYS_CPM

Zlib

Windows PHP 测试包

测试运行程序已从 run-test.php 重命名为 run-tests.php,以与其在 php-src 中的名称匹配。

添加注释

用户贡献的注释 2 条注释

20
retry, abort, fail
2 年前
上面提到的字符串到整数比较的更改(例如 '' == 0 现在等于 false)会带来一些其他的不良后果

$a = '';

// php 8

if ( $a < 0 ) echo 'true'; // 输出 true
if ( $a < -1) echo 'true'; // 输出 true
if ( $a < -100 ) echo 'true'; // 输出 true

// php 7

if ( $a < 0 ) echo 'true'; // 无输出
if ( $a < -1) echo 'true'; // 无输出
if ( $a < -100 ) echo 'true'; // 无输出

因此,在您可能拥有 Web 表单输入并期望空值等于 0 的情况下,不仅要注意 == 0、!= 0 和 <= 0 比较,还要注意所有 < 或 <= 与负整数的比较。
0
aphpguy at galaxy dot za dot net
1 年前
如果您有旧项目由于宽松比较问题而导致 PHP7 到 8 迁移中断

即 if ($a == 0) 在 PHP 7 和 PHP 8 之间具有不同的行为
(对于 $a = "" 或 $a = "123foo" 和顶部列出的其他情况)

在旧代码中替换

if ($a == 0) { .. }



if (cmp_eq($a, $b)) { .. }

已针对各种场景进行测试,甚至针对数组、布尔值、文件句柄、管道句柄、对象、标量和数字。

因此旧代码仍然像以前一样工作。
然后 PHP8.x 和旧版 PHP(最高版本 7.x)将对宽松比较给出完全相同的布尔 true 或 false 输出。

function cmp_eq($a, $b) {
// 如果 $a 和 $b 都是字符串类型,则将它们作为字符串进行比较
if (is_string($a) && is_string($b)) { return $a == $b; } // 可能不是 ===,因为 php 说 '42' 等于 '042' 为 true,但 '42' === '042' 为 false。

// 如果 $a 和 $b 都是数字字符串,则将它们作为数字进行比较
if (is_numeric($a) && is_numeric($b)) { return $a == $b; }

// 如果 $a 是空字符串且 $b 是 0,反之亦然,则返回 true
if (($a === '' && $b === 0) || ($a === 0 && $b === '')) { return true; }

// 如果 $a 是非数字字符串且 $b 是 0,反之亦然,则返回 true
if ((is_string($a) && ($a !== '') && ($b === 0)) || (($a === 0) && is_string($b) && ($b !== ''))) {
return true;
}
// 特殊情况 '123abc' == 123 .. php 7 将 123abc 转换为 123,然后 123 == 123 结果为 true。让我们模仿它。
if ((is_string($a) && ($a !== '') && (is_numeric($b)) && ((bool)$b))) {
$number = filter_var($a, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION); //"-234.56xyz"
return $number == $b;
}
if (is_numeric($a) && ((bool)$a) && is_string($b) && ($b !== '')) {
$number = filter_var($b, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION); //"-234.56xyz"
return $a == $number;
}

// 如果 $a 是数字且 $b 是非数字字符串,则将 $a 转换为字符串并进行比较
if (is_numeric($a) && is_string($b)) { return strval($a) == $b; }

// 如果 $b 是数字且 $a 是非数字字符串,则将 $b 转换为字符串并进行比较
if (is_string($a) && is_numeric($b)) { return $a == strval($b); }

// 如果 $a 和 $b 都是非数字字符串,则直接比较它们,如果它们相同,则应返回 true
return $a == $b;
} // 函数 cmp_eq 结束

注意:更好的方法是将代码移植到 PHP 8,使用严格的变量类型,并利用 === 和 !== 运算符。

但在某些情况下,需要快速修补大量旧代码,这可能会有所帮助。
To Top