Перечисление PHP (PHP 8.1+) задаёт тип с фиксированным набором возможных значений, его вариантов: enum Status { case Active; case Banned; }. Вариант используется как Status::Active, а параметр с типом Status ничего другого не примет. Типизированное перечисление, enum Status: string, ещё и даёт каждому варианту значение, которое можно сохранить и превратить обратно через Status::from().
До перечислений то же самое делали списком строковых констант, и ничто не мешало опечатке вроде 'actve' попасть в базу данных. С Status в качестве типа параметра передать можно только три варианта.
Чистые и типизированные перечисления
Варианты чистого перечисления это только имена. Типизированное перечисление объявляет тип после имени, int или string, и тогда у каждого варианта должно быть уникальное значение этого типа. ->name есть у каждого варианта; ->value только у вариантов типизированного перечисления.
Выбирайте типизированное перечисление всякий раз, когда значение покидает программу: столбец базы данных, URL, поле формы, JSON. Чистое перечисление подходит для значений, которые существуют только внутри кода, например состояния парсера.
Преобразование значения через from() и tryFrom()
У типизированных перечислений есть два статических метода, чтобы превратить сохранённое значение обратно в вариант. from() выбрасывает ValueError для неизвестного значения; tryFrom() возвращает null, что хорошо сочетается с ?? для значения по умолчанию:
Используйте tryFrom() для всего, что прислал пользователь, и from() для значений, которые записал ваш собственный код, где неизвестное значение означает ошибку, о которой вы хотите узнать. Аргумент подчиняется обычным правилам типов параметров: у перечисления int в режиме по умолчанию Priority::from('5') возвращает Priority::Normal, потому что '5' сначала приводится к 5, Priority::from('5x') это TypeError, а под declare(strict_types=1) любая строка это TypeError. Это касается и tryFrom(): Priority::tryFrom('abc') выбрасывает исключение, а не возвращает null, поэтому ввод из формы для перечисления int сначала проверяйте через filter_var($raw, FILTER_VALIDATE_INT).
Список всех вариантов через cases()
Enum::cases() возвращает массив всех вариантов в порядке объявления. Так строят выпадающий список, проверяют список значений или получают все значения через array_column:
Запустите, выберите тариф и нажмите Choose: тот же скрипт выполнится снова с заполненным $_POST['plan'], и tryFrom() превратит отправленную строку обратно в вариант Plan. Подделанное значение вроде plan=gold даёт null, и ни один пункт не будет выбран.
Методы, константы и интерфейсы перечислений
У перечислений могут быть методы, статические методы и константы, и они могут реализовывать интерфейсы. Внутри метода $this это текущий вариант, поэтому match ($this) естественный способ привязать данные к каждому варианту. Чего у перечисления быть не может, так это свойств: данные для каждого варианта берутся из методов.
Перечисления в match
match сравнивает через ===, а вариант перечисления идентичен только самому себе, поэтому match и перечисления хорошо сочетаются. Если забыть вариант и он дойдёт до match, PHP выбросит UnhandledMatchError, а не вернёт молча пустоту:
Чем match отличается от switch, смотрите в match.
Перечисления в JSON и как строки
json_encode() записывает вариант типизированного перечисления как его значение. Чистое перечисление закодировать нельзя, а echo любого варианта завершается ошибкой, потому что перечисление это объект, а не строка. Выводите ->value или ->name:
echo Role::Admin; выбрасывает Error ("Object of class Role could not be converted to string"), а реализовать __toString() перечисления не могут. На обратном пути JSON даёт обычную строку, поэтому превращайте её через from() или tryFrom(), как показано. Подробнее о кодировании в json_encode.
Типичные ошибки с перечислениями
Три ошибки встречаются снова и снова. Вариант никогда не равен своему значению, вариант не может быть ключом массива, а new с перечислением не работает:
Если нужен словарь с ключами из самих вариантов, используйте SplObjectStorage или WeakMap: они принимают объекты в качестве ключей.
Часто задаваемые вопросы
Как получить значение enum в PHP?
У типизированного перечисления читайте ->value: Status::Active->value равно 'active'. У каждого варианта есть ещё ->name, имя варианта строкой ('Active'). У чистого перечисления (без : string или : int) есть только ->name.
Чем from() отличается от tryFrom() в перечислениях PHP?
Оба превращают значение в вариант типизированного перечисления. Status::from('active') возвращает вариант или выбрасывает ValueError, когда варианта с таким значением нет; Status::tryFrom('nope') вместо этого возвращает null. Используйте tryFrom() для пользовательского ввода, а from() там, где неверное значение означает ошибку в коде.
Как получить все значения enum в PHP?
Status::cases() возвращает все варианты в порядке объявления. Для значений типизированного перечисления используйте array_column(Status::cases(), 'value'), а для имён array_column(Status::cases(), 'name').
Чем чистое перечисление отличается от типизированного?
Варианты чистого перечисления (enum Suit { case Hearts; }) это только имена. Типизированное перечисление (enum Suit: string { case Hearts = 'H'; }) даёт каждому варианту уникальное значение int или string, которое нужно, чтобы хранить его в базе данных, передавать в форме или JSON и превращать обратно через from() или tryFrom().
Могут ли у перечисления PHP быть методы?
Да. У перечислений могут быть методы, статические методы и константы, и они могут реализовывать интерфейсы. Внутри метода $this это текущий вариант, поэтому return match ($this) { self::Active => 'green', self::Banned => 'red' }; частый шаблон. Свойств у перечислений быть не может.