Enum зʼявився в PHP 8.1 і закриває стару проблему «магічних рядків»: замість набору констант класу ви отримуєте окремий тип із фіксованим переліком значень. Кожен case — це обʼєкт-одинак, створений один раз на процес, тому порівняння через === завжди коректне, а new OrderStatus(...) заборонено. Найважливіший практичний наслідок — типізація: сигнатура public function markAs(OrderStatus $status): void фізично не дає передати 'payed' з одруківкою, і перевірку робить не ваш if, а сам PHP.
Різниця pure і backed — у наявності скалярного значення. Pure enum (enum Weekday { case Mon; }) реалізує інтерфейс UnitEnum і має лише властивість name — рядок з іменем case-у. Backed enum оголошується з типом (enum OrderStatus: string), реалізує BackedEnum, додає властивість value і два статичні методи: from() повертає case або кидає \ValueError, tryFrom() повертає case або null. Правило просте: from() — для значень, у яких ви впевнені (константи, дані з власної бази), tryFrom() — для всього, що прийшло від користувача чи зовнішнього API. Метод cases() є в обох видах і повертає масив усіх case-ів у порядку оголошення — з нього роблять селекти, сідери й перевірки.
Enum — це майже повноцінний тип: у ньому можна оголошувати методи, статичні методи, константи, підключати трейти й реалізовувати інтерфейси. Чого не можна — властивостей і будь-якого стану: enum не має конструктора, всі case-и незмінні. Тому «додаткові дані» до case-у оформлюють методом, найчастіше через match ($this), як у прикладі з label(). match тут природний партнер enum: він порівнює строго через ===, а якщо жодна гілка не підійшла, кидає \UnhandledMatchError — це краще за мовчазний default, бо новий case ламає код одразу, а не в проді.
У Laravel backed enum стає типом атрибута моделі: protected function casts(): array { return ['status' => OrderStatus::class]; } (enum-cast доступний з Laravel 9, метод casts() — з Laravel 10; раніше те саме писали у властивості $casts). Читання дає обʼєкт enum, запис приймає обʼєкт і кладе в колонку value. Pure enum так кастити не можна — усередині Eloquent викликає tryFrom(). Так само enum підтримується в implicit route binding і у валідації через Rule::enum(OrderStatus::class).
Межі варто назвати чесно. json_encode серіалізує backed enum у його value автоматично, а на pure enum кидає виняток, бо представлення за замовчуванням у нього немає. У базі колонку зазвичай лишають звичайним varchar замість нативного ENUM MySQL — тоді додавання нового case-у не вимагає ALTER TABLE, а PostgreSQL взагалі не має сумісного синтаксису. І пам'ятайте, що значення backed enum — це частина контракту з базою та API: перейменувати case Paid можна майже безкарно, а змінити 'paid' на 'payed' — уже міграція даних.
enum OrderStatus: string // backed enum: у кожного case є рядкове значення
{
case Pending = 'pending';
case Paid = 'paid';
case Cancelled = 'cancelled';
public const Default = self::Pending; // константа-псевдонім, не новий case
/** У методі $this — це сам case */
public function label(): string
{
return match ($this) { // match порівнює через ===
self::Pending => 'Очікує оплати',
self::Paid => 'Оплачено',
self::Cancelled => 'Скасовано',
}; // без default: новий case одразу зламає тест
}
}
enum Weekday { case Mon; case Tue; } // pure enum: значення немає
OrderStatus::from('paid'); // OrderStatus::Paid
OrderStatus::tryFrom('deleted'); // null — для даних ззовні
OrderStatus::from('deleted'); // \ValueError
OrderStatus::cases(); // [Pending, Paid, Cancelled]
OrderStatus::Paid->value; // 'paid' (лише в backed)
OrderStatus::Paid->name; // 'Paid' (є в обох)
Weekday::Mon->value; // помилка: у pure enum немає value
OrderStatus::Paid === OrderStatus::from('paid'); // true: case — одинак
OrderStatus::Paid === 'paid'; // false: обʼєкт != рядок
// Eloquent: у колонці лежить 'paid', у моделі — обʼєкт enum
final class Order extends Model
{
protected function casts(): array
{
return ['status' => OrderStatus::class]; // працює тільки з backed enum
}
}
$order->status->label(); // 'Оплачено'
$order->status = OrderStatus::Paid;
$order->save(); // у базу піде 'paid'
Сформулюйте одним реченням: pure enum — це іменований перелік, backed enum — той самий перелік плюс міст до бази й API через value. І одразу додайте, що з зовнішніми даними використовуєте tryFrom(), а не from().