Загрузка
Все статьи

PHP 8.4: новые фичи, которые реально полезны в продакшене

Каждый релиз PHP приносит новые фичи, но не все из них попадают в реальный код. В PHP 8.4 есть несколько изменений, которые мы сразу внедрили в проект: property hooks упростили геттеры/сеттеры, new MyClass() без скобок, и улучшенный array_find. Рассказываю, что реально пригодилось, а что оказалось маркетингом.

Обновились на 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.

Поделиться статьёй

Комментарии

0
0 / 2000

Пока нет комментариев

Будьте первым, кто оставит комментарий к этой статье

Читайте также

Doctrine ORM + Symfony: N+1 проблема, которая убила производительность

Страница каталога загружалась 8 секунд. В профайлере — 340 SQL-запросов для отображения 20 товаров. Классический N+1 через Doctrine ORM. Проблема усугублялась тем, что часть запросов шла через lazy loading, а часть — через явные вызовы в Twig-шаблонах. Рассказываю, как мы нашли все источники N+1 и сократили количество запросов до 4.

14 мая 2026 10 мин

PHP: типизирование насквозь через array, список и null — опыт реального проекта

Строгая типизация в PHP 8.2+ отличная, но реальный код завален mixed и array. Как я вёл постепенную миграцию большого PHP-монолита на строгие типы, какие инструменты использовал для автоматического анализа, и почему array — это худший тип, который можно прописать в сигнатуре метода.

6 мая 2026 7 мин

Сложности с разделами каталога в Битрикс24 через REST API: игра с деревом

Обновляете товар через API, передаете SECTION_ID, но он не появляется в нужной папке. В этой статье я объясняю разницу между простым SECTION_ID и сложным деревом инфоблоков, как правильно выстроить иерархию и почему метод crm.product.update не умеет создавать папки.

5 июля 2026 1 мин