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

PHP8.3 / PHP8.2 / PHP8.1 / PHP8.0 的选择

2023年11月23日发布。

2023/07/18,PHP8.3 版本进入了特性冻结阶段。
这意味着涉及语言功能的新增和修改已经截止。
在接下来的时间里,我们将不断进行调试以提升完整性,
预计将于2023/11/23发布 PHP8.3.0 版本。

让我们来看一下将在PHP 8.3中实施的RFC。

RFC可以原生地在中文中解释为”请求评论”。

标记覆盖的方法(#[\Override])

以22票赞成、1票反对的结果被接受。

这是一个Override属性。

[Translation: This is an Override attribute.]

class C1 {
    protected function foo(): void {}
}

class C2 extends C1 {
    #[\Override]
    public function foo(): void {} // OK
}

class C3 extends C1 {
    #[\Override]
    public function bar(): void {} // NG
}

C3因为编译错误,可以发现实现错误或者在父级发生变更时察觉到。

类型化的类常量

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

可以为类常量指定类型。

enum E {
    const string TEST = "Test1";
}

trait T {
    const string TEST = E::TEST;
}

interface I {
    const string TEST = E::TEST;
}

class C implements I{
    use T;
    const string TEST = E::TEST;
}

很容易理解!

mb_str_pad – mb_str_pad是一个可以在字符串两端添加指定字符来填充字符串的函数。

以15比1的赞成投票通过。

这是多字节版本的str_pad函数。

$str = 'あ';
var_dump(str_pad($str, 2, 'い')); // あ
var_dump(mb_str_pad($str, 2, 'い')); // あい

如果不小心在日语中使用str_pad函数,会导致出现奇怪的符号或无法完成填充操作,但从现在开始,它将按照我的意愿进行填充。

动态类常量获取

以15票赞成、4票反对通过。

可以从字符串中获取类常量。

$foo->$bar;    // OK
Foo::${$bar};  // OK
Foo::{$bar}(); // OK

Foo::{$bar};   // PHP8.3以降OK

其他项目通常都可以通过字符串指定键,但是奇怪的是类常量却不能这样做。
将来类常量也将能够使用这种写法。

另外,随着这项功能的引入,ENUM的使用便利性大幅提升。

enum Suit:string{
    case Hearts = 'ハート';
    case Diamonds = 'ダイヤ';
    case Clubs = 'クラブ';
    case Spades = 'スペード';
}

echo Suit::{$_REQUEST['suit']}?->value; // ハート

方便。

任意的静态变量初始化器

以25票赞成,0票反对通过。

可以将变量和函数放入静态变量中。

function foo(int|null $start){
    static $i = $start; // PHP8.3以降OK
    echo $i++;
}

foo(5); // 5
foo();  // 6

过去,静态变量的初始化只能使用固定值,但现在可以设置变量和函数。
值得一提的是,并没有特别的技术原因或故意只能设置固定值,只是似乎从以前开始就这样。

只读修正

以26票赞成、0票反对的结果被通过。

现在可以在克隆只读属性时进行重新初始化。

class Foo {
    // コンストラクタ
    public function __construct(
        public readonly DateTime $bar,
        public readonly DateTime $baz
    ) {}

    // clone
    public function __clone()
    {
        $this->bar = clone $this->bar; // OK
        $this->cloneBaz();
    }
    private function cloneBaz()
    {
        unset($this->baz); // __cloneから呼ばれている場合はOK
    }
}

$foo = new Foo(new DateTime(), new DateTime());
$foo2 = clone $foo;

在魔术方法__clone()中,可使只读属性仅能更改一次。

从全体投票结果来看,虽然我对这个的好处不太明白,但是肯定有一些重要的利益。

PDO驱动程序特定的子类

以23票赞成、0票反对被接受。

将PDO添加了每个驱动程序的子类。

// MySQL
$pdoMySQL = new PdoMySql($dsn);
$pdoMySQL->getWarningCount(); // MySQL専用機能

// Postgres
$pdoPgsql = new PdoPgsql($dsn);
$pdoMySQL->getPid(); // Postgres専用機能

// ファクトリーメソッド
$pdo = PDO::connect($dsn); // DSNがMysQLならPdoMySqlに、PostgresならPdoPgsqlになる

// PDOはこれまでと同じ
$pdo = new PDO($dsn);

我在想为什么以前没有出现过这个。

使用数据库特有的功能。
虽然移植性会下降,但毕竟很少进行数据库移植,便利性更胜一筹。

当然,您仍然可以继续使用以前的new PDO(),在这种情况下,一切都与以前没有任何变化。

随机器添加

增加了乱数功能。

$randomizer = new \Random\Randomizer();

// ランダムな文字列を生成する
$randomizer->getBytesFromString('abcdefghijklmnopqrstuvwxyz0123456789', 16);

// aが75%、bが25%のランダム文字列を生成する
$randomizer->getBytesFromString('aaab', 16);

// (-180, 180] の乱数を生成する
$randomizer->getFloat(-180, 180, \Random\IntervalBoundary::OpenClosed);

// [0, 1)の乱数を生成する
$randomizer->nextFloat();
  // $randomizer->getFloat(0, 1, \Random\IntervalBoundary::ClosedOpen)と同じ

现在我们可以轻松地创建密码学上安全的随机字符串和范围指定的随机数。还可以指定开区间和闭区间。这非常方便易用。

JSON验证函数()

通过18票赞成、1票反对的结果予以接受。

将会添加一个函数来判断JSON是否合法。

var_dump(json_validate('{ "": "": "" } }')); // false

一些人认为只需对json_decode进行一次尝试就能知道是否正确,所以这并不必要;但是通过细致的劝说和提供各种用例,最终成功地被采纳了。

为range()函数定义适当的语义。

全票赞成,无反对,通过受理。

修改range()函数并增强其处理能力。

当以不常见的参数方式传递给范围时,可能会导致不自然的行为。为了尽可能使这种情况下的行为更加自然,并且对于非法参数进行错误提示。

var_dump(range(0, 3, -1));
// PHP8.2まで [0, 1, 2, 3]
// PHP8.3以降 ValueError

var_dump(range('9', 'A'));
// PHP8.2まで [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
// PHP8.3以降 [9, :, ;, <, =, >, ?, @, A]

var_dump(range('', 0));
// PHP8.2まで [0]
// PHP8.3以降 [0]  Warning: range(): Argument #1 ($start) must not be empty, casted to 0

对我来说,迄今为止忽略了某些情况下的$step的-1这一事实真的太令人震惊了。

更合适的日期/时间例外情况

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

这是对DateTime错误处理功能的增强。

$i = DateInterval::createFromDateString('last Monday of');
$sub = (new DateTimeImmutable())->sub($i);

// PHP8.2  Warning: DateTimeImmutable::sub(): Only non-special relative time specifications are supported for subtraction
// PHP8.3  Fatal error: DateMalformedIntervalStringException 

引入多种DateException和DateError,可以进行详细的验证。

提高unserialize()错误处理的能力

unserialize的错误处理功能已经加强。
有三个提议,其中两个被接受了。

unserialize('foo');
// PHP8.2  E_NOTICE
// PHP8.3  E_WARNING
// PHP9.0  UnserializationFailedException

unserialize('O:19:"SplDoublyLinkedList":3:{i:0;i:0;i:1;N;i:2;a:0:{}}');
// PHP8.2  UnexpectedValueException
// PHP9.0  UnserializationFailedException

在进行反序列化时,由于之前发生了各种错误和异常,比如E_NOTICE、E_WARNING和UnexpectedValueException,导致处理变得困难。但从现在开始,我们可以通过捕获UnserializationFailedException来统一处理。

另外,UnserializationFailedException的引入已在PHP9中实现。
在PHP8.3中似乎正在进行为此做准备。

使unserialize()输出一个关于尾部数据的警告

全票赞成,无票反对,被接受。

如果在unserialize()的末尾出现了不必要的字符串,将发出警告。

var_dump(unserialize('i:5;i:6;'));

// PHP8.2  エラー出ない
// PHP8.3  unserialize(): Extra data starting at offset 4 of 8 bytes

i:5; i:6;的解析在i:5;结束,并且值变为int(5)。
随后的i:6;是完全无用的字符,以前被忽略,但现在将产生E_WARNING警告。

听说有与CVE-2023-21036相同的漏洞问题。

三元数组_(求和|乘积)()

27人赞成,0人反对,通过。

提高array_sum和array_product的功能。

echo gmp_init("123") + 456;
// PHP8.2  579
// PHP8.3  579

echo array_sum([gmp_init("123"), 456]);
// PHP8.2  456 ←
// PHP8.3  579

array_sumやarray_productは、これまで一部の動作が算術演算子と異なっていました。
PHP8.3以降は同じ動作になります。

更理智的增减操作符之路

賛成25反対0で受理。

改进增量和减量运算符。


$i = 10;
var_dump($i-1); // 9
var_dump(--$i); // 9

$n = null;
var_dump($n-1); // -1
var_dump(--$n); // null

$s = 'foo';
var_dump($s-1); // Fatal error
var_dump(--$s); // "foo"
var_dump($s+1); // Fatal error
var_dump(++$s); // "fop"

intとfloat以外の型へのインクリメント・デクリメントは、算術演算子と異なる結果になることがあります。
また、時に想像と斜め上の挙動をすることがあります。
文字列にインクリメントすると次の文字になるけどデクリメントは効かないなんてところは特に意味がわかりませんね(これは移植元のPerlからそういう仕様)。

我们将对此进行改进,确保++$i的行为与$i+1相同。
对于除了int和float以外的类型,自增和自减操作将在PHP 8.3中被弃用,并在PHP 9中停止工作。

また文字列には、インクリメント・デクリメントを行う関数str_increment()・str_decrement()を別途追加します。

$s = 'foo';
var_dump(str_decrement($s)); // "fon"
var_dump(str_increment($s)); // "fop"

这是一个很好的进步,现在可以做字符串递减了,这是以前无法实现的。

在SQLite3扩展中,使用异常为默认选项。

以21票赞成,0票反对通过。

SQLite3拡張モジュールは、デフォルトでは例外を使わないようになっています。
しかしPDOもMySQLiもとっくにデフォルト例外に移行しているため、SQLiteも例外をデフォルトにすることにします。

var_dump($sqlite3->enableExceptions());
// PHP8.2 false
// PHP8.3 false
// PHP9.0 true
// PHP10.0 Fatal error Undefined method

$sqlite3->enableExceptions(false);
// PHP8.2 OK
// PHP8.3 Deprecated
// PHP9.0 Fatal error

まずPHP8.3ではenableExceptions(false)するとE_DEPRECATEDが発生します。
ただし初期値はfalseのまま変わりません。

从PHP9.0版本开始,将无法使用enableExceptions(false)来禁用异常,而默认值也将变为true。

在PHP10中,最后enableExceptions()方法将不再存在。

Deprecate remains of string evaluated code assertions

以24票赞成、0票反对的结果被接受。

assert_options()関数、および関連する定数を削除します。

assert_options(ASSERT_BAIL, 1);

// PHP8.2 エラー出ない
// PHP8.3 Deprecated

在过去,如果在assert()函数的参数$assertion中传递字符串,则会被eval()进行评估。但是,这种用法在PHP7.2中被标记为不推荐,并在PHP8.0中被删除。
然而,尽管该功能已被删除,但一些相关的ini设置仍然保留。

另外,无论是assert_options()还是ini_set(),在文档中都明确写明要使用ini_set(),但现已正式废弃。

废弃拥有过载签名的函数 – de

停用标准函数中的函数签名重载。
顺便一提,此处的重载不是指PHP中的重载,而是指一般意义上的重载。
为什么要用同一个词汇呢。

作为例子,DatePeriod::__construct()有三种不同的签名。

class DatePeriod
{
    public function __construct(DateTimeInterface $start, DateInterval $interval, DateTimeInterface $end, int $options = 0) {}
    public function __construct(DateTimeInterface $start, DateInterval $interval, int $recurrences, int $options = 0) {}
    public function __construct(string $isostr, int $options = 0) {}
}

在這些中,只保留最上方的簽名。
其他的簽名將在PHP8.3中被標示為已廢棄,並在PHP9中被刪除。

class DatePeriod
{
    public function __construct(DateTimeInterface $start, DateInterval $interval, DateTimeInterface|int $end, int $options = 0) {}
    public static function createFromISO8601String(string $specification, int $options = 0): static {}
}

将来可能仍然需要的删除签名将提供替代函数或方法。

还有很多关于其他函数和方法的提案,每个提案都会被接受或拒绝。

PHP 8.3的降级情况

由于历史原因,PHP存在许多过时的功能、存在安全问题的功能以及不知道为什么会有这种功能的功能等等。
因此,在每个版本的PHP升级中,我们会删除或保留这些负面遗留。
在PHP 8.3中,以下4个功能将被标记为不推荐,并在PHP 9中被删除。

mb_strimwidth('abcde', 0, -1);
// PHP8.2 abcd
// PHP8.3 abcd Deprecated
// PHP9.0 Fatal error

var_dump(NumberFormatter::TYPE_CURRENCY);
// PHP8.2 値は取れる エラー出ない
// PHP8.3 値は取れる Deprecated
// PHP9.0 Fatal error Undefined constant

ldap_connect($host, $port);
// PHP8.2 エラー出ない
// PHP8.3 Deprecated
// PHP9.0 Fatal error

mb_strimwidth()的参数$width将不能为负数。
当给定负数作为宽度时,从末尾计数的行为是字符串函数的共同特点,但是mb_strimwidth()本身用于『将字符串裁剪到指定长度』的目的,与其目的不符。
如果想要进行依赖于原始字符串长度的裁剪操作,可以使用mb_substr()等方法。

将被删除的常量NumberFormatter::TYPE_CURRENCY。
尽管定义了该常量,但其并未实现。

“将删除常量MT_RAND_PHP。”

曾经 PHP 的随机数存在着破损的 Mersenne Twister 问题。这个问题在 PHP 7.1 版本中得到修复,但为了兼容等原因,引入了常量 MT_RAND_PHP 作为有意再现破损随机数的手段。

然后在PHP8中发现了一个事实上已经损坏的Mersenne Twister,但是没有人提出任何抱怨,似乎已经认为它已经很兼容了。

顺便提一下,也有人提出了删除未损坏的梅森旋转机提议,但这个提议被明确否决了。虽然如此,梅森旋转机在密码学上并不安全,所以现在不应该再使用它。

请使用ldap_connect函数的单参数签名,将会删除其中的两个参数。

思考

PHP8.3は既にα版が提供されています。
Linux / Windows
締め切り間際で受理された機能などはまだ入ってないものもありますが、既に一部の機能は試すことができるので、遊んでみてはどうしょうか。

Nikitaの遺産が残っていたPHP8.2と異なり、PHP8.3は彼の手はほとんど入っていません。
しかしながらも着実に様々な改善、改良がなされており、次世代にしっかり引き継がれているのがわかりますね。

希望未来PHP仍然保持易学且简洁的语言特点。

bannerAds