Обновились на PHP 8.4 в прошлом месяце. До этого сидели на 8.2, потому что 8.3 нас не сильно впечатлил. С 8.4 всё иначе — несколько фич реально попали в продакшен и сократили количество бойлерплейта. Рассказываю, что мы внедрили, а что пропустили.
Property Hooks — конец эпохи геттеров-сеттеров
Самая значимая фича для нас. До PHP 8.4 любой класс с валидацией выглядел так:
class Price
{
private int $amount;
private string $currency;
public function __construct(int $amount, string $currency)
{
$this->setAmount($amount);
$this->setCurrency($currency);
}
public function getAmount(): int
{
return $this->amount;
}
public function setAmount(int $amount): void
{
if ($amount < 0) {
throw new \InvalidArgumentException('Цена не может быть отрицательной');
}
$this->amount = $amount;
}
public function getCurrency(): string
{
return $this->currency;
}
public function setCurrency(string $currency): void
{
if (!in_array($currency, ['RUB', 'USD', 'EUR'], true)) {
throw new \InvalidArgumentException("Неподдерживаемая валюта: $currency");
}
$this->currency = $currency;
}
}42 строки для двух полей с базовой валидацией. В нашем проекте таких value objects десятки. С property hooks в PHP 8.4 это выглядит так:
class Price
{
private set int $amount {
if ($value < 0) {
throw new \InvalidArgumentException('Цена не может быть отрицательной');
}
}
private set string $currency {
if (!in_array($value, ['RUB', 'USD', 'EUR'], true)) {
throw new \InvalidArgumentException("Неподдерживаемая валюта: $value");
}
}
public function __construct(
public readonly int $amount,
public readonly string $currency,
) {}
}19 строк вместо 42. Хуки set срабатывают при присваивании в конструкторе. Сочетание readonly с хуками — это мощная штука: значение immutable после создания, но валидация работает. Мы переписали около 30 value objects за два дня. Единственный нюанс: property hooks не работают с promoted properties в анонимных классах, но это редкий кейс.
array_find и array_find_key
Наконец-то. Сколько раз я писал array_filter + reset или array_reduce, чтобы найти один элемент в массиве. В PHP 8.4 появился array_find:
// Было
$user = null;
foreach ($users as $u) {
if ($u['role'] === 'admin') {
$user = $u;
break;
}
}
// Или так (но всегда возвращал все элементы)
$admins = array_filter($users, fn($u) => $u['role'] === 'admin');
$user = reset($admins) ?: null;
// Стало в PHP 8.4
$admin = array_find($users, fn($u) => $u['role'] === 'admin');
// С ключом
$adminKey = array_find_key($users, fn($u) => $u['role'] === 'admin');
// Практический пример: найти первый активный тариф
$activeTariff = array_find($user->getTariffs(), fn(Tariff $t) => $t->isActive());Мелочь, а код стал чище. В нашем проекте нашёл ~40 мест, где заменил array_filter + reset на array_find. Производительность лучше — не создаёт промежуточный массив.
new без скобок
Небольшая, но приятная syntactic sugar фича. В PHP 8.4 можно писать new Foo без скобок в контекстах, где они не нужны:
// Было
$collection = new ArrayCollection([new User(), new User()]);
$mapper = new UserMapper(new EntityManager());
// Стало
$collection = new ArrayCollection([new User, new User]);
$mapper = new UserMapper(new EntityManager);Это не меняет жизнь, но код визуально легче. Особенно заметно в коллекциях и фабриках, где много new. Мы не стали делать отдельный рефакторинг для этого, но все новые участки кода пишем без лишних скобок.
Асимметричная видимость свойств
Позволяет сделать свойство публичным на чтение и приватным на запись:
class Invoice
{
public private(set) string $number;
public private(set) \DateTimeImmutable $createdAt;
public private(set) InvoiceStatus $status;
public function __construct(string $number, InvoiceStatus $status)
{
$this->number = $number;
$this->createdAt = new \DateTimeImmutable();
$this->status = $status;
}
public function markAsPaid(): void
{
$this->status = InvoiceStatus::Paid;
}
}
// Снаружи — только чтение
$invoice->number; // OK
$invoice->status; // OK
$invoice->number = 'NEW-001'; // Error: private setЭто отлично ложится на DDD-модели, где у сущностей есть инварианты. Мы применили в трёх агрегатах, и код стал чище — не нужно писать геттеры для readonly-полей.
Что мы пропустили
HTML5-парсинг в DOM-расширении — у нас нет задач по парсингу HTML. Новые функции request_parse_body() — пока не мигрировали с php://input, хотя планируем. Имплементация __toString в Stringable теперь обязательна — не затронуло, мы и так реализовывали.
Опыт миграции
Переход с 8.2 на 8.4 прошёл безболезненно. Запустили phpstan на уровне 9, исправили 3 деprecated-варнинга. Основной gain — property hooks сократили кодовую базу примерно на 800 строк. Не критично, но приятно. Рекомендую обновляться, особенно если у вас много value objects.
Пока нет комментариев
Будьте первым, кто оставит комментарий к этой статье