Что такое enum
Enum в PHP — это собственный тип с закрытым набором допустимых значений. Он появился в PHP 8.1 и решает задачу, которую раньше часто имитировали строками, числами или набором const: статус заказа, роль пользователя, направление сортировки, тип платежа, состояние задачи.
Главная разница в том, что enum — не просто «удобные константы». PHP проверяет тип: если функция принимает OrderStatus, туда нельзя случайно передать строку 'paid', даже если она выглядит правильно. Это продолжение темы из Типы и strict_types: enum даёт отдельный доменный тип, а не ещё одну строку с договорённостью в голове.
<?php
declare(strict_types=1);
enum OrderStatus
{
case Draft;
case Paid;
case Cancelled;
}
function canShip(OrderStatus $status): bool
{
return $status === OrderStatus::Paid;
}
var_dump(canShip(OrderStatus::Paid)); // bool(true)
// canShip('paid'); // TypeErrorКаждый case — singleton-объект этого enum-типа. Поэтому сравнивать cases обычно нужно через ===, а не через строки или числа.
flowchart TD
A[Enum type] --> B[Unit / pure enum]
A --> C[Backed enum]
B --> D[case без scalar value]
C --> E[string или int value]
A --> F[UnitEnum::cases]
C --> G[BackedEnum::from]
C --> H[BackedEnum::tryFrom]
A --> I[methods и interfaces]
A --> J[autoload как class-like сущность]Unit enum и backed enum
В PHP есть два основных вида enum. Unit enum, его ещё называют pure enum, не имеет scalar-значения у cases. В примере выше OrderStatus::Paid — это не 'Paid', не 1 и не 'paid'; это отдельный объект-case.
Backed enum имеет backing type: string или int. Это удобно, когда значение должно жить в базе, JSON, URL, HTML-форме или внешнем API.
<?php
declare(strict_types=1);
enum OrderStatus: string
{
case Draft = 'draft';
case Paid = 'paid';
case Cancelled = 'cancelled';
}
$status = OrderStatus::Paid;
echo $status->name; // Paid
echo $status->value; // paidname есть у любого enum-case и соответствует имени case в коде. value есть только у backed enum. Значения backed enum должны быть явно заданы и уникальны; PHP не генерирует автоматические 0, 1, 2, как в некоторых других языках. В одном enum нельзя смешать int и string.
Практическое правило простое: если значение нужно только внутри кода, начинайте с unit enum. Если оно должно стабильно сохраняться или обмениваться с внешним миром, используйте backed enum и выбирайте человекочитаемые строки, например 'paid', а не случайные числа.
from, tryFrom и доверие к данным
Backed enum автоматически получает методы from() и tryFrom(). Оба превращают scalar-значение обратно в enum-case, но по-разному ведут себя при неизвестном значении.
<?php
declare(strict_types=1);
enum PaymentMethod: string
{
case Card = 'card';
case BankTransfer = 'bank_transfer';
case Cash = 'cash';
}
$trusted = PaymentMethod::from('card');
$maybe = PaymentMethod::tryFrom($_GET['method'] ?? '');
if ($maybe === null) {
// Показать 400 Bad Request, выбрать дефолт или вернуть ошибку формы.
}from() подходит для данных, которые приложение считает корректными: например, значение уже пришло из собственного кода или из колонки БД с контролируемым набором значений. Если case не найден, будет ValueError.
tryFrom() лучше для ввода пользователя, query string, webhook payload и старых данных, где неизвестное значение — ожидаемый сценарий. Он возвращает null, и вызывающий код сам решает, что делать. Важно помнить связь со strict_types: from() и tryFrom() подчиняются обычным правилам строгой и слабой типизации для scalar-параметров.
Методы, интерфейсы и match
Enum может содержать методы и реализовывать интерфейсы. Это сильнее, чем держать рядом массив status => label: поведение живёт рядом с допустимыми значениями, а статический анализ лучше понимает связь.
<?php
declare(strict_types=1);
interface HasLabel
{
public function label(): string;
}
enum OrderStatus: string implements HasLabel
{
case Draft = 'draft';
case Paid = 'paid';
case Cancelled = 'cancelled';
public function label(): string
{
return match ($this) {
self::Draft => 'Черновик',
self::Paid => 'Оплачен',
self::Cancelled => 'Отменён',
};
}
public function canBeEdited(): bool
{
return $this === self::Draft;
}
}Внутри метода доступен $this, то есть конкретный case. Для различий между cases часто используют match ($this): он строгий и хорошо показывает, что все состояния перечислены явно. Это особенно полезно для PHPDoc, generics и статический анализ: анализатору проще заметить, что новый case добавили, а match забыли обновить.
У enum есть ограничения. Нельзя объявлять обычные свойства, конструктор и деструктор; enum нельзя наследовать через extends, и его cases нельзя создать через new. Если вам нужен объект с произвольным состоянием, используйте обычный class из Классы, объекты и видимость. Enum хорош именно для конечного набора вариантов.
cases() и списки для UI
Любой enum реализует внутренний интерфейс UnitEnum, поэтому у него есть статический метод cases(). Он возвращает массив всех cases в порядке объявления.
<?php
declare(strict_types=1);
$options = [];
foreach (OrderStatus::cases() as $status) {
$options[$status->value] = $status->label();
}
print_r($options);Так удобно строить options для формы, документацию API, whitelist значений для фильтра или список разрешённых статусов. Но не превращайте enum в dumping ground для всего подряд. Если метод начинает ходить в базу, читать конфиг или зависеть от текущего пользователя, это уже не справочник значений, а сервисная логика.
Enum, неймспейсы и autoloading
Enum — class-like сущность. Он живёт в тех же namespace, что классы, интерфейсы и traits, и автозагружается так же, как обычный класс. Это связывает тему с Неймспейсы и use и Autoloading и PSR-4.
<?php
declare(strict_types=1);
namespace App\Order;
enum OrderStatus: string
{
case Draft = 'draft';
case Paid = 'paid';
case Cancelled = 'cancelled';
}При PSR-4 такой enum обычно лежит в src/Order/OrderStatus.php, а в другом файле импортируется через use App\Order\OrderStatus;. Для Composer нет принципиальной разницы между class Product, interface Repository и enum OrderStatus: это объявления с fully qualified name.
Serialization, JSON и база данных
Для обычной PHP-сериализации enum имеет специальное представление: сериализуется не копия объекта со свойствами, а ссылка на конкретный enum-case. После unserialize() вы снова получаете тот же singleton-case.
С JSON важнее различать два вида enum. Backed enum кодируется своим scalar-значением, например OrderStatus::Paid становится 'paid'. Unit enum не имеет стандартного scalar-представления для JSON; если вам нужно отдавать его наружу, задайте явное правило: backed enum, отдельный mapper или JsonSerializable.
С базой данных та же логика. В таблицу обычно сохраняют $status->value, а при чтении восстанавливают enum через OrderStatus::from($row['status']) или tryFrom(). В связке с PDO и подключение к базе это даёт понятную границу: PDO возвращает scalar-данные, доменная модель работает с enum-типом.
Enum против набора constants
До PHP 8.1 часто писали так:
final class OrderStatus
{
public const DRAFT = 'draft';
public const PAID = 'paid';
public const CANCELLED = 'cancelled';
}Это лучше, чем разбросанные строки, но всё ещё слабее enum. Константы не создают отдельный тип: функция с параметром string $status примет любую строку. Нельзя штатно получить список допустимых значений без Reflection или ручного массива. Поведение вроде label() приходится держать отдельно.
Enum закрывает эти дыры: тип проверяется PHP, список cases доступен через cases(), поведение можно положить рядом, а backed value остаётся удобным для хранения. Constants всё ещё уместны для технических значений без конечного набора состояний: лимиты, имена заголовков, числовые настройки. Но для доменных вариантов enum обычно честнее.
См. также
- Типы и strict_types — как enum участвует в type declarations и где работают строгие scalar-правила.
- Классы, объекты и видимость — чем enum похож на объект и где намеренно ограничен.
- Наследование, интерфейсы и трейты — enum может реализовывать интерфейсы, но не участвует в наследовании как class.
- Неймспейсы и use — как импортировать enum по fully qualified name.
- Autoloading и PSR-4 — почему enum лежит в файле и автозагружается как class-like сущность.
- Атрибуты и Reflection — как читать enum, cases и attributes во время выполнения.
- JSON, сериализация и форматы данных — что происходит при
serialize()иjson_encode(). - PDO и подключение к базе — где scalar-значение enum обычно пересекает границу с БД.