32、PHP 8.4 方法或功能修改 - exit,die 从语言结构更改为函数
在 PHP 8.4 中,exit 和 die 这两个语句从 语言结构(language constructs)更改为 函数(functions)。这个变化看似微小,但实际上对 PHP 编程方式和语法解析带来了一定的影响,尤其是涉及到 PHP 内部如何处理 exit 和 die 时的工作方式。
exit 和 die 的历史
在 PHP 中,exit 和 die 最初是作为 语言结构 引入的,而不是作为普通的函数。作为语言结构,它们的行为与常规函数有所不同,主要体现在以下几个方面:
- 语言结构:不像函数那样需要括号,也可以省略括号来传递参数。
 - 
执行时机:
exit和die会在执行到语句时立即停止脚本的运行,并且可以选择性地输出退出状态码或消息。 
为什么做这个改变
将 exit 和 die 作为普通函数处理,PHP 可以统一语言结构和函数的差异,使得语法更加一致。这种方式有以下优点:
- 
统一性:PHP 8.4 之前,
exit和die与其他函数有明显区别。这会让开发者在学习和使用时产生混淆。将它们转为普通函数,有助于消除这种差异,使得语法更统一。 - 简洁性:现在,所有的函数调用都要求使用括号,即使没有参数。这对于代码的规范化、可读性和一致性是有帮助的,避免了对 PHP 语言结构的特殊处理。
 - 
维护和扩展性:将 
exit和die转为函数后,PHP 引擎能够更容易地对其进行优化和扩展。 
函数 exit 和 die
exit; // 允许
exit(); // 允许
exit(1); // 允许
exit("Fatal error, exiting"); // 允许
在 PHP 8.4 之前,可选的 “parameter” 接受 string 或 int 值,但它不遵循相同类型的杂耍或严格的类型行为。
declare(strict_types=1);
exit([]);
Warning: Array to string conversion in ... on line ...
Array
在 PHP 8.4 中,exit 和 die 被声明为 PHP 函数。它们具有特殊的处理方式,允许在不带括号的情况下调用它们,以确保与较旧的 PHP 应用程序向后兼容。
函数签名
function exit(string|int $status = 0): never {}
function die(string|int $status = 0): never {}
函数 die 等同于 exit 函数。这两个函数都在全局命名空间中声明。它们都有 return type never。
不能使用 disable_functions INI 指令禁用 exit 和 die
不能使用 disable_functions 指令禁用新的 exit 和 die 函数。这样做会发出 PHP 警告,并且函数保持启用状态:
[PHP]
disable_functions=exit,die
Warning: Cannot disable function exit()
Warning: Cannot disable function die()
未更改:exit 和 die 不能用于函数名、类名、常量名和 goto 标签
exit 和 die 不能用作函数名或常量名,即使在命名空间内部也是如此。此外,exit 和 die 也不能用作 goto 语句的标签。
以下所有代码片段在所有 PHP 版本(包括 PHP 8.4 及更高版本)中都会导致语法错误。
function exit() {}
namespace Test;
function exit() {}
分析错误:语法错误,意外的标记“exit”,应为“(“
const die = 42;
const EXIT = 42;
Parse error: syntax error, unexpected token "exit", expecting identifier
仍然可以使用 define 函数来声明名为 exit 和 die 的常量。然而,需要注意的是,尝试使用这些常量时会调用 die 或 exit 函数,而不是获取常量的值。
exit:
echo "Called";
Parse error: syntax error, unexpected token ":"
未更改:exit 和 die 可以用作枚举成员、类常量、方法和属性
exit 和 die 可以用作类常量名、类方法名和属性名。以下代码片段在所有 PHP 版本(包括 PHP 8.4 及更高版本)中都是有效的。此外,由于枚举(Enums)在内部扩展了 PHP 的类结构,exit 和 die 也可以作为有效的枚举成员名。
<?php
    
class Test {
    public int $exit = 442;
    public int $die = 116;
    public const int exit = 42;
    public const int die = 16;
    public function exit() {
        return self::exit;
    }
    public function die() {
        return self::die;
    }
}
$c = new Test();
echo $c->exit(); // 42
echo $c->die(); // 16
echo $c->exit; // 442
echo $c->die; // 116
echo Test::exit; // 42
echo Test::die; // 16
类型处理更改
因为 exit 和 die 是 PHP 8.4 和更高版本中的 PHP 函数,它们现在遵循其他 PHP 函数遵循的标准类型处理。
参数数量错误(ArgumentCountError)在额外参数上的变化
之前,在 exit 或 die 函数中传递多个参数会导致语法错误。而在 PHP 8.4 中,这种情况会引发 ArgumentCountError 异常。
exit(255, "foobar");
ArgumentCountError: exit() expects at most 1 argument, 2 given
PHP 8.4 中的标准类型转换(Type-Juggling)行为变化
与 PHP 中其他函数一样,exit 和 die 现在也遵循相同的类型转换(类型强制)规则。这一变化影响了向后兼容性,但在一定程度上可以认为是朝着更正确的方向发展。
主要变化:
- 
exit(true)之前的行为:- 在 PHP 8.4 之前,
exit(true)会被自动转换为exit("1"),这会打印1到标准输出(STDOUT),并且退出代码为0(表示没有错误)。 - 在 PHP 8.4 及之后的版本中,
exit(true)现在会被解释为exit(1),这会导致退出代码为1(表示有错误)。 
 - 在 PHP 8.4 之前,
 - 
传递浮点值的行为:
- 传递浮点值时,PHP 会触发一个弃用警告:"Implicit conversion from float ... to int loses precision"(隐式转换从浮动数到整数时会丢失精度)。这意味着如果你传递一个浮点数,PHP 会隐式地将其转换为整数,但可能会丢失小数部分。
 
 - 
传递 
null的行为:- 传递 
null会触发一个弃用警告,因为exit和die只接受string或int类型参数。传递null会被认为是不合适的参数类型,PHP 会在以后的版本中警告这种做法。 
 - 传递 
 
启用 strict_types 时的 TypeError 异常
当 strict_types=1 生效时,传递任何非 string 或 int 类型的值都会导致抛出 TypeError 异常。
declare(strict_types=1);
exit(null);
TypeError: exit(): Argument #1 ($status) must be of type string|int, null given
exit 和 die 现在可以作为可调用函数
由于 exit 和 die 是 PHP 函数,它们现在可以作为标准的 PHP 可调用函数使用。
在 PHP 8.3 及更早版本中,以下代码片段会失败,因为在这些版本中,exit 并没有被声明为一个可调用函数。
$callable = 'exit';
$status = "My success message";
$callable($status);  // 这种用法在 PHP 8.3 及更早版本中会失败
$callable = exit(...);
$status = "My success message";
$callable($status);  // 这种用法也会失败
向后兼容性影响
exit 和 die 现在是 PHP 函数。然而,这一变化与旧版 PHP 保持了较好的向后兼容性,因为 exit 和 die 仍然不允许作为类名、常量名和函数名使用,但仍然允许作为类常量、属性和方法名使用。
将 exit 和 die 作为可调用函数的功能仅在 PHP 8.4 及更高版本中得到支持。
由于在旧版本的 PHP 中,exit 和 die 是语言结构(language constructs),因此无法(也不需要)为它们提供 polyfill(即填补或模拟该功能的解决方案)。