【PHP8.2】PHP8.2的新功能

PHP8.3 / PHP8.2 / PHP8.1 / PHP8.0

PHP8.3 / PHP8.2 / PHP8.1 / PHP8.0可以进行重述嘛?

2022年7月19日,PHP8.2已经进入功能冻结阶段。这意味着与语言功能相关的新增和修改已经截止。在接下来的时间里,我们将不断进行调试,提高PHP8.2.0版本的完成度,并计划于2022年11月24日发布。

让我们来看看在PHP8.2中将会实现的RFC。

RFC可以被概括为一种互联网标准,旨在规范网络通信和数据传输的操作、协议和格式。

Disjunctive Normal Form Types

以25票赞成、1票反对通过。

這是標準選擇形式。
可以同時使用 UNION 型和交叉型。
您可以盡情在類型拼圖中玩耍。

// A型、もしくはB型かつC型、もしくはint
function hoge( A | (B & C) | int $param){}

允许将null和false作为独立的类型使用

一致同意,共38人贊成,無人反對,被接受。

是null类型和false类型。

只能将null赋给null类型,只能将false赋给false类型。

class Nil {
    public null $nil = null;

    public function foo(false $falsy): false{
        return false;
    }
}

最初引入UNION类型时,引入了null伪类型和false伪类型。这些是作为UNION类型的一部分而存在的特殊类型,不能单独使用。

当時としてはそうする合理的理由があったわけですが、その後交差型などで型システムが拡張された結果、単独でも使えた方がいいだろうということになり疑似型から通常の型に昇格しました。

添加TrueType字体

全体一致通过,赞成33,反对0。

是真的。

在中文中,只能使用true来代替true型。

function foo(true $true): true{
    return true;
}

由于引入了false类型,如果没有true类型,就会变得不完整。
此外,对于静态分析工具也非常方便,并且因为存在实际返回true的函数等其他优点,决定进行引入。

由于引入了false型,这个时候如果没有true型的话就显得不完整了。
此外,对于静态分析工具来说也很方便,而且存在只返回true的函数等其他优点,所以决定引入。

只读类

28人赞成,7人反对,通过。

这是一个只读的类。

readonly class Test {
    public int $foo;
}

$t = new Test();
$t->foo = 1; // OK
$t->foo = 2; // NG 上書きは不可
$t->bar = 3; // NG 動的プロパティは禁止

只读类的特点是静态属性无法设置、动态属性无法使用,类型指定是必需的,并且一旦设置了属性就无法重新赋值。尽管功能受到了极大限制,但可以实现严格的运用。

随机扩展 5.x

以全体一致的方式通过,得到21票赞成,0票反对。

PHP的随机数功能过去存在着各种问题。例如,著名的梅森旋转器实现错误,或者根据范围可能只产生奇数。

这个问题在很大程度上已经得到了个别的解决,但更为严重的问题是存在全局状态的影响。
由于这个原因,导致了外部库中的随机数出现了混乱,以及无法再现情况等问题。
老实说,我从来没有写过这样的代码,所以不太清楚。
RFC中也提到了许多其他问题。

因此,我们将引入一个名为Random\Randomizer的类来解决这个问题。

$engine = new Random\Engine\Secure();
$randomizer = new Random\Randomizer($engine);

$randomizer->getInt(1, 10);           // mt_rand
$randomizer->getBytes(10);            // random_bytes
$randomizer->shuffleArray([1, 2, 3]); // shuffle
$randomizer->shuffleString('abc');    // str_shuffle

随机扩展改进

这是关于Random Extension的相关RFC补充,包括解决投票开始后发现的问题以及添加缺失的功能等。

$randomizer->pickArrayKeys([1, 2, 3], 1); // array_rand
$randomizer->shuffleBytes('abc');         // shuffleStringから改名

由于在使用shuffleString进行多字节字符混洗时会导致错误,所以需要将其更名为适当的shuffleBytes,并实现array_rand函数。原始RFC中包括的Random\Engine\CombinedLCG已被删除,因为其作为随机数的质量较低。

在回溯中删除参数

以24票赞成、1票反对通过。

引数をスタックトレースに出力しないようにするアトリビュート#[\SensitiveParameter]です。

function test(
    $foo = null,
    #[\SensitiveParameter]
    $bar = null,
    $baz = null
) {
    throw new \Exception('Error');
}
 
test(
    bar: 'bar',
    baz: 'baz',
);
 
