Почему «Enumerations on PHP» — это вопрос на миллион?
Если вы когда-нибудь гуглили «enumerations on PHP», то знаете: до выхода PHP 8.1 эта тема была настоящим квестом. Разработчики изобретали велосипеды с константами, абстрактными классами и хитрыми трейтами. Stack Overflow пестрил вопросами вроде «Как эмулировать Enum в PHP?», а ответы собирали сотни апвоутов.
Но времена изменились. Начиная с PHP 8.1, перечисления (Enum) стали нативной частью языка. И это не просто синтаксический сахар — это мощный инструмент для написания чистого, безопасного и самодокументируемого кода.
В этой статье мы разберём всё: от классического подхода с константами до современных Enum, с реальными примерами и лучшими практиками. Поехали!
Проблема: как жили PHP-разработчики до PHP 8.1
До появления нативных перечислений у нас было два основных пути: константы класса и «костыли» с абстрактными классами. Рассмотрим оба.
Подход №1: Константы класса (самый популярный)
class OrderStatus {
const PENDING = 'pending';
const PROCESSING = 'processing';
const SHIPPED = 'shipped';
const DELIVERED = 'delivered';
const CANCELLED = 'cancelled';
}
// Использование
$status = OrderStatus::PENDING;
if ($status === OrderStatus::PROCESSING) {
echo 'Заказ обрабатывается';
}
Плюсы: простота, понятность, автодополнение в IDE.
Минусы: отсутствие типизации. Любой может передать строку 'invalid_status' — и PHP не ругнётся.
Подход №2: Абстрактный класс + приватный конструктор
Продвинутые разработчики шли дальше:
abstract class OrderStatus {
private function __construct() {}
public static function pending(): self {
return new class extends OrderStatus {};
}
public static function processing(): self {
return new class extends OrderStatus {};
}
// ... остальные статусы
}
Работало, но код раздувался. И это всё равно была эмуляция, а не настоящая поддержка языка.
Нативные Enum в PHP 8.1: революция
С выходом PHP 8.1 проблема «enumerations on PHP» была решена раз и навсегда. Теперь перечисления — это полноценные типы, которые:
- Поддерживают строгую типизацию
- Имеют встроенные методы (
name,value,cases()) - Могут реализовывать интерфейсы
- Поддерживают трейты
- Интегрируются с
matchиswitch
Базовый синтаксис Enum в PHP
enum OrderStatus {
case Pending;
case Processing;
case Shipped;
case Delivered;
case Cancelled;
}
// Использование
$status = OrderStatus::Pending;
echo $status->name; // 'Pending'
Всё просто и элегантно. Но это только начало.
Типы Enum: чистые и со значениями
PHP поддерживает два типа перечислений:
1. Pure Enum (чистые перечисления)
Без привязки к конкретным значениям. Идеально для флагов, состояний, типов.
enum UserRole {
case Admin;
case Editor;
case Viewer;
}
function checkAccess(UserRole $role): bool {
return match($role) {
UserRole::Admin => true,
UserRole::Editor => true,
UserRole::Viewer => false,
};
}
2. Backed Enum (перечисления со значениями)
Когда нужно сопоставить enum с конкретным значением (строкой или целым числом).
enum OrderStatus: string {
case Pending = 'pending';
case Processing = 'processing';
case Shipped = 'shipped';
case Delivered = 'delivered';
case Cancelled = 'cancelled';
}
// Получаем значение
$status = OrderStatus::Shipped;
echo $status->value; // 'shipped'
// Создаём из значения
$status = OrderStatus::from('processing'); // OrderStatus::Processing
$status = OrderStatus::tryFrom('unknown'); // null (без исключения)
Важно: from() выбрасывает исключение, если значение не найдено. tryFrom() возвращает null — используйте его, когда не уверены в корректности данных.
Продвинутые возможности Enum в PHP
Методы внутри Enum
Да, у перечислений могут быть свои методы! Это не просто набор констант — это полноценные объекты.
enum OrderStatus: string {
case Pending = 'pending';
case Processing = 'processing';
case Delivered = 'delivered';
public function label(): string {
return match($this) {
self::Pending => 'Ожидает обработки',
self::Processing => 'В обработке',
self::Delivered => 'Доставлен',
};
}
public function canCancel(): bool {
return match($this) {
self::Pending, self::Processing => true,
default => false,
};
}
}
// Использование
$status = OrderStatus::Processing;
echo $status->label(); // 'В обработке'
echo $status->canCancel() ? 'Можно отменить' : 'Нельзя отменить';
Реализация интерфейсов
Enum могут реализовывать интерфейсы — это даёт невероятную гибкость.
interface HasColor {
public function color(): string;
}
enum Status: string implements HasColor {
case Success = 'success';
case Warning = 'warning';
case Error = 'error';
public function color(): string {
return match($this) {
self::Success => '#28a745',
self::Warning => '#ffc107',
self::Error => '#dc3545',
};
}
}
function renderBadge(HasColor $status): string {
return "<span style='color: {$status->color()}'>{$status->value}</span>";
}
Метод cases() — итерация по всем значениям
Часто нужно получить список всех возможных вариантов. Метод cases() возвращает массив всех кейсов.
enum Day: int {
case Monday = 1;
case Tuesday = 2;
case Wednesday = 3;
case Thursday = 4;
case Friday = 5;
case Saturday = 6;
case Sunday = 7;
public static function workingDays(): array {
return array_filter(self::cases(), fn($day) => $day->value <= 5);
}
}
// Все дни
foreach (Day::cases() as $day) {
echo $day->name . ' (' . $day->value . ')<br>';
}
// Только рабочие
$workingDays = Day::workingDays();
Enum vs. match: идеальная пара
Одно из главных преимуществ Enum — идеальная интеграция с конструкцией match. Вместо громоздких switch-case:
// До Enum
function getDiscount(string $userType): float {
switch ($userType) {
case 'vip': return 0.2;
case 'regular': return