PHP 8.4 的正式发布计划于下周,即 2024 年 11 月 21 日发布。
在此次发布之前,一系列预发布版本(Alpha、Beta 和候选版本)允许社区测试新功能并进行最后一刻的调整。PHP 8.4 引入了多项改进,包括用于操作数组的新功能、受其他语言启发的属性钩子以及简化的语法。让我们一起回顾一下此版本中要记住的新功能。
Property Hooks
Property Hooks 是 8.4 版中引入的主要功能之一。PHP 实现的灵感来自其他语言(如 Kotlin、C#、Swift、Javascript 或 Python)中的现有实现。让我们举几个例子来说明 Property Hooks 的用途以及如何使用它们:
<?php
declare(strict_types=1);
class Foo
{
private string $id;
public function getId(): string
{
return $this->id;
}
public function setId(string $id): void
{
$this->id = $id;
}
}
从 PHP 8 开始,由于 Constructor Property Promotion
,可以通过以下方式进行简化:
<?php
class Foo
{
function __construct(public string $id) {}
}
在我们必须保留访问器 (getter/setter)
的情况下,我们会考虑包含业务逻辑的丰富模型,我们失去了更轻量级语法的优势。PHP 版本 8.4 和引入的 Property Hooks
修复了这个问题:
<?php
class Foo {
public function __construct(
public string $id {
get {
return '#' . $this->id;
}
set(string $id){
$this->id = mb_strtoupper($id);
}
},
){}
}
乍一看,语法似乎令人困惑,但就像任何语法演变一样,随着时间的推移,您会习惯它。
Property Hooks 引入的另一种可能性是能够在 property 上定义接口。
使用这个新版本的 PHP,我们可以编写以下定义:
<?php
interface HasId {
public string $id { get; set; }
}
// Les fonction fléchées peuvent être aussi utilisées pour raccourcir la syntaxe
class Foo implements HasId {
function __construct(
public string $id {
get => '#' . $this->id;
set (string $id) => $this->id = mb_strtoupper($id);
},
) {}
}
// Le contrat d’interface est également respecté sans l’utilisation des Property Hooks en déclarant publiquement la propriété
class Bar implements HasId {
function __construct(public string $id) {}
}
一个完整的案例
<?php
declare(strict_types=1);
interface HasId {
public string $id { get; set; }
}
class Foo implements HasId {
function __construct(
public string $id {
get => '#' . $this->id;
set (string $id) => $this->id = mb_strtoupper($id);
},
) {}
}
class Bar implements HasId {
function __construct(public string $id) {}
}
class Baz {
public function display(HasId $object): void
{
echo $object->id . PHP_EOL;
}
public function update(HasId $object, string $id): void{
$object->id = $id;
$this->display($object);
}
}
$foo = new Foo(id: 'FOO');
$bar = new Bar(id: 'BAR');
$baz = new Baz();
$baz->display($foo);
$baz->update($foo, 'foo');
$baz->display($bar);
$baz->update($bar, 'bar');
设置具有非对称可见性的类属性的可见性
非对称可见性 允许您根据相关操作是读取还是写入属性,对同一属性设置不同的可见性。然后,可以定义读取访问的公共可见性和写入访问的更受限的可见性(受保护或私有)。接下来的两个类是等效的,随着非对称可见性的引入,语法更加简洁。
<?php
class Foo {
function __construct(private string $id,) {
//
}
public function getId(): string {
return $this->id;
}
}
class Bar {
function __construct(
public private(set) string $id,
) {}
}
不对称可见性附带一些规则,这些规则很容易理解:
- 属性必须被类型化(来自 PHP 实现的约束)
- 只关注对象属性,静态属性不能从中受益(这也是 PHP 实现产生的约束)。
- 对于对象属性,如果该属性设置为 private(set),则不能在与当前类不同的范围内修改链接对象。但是,如果链接对象的属性被定义为 Properties,则可以对其进行修改。
- 对于数组类型属性,如果该属性已设置为 private(set),则无法在当前类的范围之外操作数组(添加元素、删除元素等)。
- set 的可见性不能比 get 的可见性更宽。
- 对于类继承或接口协定,可见性不能更严格,也可以更广泛。
在 readonly
中定义的属性(在 PHP 8.1 中引入)和 public private(set)
中定义的属性之间的差异非常小,但值得一提。上面定义的不对称可见性将具有相同的效果,只是它允许内部更改。换句话说, readonly
限制了 mutation
,并且在实例化期间还具有唯一写入的效果。
管独立于 Property Hook 运行,但这两种机制可以结合使用。此处提供了这两种功能的示例。
对惰性对象的原生支持
惰性对象 是其实际实例化将被推迟到实际需要的时间(因为它们的实例化通常很昂贵)的对象。出于性能原因,它们在 Doctrine 和 Symfony 中被大量使用。
Martin Fowler 在他的理论定义中建立了四种可能的实现。其中两个是不需要修改现有对象的实现:Ghost 和 Proxy。这些是通过在 PHP Reflection API 中添加方法保留和访问的。
在这两种情况下,都会创建一个初始化函数。对于 Ghosts,该函数将直接作用于对象。对于 Proxy,它是实例化惰性对象的函数,然后将交互反馈给真实实例。
在这两种情况下,实例化机制都是通过访问真实对象的 state 来触发的:读取或写入属性、测试属性是否具有值、克隆等。可以通过特定函数对特定属性禁用此行为,在某些情况下,可以定义或参数化,例如用于调试或序列化。
由于它是为非常有限且根据定义相当抽象的用例保留的,因此我们邀请您阅读 RFC 以发现代码示例和两种不同实现的详细功能。
不带括号的类实例化
更有趣的是,这种演变通过在实例化新对象时使括号变得多余,从而减轻了语法的负担。
<?php
// Avant et toujours valide
$o = (new Operation(0))->add(10)->multiply(2);
// Depuis PHP 8.4
$o = new Operation(0)->add(10)->multiply(2);
解析 HTML5
创建了一个新的 DOM\HTMLDocument
类来允许 HTML5 解析,为了确保与 HTML4
的向后兼容性,当前类将保持不变。这两个类保持相同的 API,因此可以以相同的方式使用。只有构造逻辑已更改,并且需要使用其中一个可用的工厂。用 C
语言编写的底层库是 Lexbor
。
新的函数
添加了四个作用于数组的新函数,它们补充了现有函数。
array_find
array_find
将返回传递给它的回调函数的第一个匹配项
<?php
$array = ['A', 'AA', 'AAA'];
$arrayWithKeys = ['A' => 1, 'AA' => 2, 'AAA' => 3];
array_find($array, static fn(string $value): bool => strlen($value) > 2);// returns AAA
array_find($array, static fn(string $value): bool => strlen($value) > 3);// returns null
array_find($arrayWithKeys, static fn(int $value, string $key): bool => $value === strlen($key)); // returns 1
array_find_key
array_find_key
的工作方式与上一个函数相同,但返回 key 而不是 value:
<?php
$array = ['A', 'AA', 'AAA'];
$arrayWithKeys = ['A' => 1, 'AA' => 2, 'AAA' => 3];
array_find_key($array, static fn(string $value): bool => strlen($value) > 2); // returns 2
array_find_key($array, static fn(string $value): bool => strlen($value) > 3); // returns null
array_find_key($arrayWithKeys, static fn(int $value, string $key): bool => $value === strlen($key)); // returns A
array_any
如果数组中至少有一个元素与回调函数匹配,array_any
将返回布尔值 true
:
<?php
$array = ['A', 'AA', 'AAA'];
$arrayWithKeys = ['A' => 1, 'AA' => 2, 'AAA' => 3];
array_any($array, static fn(string $value): bool => strlen($value) > 2)); // returns true
array_any($arrayWithKeys, static fn(int $value, string $key): bool => $value === strlen($key)); // returns true
array_all
如果数组中的所有元素都与回调函数匹配,array_all
将返回布尔值为 true
<?php
$array = ['A', 'AA', 'AAA'];
$arrayWithKeys = ['A' => 1, 'AA' => 2, 'AAA' => 3];
array_all($array, static fn(string $value): bool => strlen($value) < 4)); // returns true
array_all($arrayWithKeys, static fn(int $value, string $key): bool => $value === strlen($key)); // returns true
改进和错误修复
除其他事项外,我们保留了以下更改:
- 添加了新的多字节函数来操作字符串
mb_trim、mb_ltrim、mb_rtrim、mb_ucfirst mb_lcfirst
。 - 添加了用于从时间戳创建
DateTime
对象的新函数。 exit
和die
的元素失去了它们的地位,取而代之的是特殊功能。在不破坏向后兼容性的情况下,这允许对函数进行类似的操作,例如,更精确地键入输入参数。- 以下扩展正在从核心中移出以加入
PECL:Pspel、IMAP、OCI8
和PDO-OCI
。 - 为舍入功能添加了四种新的舍入模式
- 在 Sodium 中添加了两种新的加密算法,并升级了
OpenSSL
。 - 添加了 cURL 的新选项。
完整的更新日志:https://www.php.net/ChangeLog-8.php