编写符合惯用法的 PHP 8 类:属性提升、readonly、命名构造函数以及不可变值对象。
在 Kodokon 中打开本课你写类已经很久了,但 PHP 8 让好几个旧习惯变得过时。其中最显眼的一个:构造函数中的属性提升。在构造函数参数前加上一个可见性关键字(private、protected、public),就能一步到位地创建该属性并为它赋值。旧写法中那三段重复的代码 - 声明、参数、赋值 - 就此合而为一。
<?php
declare(strict_types=1);
class LegacyProduct
{
private string $name;
private int $priceCents;
public function __construct(
string $name,
int $priceCents,
) {
$this->name = $name;
$this->priceCents = $priceCents;
}
}
final class Product
{
public function __construct(
private string $name,
private int $priceCents,
) {
}
public function label(): string
{
return sprintf(
'%s (%.2f EUR)',
$this->name,
$this->priceCents / 100,
);
}
}
$product = new Product('Keyboard', 4990);
echo $product->label();可见性仍然是你实现封装的首选工具:默认用 private,只有当子类需要访问时才用 protected,public 则作为最后的选择。自 PHP 8.1 起,readonly 提供了更强的保证:该属性只能被写入一次,而且只能在声明它的类的作用域内写入。与属性提升结合,只需几行代码就能造出不可变值对象:任何"修改"都会返回一个新实例,从而消除共享的副作用。
<?php
declare(strict_types=1);
final class Money
{
public function __construct(
public readonly int $amount,
public readonly string $currency,
) {
}
public function add(Money $other): self
{
if ($other->currency !== $this->currency) {
throw new InvalidArgumentException(
'Currency mismatch'
);
}
return new self(
$this->amount + $other->amount,
$this->currency,
);
}
}
$total = (new Money(500, 'EUR'))
->add(new Money(250, 'EUR'));
echo $total->amount;在方法这一侧,有两种模式在资深开发者中占据主导。命名构造函数:一个 private 构造函数搭配静态工厂方法,让每个意图都清晰明确,并禁止不一致的实例化。流式接口:返回 $this(可变)或 new self(...)(不可变),让你能够链式调用。其中的取舍众所周知:不可变版本更安全,但每次调用都会分配一个对象 - 这在几乎所有场景下都可忽略不计,只有在热循环中才可测量。
<?php
declare(strict_types=1);
final class User
{
private function __construct(
public readonly string $email,
public readonly string $role,
) {
}
public static function admin(string $email): self
{
return new self($email, 'admin');
}
public static function member(string $email): self
{
return new self($email, 'member');
}
}
$user = User::admin('lea@example.com');
echo $user->role;readonly 属性什么时候可以被写入?