Урок по треку PHP 7 мин

PHP 8.x: enum, readonly, match и другое

match, enum, readonly, атрибуты, nullsafe, JIT и файберы.

Знание PHP 8 на собесе это маркер «пишет на современном PHP». Спрашивают match, enum, readonly, атрибуты и что дал JIT.

match

$label = match ($status) {
    'new', 'pending' => 'Ожидает',
    'paid' => 'Оплачен',
    default => throw new UnexpectedValueException($status),
};

Отличия от switch: это выражение, сравнение строгое (===), нет проваливания, без подходящей ветки и default бросается UnhandledMatchError. match (true) заменяет цепочку if/elseif.

enum (8.1)

enum OrderStatus: string
{
    case New = 'new';
    case Paid = 'paid';
    case Cancelled = 'cancelled';

    public function isFinal(): bool
    {
        return match ($this) {
            self::Cancelled => true,
            self::New, self::Paid => false,
        };
    }
}

OrderStatus::from('paid');   // вариант или ValueError
OrderStatus::tryFrom('x');   // null
OrderStatus::cases();        // все варианты
OrderStatus::Paid->value;    // 'paid'

Варианты это синглтоны: сравнивают через ===. Перечисления могут иметь методы, константы и реализовывать интерфейсы, но не свойства с состоянием, наследование и new. Laravel (casts) и Doctrine (enumType) умеют хранить их в колонках.

readonly

final readonly class Money          // readonly-класс (8.2)
{
    public function __construct(public int $amount, public string $currency) {}

    public function add(Money $o): self
    {
        return new self($this->amount + $o->amount, $this->currency);
    }
}

Свойство записывается один раз и только изнутри класса, повторная запись бросает Error. Вложенные объекты остаются изменяемыми: readonly защищает ссылку, а не содержимое. Для изменённой копии создают новый объект (метод with...); с PHP 8.3 readonly-свойства можно переинициализировать в __clone.

Атрибуты

#[Route('/posts/{id}', methods: ['GET'])]
public function show(int $id): Response { ... }

Атрибуты это метаданные, которые читает код через рефлексию (ReflectionMethod::getAttributes()). Фреймворки делают это при сборке кеша. Полезные встроенные: #[\Override] (8.3) проверяет, что метод действительно переопределяет родительский, #[\SensitiveParameter] (8.2) скрывает значение в стектрейсах.

Другие новинки

  • 8.0: union types, nullsafe ?->, именованные аргументы, продвижение свойств, str_contains, throw как выражение, JIT.
  • 8.1: never, пересечения типов, first-class callable f(...), new в инициализаторах, файберы, array_is_list.
  • 8.2: DNF-типы, true как тип, динамические свойства объявлены устаревшими.
  • 8.3: типизированные константы классов, json_validate, #[\Override].
  • 8.4: property hooks и асимметричная видимость свойств.

JIT и файберы

JIT компилирует горячий байткод в машинный код поверх OPcache. Типичному веб-приложению, которое ждёт базу и сеть, он почти не помогает, а вычислениям помогает заметно.

Файбер это функция с собственным стеком, которую можно приостановить из любой глубины (Fiber::suspend()). Параллельности нет, это основа асинхронных библиотек вроде Revolt и AMPHP.

Частые ошибки

  • Ждать от match нестрогого сравнения, как у switch.
  • Считать readonly-объект глубоко неизменяемым.
  • Хранить в базе порядковый номер варианта enum вместо строки.
  • Рассчитывать, что JIT ускорит CRUD.

Что спросят на собесе

  • Чем match отличается от switch?
  • Как устроены enum, чем backed отличается от pure?
  • Что гарантирует readonly и чего не гарантирует?
  • Что такое атрибуты и как их использует фреймворк?
  • Что дал JIT и кому?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.