/*  エラーメッセージは以下のようになる

	Fatal error: Uncaught Exception: Error in test.php:8
	Stack trace:
	#0 test.php(13): test(NULL, Object(SensitiveParameterValue), 'baz')
	#1 {main}
	  thrown in test.php on line 8
*/

設置了屬性#[\SensitiveParameter]的參數$bar的值將不會輸出到異常堆棧追蹤或var_dump等地方。

为什么需要这样的东西呢?例如,PDO::__construct()在失败时会抛出异常,但如果display_errors打开的话,数据库密码就会被完全显示出来。
如果这是由于疏忽设置错误等原因在生产环境中发生,将是一场大灾难。
还可能出现敏感信息(如信用卡号码等)在访问日志中流出的可能性。

そういうミスがあった場合でも大丈夫なように、という安全策ですね。

Deprecate dynamic properties

賛成52、反対25の僅差で受理。

禁止使用动态属性。

class Test {}

$t = new Test();
$t->foo = 1; // PHP <= 8.1: $t->fooが生える
             // PHP    8.2: E_DEPRECATE
             // PHP    9.0: Exception

在PHP8.2中,未定义的属性将会引发E_DEPRECATE,而在PHP9及以后的版本中将会抛出异常。

我在过去看过许多 PHP RFC,但这个 RFC 是我觉得迄今为止最不同于以往 PHP 思想的。

#[AllowDynamicProperties]
class Test {}

$t = new Test();
$t->foo = 1; // OK

如果要执行先前的操作,可以通过指定Attribute #[AllowDynamicProperties]来实现兼容。

部分支持的可调用物件被弃用

全體一致同意32人,無人反對,該提案獲得批准。

一个可调用语法删除的部分。

$callable = 'self::hoge';
is_callable($callable);    // OK
call_user_func($callable); // OK
$callable();               // NG ←

在过去,存在着一些矛盾的语法,比如在使用is_callable或者call_user_func时可能返回true却无法实际进行回调。

因此,将调整这些操作。它会在PHP 8.2中被标记为E_DEPRECATE,并在PHP 9中被删除。

扩大部分受支持可调用项的弃用通知范围

部分的にサポートされたコールアブルな機能の廃止についての追加情報です。

在相应的RFC中明确说明了“is_callable()和callable在不受支持之前不会产生弃用警告”,也就是说不会引发错误,但现在发现存在危险的行为。

class Foo {
    public function bar() {
        if (is_callable('static::methodName')) {
            static::methodName();
        }
    }
}

(new Foo())->bar();

在PHP8.x中,这段代码没有任何错误,但在PHP9.0中,它的行为突然发生了变化。

因此,即使是传递给is_callable等的格式不再支持,也会引发E_DEPRECATED错误。

区域无关的大小写转换

以29票贊成、1票反對通過。

像 strtolower 这样的函数,会受到地域设置的影响。

01.png

在手册中写道:“例如,默认的“C”区域设置情况下,像 A Umlaut (Ä) 这样的字符将不会被转换。”但是,如果改变区域设置,我们很难理解和掌握这种行为,并且存在风险。因此,我们将确保这些函数不受区域设置的影响。具体而言,我们将视其为 C 区域设置。

弃用${}字符串插值

以31票赞成、1票反对的结果通过。

取消部分的文本内变量展开语法。

$foo = 'value';
$bar = 'foo';

var_dump(
    "$foo",   // value
    "{$foo}", // value
    "${foo}", // value
    "${$bar}" // value
);

在这4种格式中,后两种将被废弃。
在PHP8.2中,E_DEPRECATED将被废弃,而在PHP9.0中将变为错误。

第二个和第三个看起来一样,但实际上第二个有用而第三个却无效的格式存在。

var_dump("{$foo->bar()}");        // OK
var_dump("{$foo['bar']->baz()}"); // OK

var_dump("${foo->bar()}");        // NG!
var_dump("${foo['bar']->baz()}"); // NG!

因为非常难懂,所以只能使用其中一种方式。

既然第四个可变变量与第三个重复且完全没有使用,我们将其删除。
${$bar}将来将改为{${$bar}}来表示。

嗯,我想说在字符串中使用可变变量这种事情有点复杂。

RFC中包含了很多细致的例子,使我感到困惑。

弃用和移除utf8_decode()和utf8_encode()函数

以33票赞成、2票反对的结果通过。

删除utf8_decode和utf8_encode。

