До PHP 8.1 «перечисления» в проектах изображали как угодно: набором констант класса, строковыми литералами вроде 'active' или магическими числами. Всё это легко сломать опечаткой и невозможно типизировать. Нативный enum закрывает проблему: это полноценный тип, который ограничивает значение фиксированным набором вариантов, ловит ошибки на этапе компиляции и работает с проверкой типов в аргументах и свойствах. Разберём enum на рабочих примерах — от базового объявления до методов, интерфейсов и хранения в базе данных.
Pure enum против Backed enum
В PHP есть два вида перечислений. Pure enum (чистый) — это просто набор именованных вариантов без скалярного значения. Каждый case внутри представляет собой объект-синглтон: два обращения к одному варианту — это буквально один и тот же объект, поэтому сравнивать их можно через ===.
<?php
// Чистый enum — без привязки к значению
enum Suit
{
case Hearts;
case Diamonds;
case Clubs;
case Spades;
}
$card = Suit::Hearts;
// Сравнение по идентичности — это синглтоны
var_dump($card === Suit::Hearts); // bool(true)
var_dump($card instanceof Suit); // bool(true)
// У каждого case есть имя
echo $card->name; // Hearts
Backed enum (типизированный) привязывает каждый вариант к скалярному значению — string или int. Это нужно, когда значение надо сериализовать: записать в базу, отдать в JSON или принять из формы. Тип объявляется после двоеточия, а значение читается через свойство value.
<?php
// Backed enum, привязанный к строке
enum Status: string
{
case Active = 'active';
case Banned = 'banned';
case Pending = 'pending';
}
$status = Status::Active;
echo $status->name; // Active
echo $status->value; // active
// Тип значения должен совпадать у всех case:
// нельзя смешивать string и int в одном enum

