Пиши идиоматичные классы 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?