Перечисления enums в PHP 8.1

Кратко
Перечисления (enums) в PHP 8.1 позволяют хранить значение из заранее определённого набора. Они повышают безопасность типов и делают код читабельнее — особенно полезно для опций в выпадающих списках, статусов, типов и похожих наборов.
Важно: перечисления — это не просто набор констант: у них есть методы, собственные типы (backed enums) и поведение, похожее на объекты.
Что вы найдёте в статье
- Объяснение базовых и backed enums
- Примеры использования и проверки типов
- Когда enums не подходят и альтернативы
- Чек-листы, критерии приёмки и рекомендации по внедрению
Что делают перечисления
Перечисления удобны, когда вы работаете с фиксированным набором связанных значений. Примеры: масти в колоде карт, статусы заказа, тип транспорта. Enums повышают предсказуемость кода и улучшают автодополнение в IDE.
Краткая дефиниция: перечисление — это именованный набор значений, каждое из которых является отдельной сущностью в типовой системе.
Базовые перечисления
Базовые (basic) enums в PHP — это перечисления, у которых значения связаны с именами случаев, но не имеют скалярного эквивалента по умолчанию. Пример из документации:
enum Season
{
case Spring;
case Summer;
case Autumn;
case Winter;
}Значение указывается как EnumTypeName::EnumField, например:
Season::WinterВы можете присвоить такое значение переменной и сравнивать его с другими значениями того же enum:
$favorite = Season::Summer;
if ($favorite == get_current_season()) {
echo "It's my favorite season!";
}Типизация помогает ограничить параметры и возврат конкретным перечислением, уменьшая количество ошибок:
function get_current_season(): Season
{
$day = date("z");
if ($day < 59 || $day > 333) return Season::Winter;
if ($day < 151) return Season::Spring;
if ($day < 243) return Season::Summer;
if ($day < 334) return Season::Autumn;
}Если функция пытается вернуть не-Season, PHP выдаст TypeError.
Backed enums (перечисления со значениями)
Backed enums — это перечисления, у которых у каждого случая есть скалярное значение (int или string):
enum Month: int
{
case Jan = 1;
case Feb = 2;
//...
}Тип должен быть объявлен и быть либо int, либо string. Доступ к базовому значению осуществляется через свойство value:
echo Month::Jan->value;Чтобы получить enum по скалярному значению, используйте метод from:
var_dump(Month::from(2));Если значение не соответствует ни одному случаю, метод from выбросит ValueError; есть также tryFrom, который вернёт null вместо ошибки.
Методы перечислений
Enum в PHP может содержать методы — это делает их ближе к классам.
Пример метода для получения количества дней в месяце (без учёта високосного года):
/* Note: no leap-year handling! */
function daysInMonth(): int {
if ($this == Month::Feb) return 28;
if (in_array($this, array(Month::Apr, Month::Jun, Month::Sep, Month::Nov))) return 30;
return 31;
}Внутри метода $this ссылается на конкретный случай перечисления. Также доступны статические методы, например для генерации случайного значения:
static function random(): Month {
return Month::from(rand(1, count(Month::cases())));
}Когда перечисления не подходят
- Нужна расширяемость в рантайме: enums фиксированы в коде и не предназначены для динамического добавления значений.
- Значения содержат сложную связанную структуру или много состояний — лучше класс/DTO.
- Необходима обратная совместимость с системой, где ожидаются примитивы (иногда проще использовать константы).
Контрпример: если у вас управляемый через админку список статусов, который редакторы изменяют без деплоя, enum в коде не подойдёт.
Альтернативы и сравнение
- Константы класса: простые, но не формируют отдельный тип.
- Ассоциативные массивы: гибкие, но без типовой безопасности.
- Классы/Value objects: если нужны поведение и состояние для каждого значения.
Сравнение (кратко):
- enum — явный тип, безопасный для проверки и подсветки в IDE;
- константы — простые и гибкие, но не обеспечивают типовой безопасности;
- класс — больше возможностей, но сложнее, требует больше кода.
Ментальные модели и эвристики
- Ментальная модель: enum — это «именованный набор уникальных сущностей», каждая сущность — не просто число/строка, а самостоятельный член множества.
- Эвристика выбора: если набор фиксирован и логически связан, и вы хотите предотвратить передачу произвольных строк/чисел — используйте enum.
Мини-методология внедрения enums в проект
- Идентифицируйте фиксированные наборы (статусы, типы, роли).
- Оцените требование к расширяемости.
- Создайте enum с полным набором случаев.
- Замените магические строки/числа на enum и добавьте типизацию.
- Напишите тесты, покрывающие перевод между значениями и методами enum.
Чек-лист по ролям
- Разработчик: заменить литералы на enum, настроить автодок и IDE-настройки.
- Технический лидер: согласовать границы enum, определить правила изменения и версионирования.
- Тестировщик: проверить сериализацию/десериализацию, совместимость с API.
Критерии приёмки
- Все места, где ранее использовались «магические» значения, используют enum или документированное обоснование.
- Unit-тесты покрывают методы enum и граничные случаи (from/tryFrom).
- Документация/README проекта обновлены с указанием новых типов.
Примеры тест-кейсов (коротко)
- Month::from(2) возвращает Month::Feb.
- Month::tryFrom(99) возвращает null.
- Метод daysInMonth() возвращает корректное количество дней для каждого месяца (включая 28/29 для февраля, если вы внедрили логику високосного года).
Миграционные заметки
- При добавлении новых значений enum это breaking change для внешних клиентов, если enum используется в публичном API (особенно для string-backed).
- Для сериализации используйте value или name сознательно — договоритесь о формате (name даёт понимание, value удобен для хранения в БД).
Важно: перед тем как хранить enum->value в базе, задокументируйте её семантику и поведение при изменениях.
Быстрый справочник (cheat sheet)
- Объявление базового enum: enum Name { case A; case B; }
- Backed enum: enum Name: int { case A = 1; }
- Доступ: Name::A
- Базовое значение: Name::A->value
- Конвертация: Name::from(1) или Name::tryFrom(1)
- Перечисления могут иметь методы и статические фабрики.
Пример простого рабочего сценария
- UI: выпадающий список статусов формируется из перечисления Status::cases()
- Backend: API принимает и возвращает либо name, либо value (по соглашению)
- БД: сохраняет value для компактности, но имеет миграцию на случай изменений
Заключение
Перечисления в PHP 8.1 — значимое дополнение языка: они дают типовую безопасность, улучшают читаемость и упрощают поддержку фиксированных наборов значений. При грамотном применении помогают избежать ошибок, связанных с «магическими» строками и числами. Однако enums не заменят классы там, где нужна динамичность или сложное состояние.
Краткое резюме
- Используйте enums для фиксированных наборов значений.
- Выбирайте backed enums, если нужно сохранять скалярные значения.
- Тщательно продумывайте сериализацию и миграции при использовании enums в API и БД.