from, tryFrom и cases: получаем значения обратно
Главная сила backed enum — преобразование скалярного значения обратно в объект перечисления. Для этого есть два статических метода. from() бросает \ValueError, если значение не найдено, а tryFrom() в этом случае возвращает null. На практике tryFrom() почти всегда удобнее: он позволяет обработать невалидный ввод без исключений.
<?php
// Значение из базы или запроса
$raw = $_GET['status'] ?? '';
// Строгий вариант: упадёт с ValueError при мусоре
$status = Status::from('active'); // Status::Active
// Безопасный вариант: вернёт null
$status = Status::tryFrom($raw) ?? Status::Pending;
// from при неизвестном значении:
try {
Status::from('deleted');
} catch (\ValueError $e) {
echo $e->getMessage();
// "deleted" is not a valid backing value for enum Status
}
Метод cases() доступен у обоих видов enum и возвращает массив всех вариантов в порядке объявления. Это удобно для построения выпадающих списков, валидации и миграций.
<?php
// Собираем массив для <select> в форме
$options = [];
foreach (Status::cases() as $case) {
$options[$case->value] = $case->name;
}
// ['active' => 'Active', 'banned' => 'Banned', 'pending' => 'Pending']
// Список допустимых значений для валидации
$allowed = array_column(Status::cases(), 'value');
// ['active', 'banned', 'pending']
Методы и константы внутри enum
Enum в PHP — это не просто список, а почти полноценный класс: в нём можно объявлять методы, константы и даже статические методы. Внутри метода доступен $this, указывающий на текущий вариант. Это позволяет держать логику, связанную с перечислением, рядом с ним — а не размазывать её по бесконечным switch в коде.
<?php
enum Status: string
{
case Active = 'active';
case Banned = 'banned';
case Pending = 'pending';
// Метод экземпляра: $this — текущий case
public function label(): string
{
return match($this) {
Status::Active => 'Активен',
Status::Banned => 'Заблокирован',
Status::Pending => 'Ожидает проверки',
};
}
// Можно вернуть и другой тип, например цвет для UI
public function color(): string
{
return match($this) {
Status::Active => '#22c55e',
Status::Banned => '#ef4444',
Status::Pending => '#f59e0b',
};
}
// Статический метод-фабрика
public static function default(): self
{
return self::Pending;
}
// Константы внутри enum тоже разрешены
const DEFAULT_LABEL = 'Неизвестно';
}
echo Status::Active->label(); // Активен
echo Status::Banned->color(); // #ef4444
echo Status::default()->value; // pending
Связка match($this) с методами enum — типичный паттерн. В отличие от switch, match строгий (сравнивает через ===) и выбрасывает \UnhandledMatchError, если вы добавили новый case, но забыли описать его в методе. Это превращает забытую ветку из тихого бага в явную ошибку.
Enum с интерфейсами и в типах
Перечисления могут реализовывать интерфейсы — это позволяет принимать разные enum в одной функции по общему контракту. Backed enum при этом автоматически реализует встроенный интерфейс BackedEnum, а любой enum — UnitEnum.
<?php
interface HasLabel
{
public function label(): string;
}
enum Priority: int implements HasLabel
{
case Low = 1;
case Medium = 2;
case High = 3;
public function label(): string
{
return match($this) {
Priority::Low => 'Низкий',
Priority::Medium => 'Средний',
Priority::High => 'Высокий',
};
}
}
// Функция принимает enum как полноценный тип
function renderBadge(HasLabel $item): string
{
return '<span class="badge">' . htmlspecialchars($item->label()) . '</span>';
}
echo renderBadge(Priority::High); // <span class="badge">Высокий</span>
// Enum как тип аргумента — невалидное значение просто не передать
function setPriority(Priority $p): void
{
// здесь $p гарантированно один из трёх вариантов
}
Главное преимущество — типобезопасность сигнатур. Если функция объявляет Priority $p, передать туда случайное число или строку уже не получится: PHP отклонит вызов с TypeError. Никаких проверок «а вдруг пришло не то значение» внутри метода писать не нужно.
Хранение enum в базе данных
В реальном коде enum чаще всего ездит между приложением и базой. Схема простая: при записи отдаём ->value, при чтении восстанавливаем через tryFrom(). Покажу на чистом PDO.
<?php
// Запись: в базу уходит строка, а не объект
$stmt = $pdo->prepare(
'UPDATE users SET status = :status WHERE id = :id'
);
$stmt->execute([
'status' => Status::Active->value, // 'active'
'id' => $userId,
]);
// Чтение: строку из БД превращаем обратно в enum
$row = $pdo->query('SELECT status FROM users WHERE id = 1')
->fetch(PDO::FETCH_ASSOC);
$status = Status::tryFrom($row['status']);
if ($status === null) {
// в базе оказалось неизвестное значение — данные испорчены
$status = Status::default();
}
echo $status->label(); // Активен
Если вы используете Doctrine или Eloquent, ручное преобразование не нужно — оба ORM умеют кастовать колонку в enum автоматически. В Laravel это делается через свойство $casts:
<?php
// app/Models/User.php (Laravel 9+)
class User extends Model
{
protected $casts = [
'status' => Status::class, // строка из БД -> enum и обратно
];
}
// Теперь $user->status — это объект Status, а не строка
$user->status = Status::Banned; // в базу уйдёт 'banned'
echo $user->status->label(); // Заблокирован
Чеклист по работе с enum
- Pure enum — когда значение нужно только внутри кода (состояния, режимы). Сравнивайте через
===. - Backed enum — когда значение уходит в базу, JSON или форму. Тип
stringчитабельнее в БД,intкомпактнее. - Для разбора внешнего ввода используйте
tryFrom(), а неfrom()— это избавит от лишних try/catch. - Логику варианта (label, цвет, права) держите в методах enum через
match($this), а не в разбросанныхswitch. matchвнутри метода защищает от забытых вариантов: новыйcaseбез ветки сразу даст ошибку.- Используйте enum как тип аргумента — это убирает ручную валидацию «допустимого значения».
- Реализуйте интерфейсы, если нужно обрабатывать несколько перечислений единообразно.
- В ORM (Laravel
$casts, DoctrineenumType) преобразование автоматическое — не дублируйте его руками.
Нативный enum убирает целый класс ошибок, связанных с «магическими» строками и числами, и переносит проверку корректности с рантайма на уровень типов. Если проект уже на PHP 8.1+, заменять старые наборы констант на enum стоит при первой же возможности — код становится строже и понятнее без единой дополнительной строчки валидации.
