Перейти к основному содержимому

Современный PHP 8 — enum, readonly и атрибуты

Разработчику Архитектору

Материал дополняет ООП в PHP и ориентирован на PHP 8.1+. Устаревшие константы вместо enum и «магические» строки статусов здесь рассматриваются только как антипаттерн.


Readonly-свойства (PHP 8.1)

Модификатор readonly запрещает присвоение свойству после инициализации. Запись возможна только один раз — обычно в конструкторе.

<?php
declare(strict_types=1);

class Order
{
public function __construct(
public readonly string $id,
public readonly \DateTimeImmutable $createdAt,
private readonly float $amount,
) {}

public function total(): float
{
return $this->amount;
}
}

$order = new Order('ord-1', new \DateTimeImmutable(), 99.5);
// $order->id = 'x'; // Error: Cannot modify readonly property

Правила:

  • Тип свойства обязателен (нельзя public readonly $x без типа).
  • Readonly нельзя сделать static.
  • Свойство без значения по умолчанию должно быть инициализировано до конца конструктора.

Readonly-класс (PHP 8.2)

Если пометить весь класс как readonly, все его свойства становятся readonly автоматически:

readonly class Point
{
public function __construct(
public float $x,
public float $y,
) {}
}

Такой класс не может содержать обычные изменяемые свойства и не может быть унаследован не-readonly классом. Удобно для DTO и объектов-значений, которые не должны меняться после создания.


Перечисления enum (PHP 8.1)

Enum — отдельный тип для ограниченного набора вариантов. Заменяет «магические» строки и int-константы в классе.

Unit enum (без значения)

enum Status
{
case Draft;
case Published;
case Archived;
}

function canEdit(Status $status): bool
{
return match ($status) {
Status::Draft => true,
Status::Published, Status::Archived => false,
};
}

Сравнение — только через === между case одного enum. Status::Draft === 'draft' всегда ложь.

Backed enum (со значением в БД или JSON)

enum Priority: string
{
case Low = 'low';
case Normal = 'normal';
case High = 'high';
}

// Из строки (например, из БД)
$p = Priority::from('high');

// Безопасный разбор
$p = Priority::tryFrom('unknown'); // null, если нет такого значения

echo $p->value; // 'high'
echo $p->name; // 'High'

В таблице хранят колонку priority VARCHAR со значениями 'low', 'normal', 'high' и при чтении вызывают Priority::from($row['priority']).

Методы в enum

Enum может содержать методы, как класс:

enum Role: string
{
case Admin = 'admin';
case Editor = 'editor';
case Guest = 'guest';

public function label(): string
{
return match ($this) {
self::Admin => 'Администратор',
self::Editor => 'Редактор',
self::Guest => 'Гость',
};
}

public function canPublish(): bool
{
return $this === self::Admin || $this === self::Editor;
}
}

Атрибуты (PHP 8.0)

Атрибут — метаданные у класса, метода, свойства или параметра. Синтаксис: #[ИмяАтрибута(...)]. Это не комментарии: атрибуты доступны через Reflection во время выполнения.

#[\Attribute(\Attribute::TARGET_METHOD)]
class Route
{
public function __construct(
public string $path,
public array $methods = ['GET'],
) {}
}

class HomeController
{
#[Route('/')]
public function index(): string
{
return 'Главная';
}

#[Route('/about', methods: ['GET'])]
public function about(): string
{
return 'О проекте';
}
}

Чтение атрибутов (упрощённый диспетчер):

$ref = new \ReflectionMethod(HomeController::class, 'index');
$attrs = $ref->getAttributes(Route::class);

foreach ($attrs as $attr) {
$route = $attr->newInstance();
echo $route->path; // /
}

Фреймворки (Symfony, Laravel 8+) используют встроенные и пользовательские атрибуты для маршрутов, валидации, DI. В учебном коде атрибуты помогают декларативно описывать правила без дублирования в конфигурационных массивах.

Встроенный атрибут для переопределения типов

class Container
{
/** @var array<string, object> */
private array $services = [];

#[\ReturnTypeWillChange]
public function offsetGet($key): mixed
{
return $this->services[$key] ?? null;
}
}

ReturnTypeWillChange — временная метка совместимости при реализации интерфейсов, меняющих сигнатуры между версиями PHP.


Как сочетать возможности

Пример неизменяемой сущности со статусом-enum:

readonly class Article
{
public function __construct(
public string $slug,
public string $title,
public Status $status,
) {}

public function withStatus(Status $new): self
{
return new self($this->slug, $this->title, $new);
}
}

Вместо изменения полей создаётся новый объект — предсказуемое поведение без побочных эффектов.


Сравнение с прежними приёмами

ЗадачаСтарый подходPHP 8+
Статусы заказаconst STATUS_PAID = 2enum OrderStatus
Неизменяемый IDprivate + только геттерpublic readonly string $id
Метаданные маршрутамассив в отдельном файле#[Route('...')] на методе

Что изучить дальше


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).