这个函数的作用是将任意字符串编码为UTF-8进行解码,但实际上只能与Latin 1进行转换。
函数名称非常容易引起误解,容易造成错误的实现。

因此建议将这个函数删除,并推荐使用较难混淆的mb_convert_encoding作为替代。

MySQLi 执行查询

以24票赞成、0票反对通过。

提议向MySQLi添加新的函数mysqli_execute_query。

// これまで
$statement = $db->prepare('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)');
$statement->bind_param('sii', $name, $type1, $type2);
$statement->execute();
foreach ($statement->get_result() as $row) {
    print_r($row);
}

// 今後
foreach ($db->execute_query('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)', [$name, $type1, $type2]) as $row) {
    print_r($row);
}

在使用MySQLi进行SELECT操作时,需要进行一系列繁琐的步骤,包括调用mysqli_prepare()函数、mysqli_execute()函数和mysqli_stmt_get_result()函数。尽管只是将这些步骤简单地整合在一起,但只需这么做使用起来就变得更加轻松了。

这个功能在PDO中也没有,所以我也想在那边有。

从mysqli中移除对libmysql的支持。

一致意見,全体一致,30人赞成,0人反对,被接受。

MySQL的模块有两种存在,mysqlnd和libmysql。
建议删除libmysql。

mysqlnd是PHP5.4以后的默认选项,因为它已经包含在PHP中,所以无需单独安装,而且mysqlnd在性能上有优势,还具有一些libmysql中不存在的功能。基本上选择libmysql没有什么优点。
因此,我们决定将libmysql删除,并统一使用mysqlnd。

使 iterator_*() 这一类函数接受所有可迭代对象。

使RFC来实现可以将iterable传递给iterator_to_array和iterator_count的功能。

iterator_to_array函数只接受可遍历的参数,与其名字相反。

iterator_to_array([1, 2]); // Fatal error: Uncaught TypeError

因为非常难懂,所以将它修改为可以接受类似foreach的可迭代对象。

因为iterable可以是数组或可遍历对象,所以实质上只需要传递一个数组就可以了。

获取枚举常量表达式的属性

以24票赞成,11票反对的微弱差距通过接受。

让 ENUM 在常数表达式中可用通过使用箭头符号->。

enum E: string {
    case Foo = 'foo';
}

const C = E::Foo->name; // 8.2からOK

听说是因为Nikita说可以支持这个事情,才确定了方向性。
Nikita至今还拥有巨大的影响力。

起初似乎想要支持所有对象,而不仅仅是ENUM,但由于性能等原因,这一点被取消了。

class X {
    public $name = 'value';
}

const C = (new X())->name; // こっちはPHP8.2以降も駄目

特质中的常数

以28票赞成、12票反对通过。

可以将常量写入特征。

trait T {
    public const CONSTANT = 42;
}

class C1 {
    use T;

    public function doSomething(): void {
        // OK
        echo self::CONSTANT;
        echo static::CONSTANT;
        echo $this::CONSTANT;

        // Fatal Error
        echo T::CONSTANT;
    }
}

在被实例化的类中,可以处理trait常量的声明,就像处理trait常量的方式一样。
不能像T::CONSTANT一样直接引用trait。
此外,通过as运算符创建别名或者使用insteadof避免冲突是被禁止的,如果常量重复会导致错误。

据实施者表示,当特征被实施时,并没有特别考虑对应常数的意图。

我的感想

PHP8.1之后,与类型相关的RFC很多啊。
从PHP7的后半期开始,可以通过类型声明完成的功能越来越多,最终在这个版本的选择标准形式中,甚至能表示任何类型了。
能做到这种程度的语言并不多吧。

在除了新功能之外,還有一些不太清楚或含糊的功能被删除的情况很常见。

现在RFC的总量相比PHP 8.1和PHP 8.0大幅减少。这主要是因为Nikita的离职事件所造成的影响。实际上,之后一段时间RFC的提交和投票也受到了一些阻碍。

然而,在Nikita之后,一些接任者出现了,并且在截止日期临近之前,由他们创建的RFC也被投票讨论了很多次。虽然大部分都被否决了。

未来,他们也会不断成长,PHP的进化速度毫无疑问会再次加快。

PHP8.2已经提供了Alpha版本,并且也有Windows版本。
尽管我们介绍的一些功能尚未被包括,但如果你想尽早尝试这些新功能,不妨尝试下载一下吧。

广告
将在 10 秒后关闭
bannerAds