Enums в PHP 8.1 — что это, как и когда использовать

Быстрые ссылки
- Базовый синтаксис
- Pure vs Backed Enums
- Добавление методов в enums
- Константы в enums
- Когда использовать enums
- Примеры и практические сценарии
- Миграция и чек-листы
- Критерии приёмки и тесты
- Заключение
Базовый синтаксис
Enums — это типы, экземпляры которых могут существовать только в виде заранее определённых случаев (cases). Ниже — самый простой пример перечисления:
enum PostStatus {case Published;
case InReview;
case Draft;
}
Ключевое слово case используется для перечисления допустимых вариантов. Получить доступ к конкретному варианту можно так же, как к константе класса:
$published = PostStatus::Published;Enums интегрируются с системой типов PHP. Это значит, что вы можете использовать их в аргументах функций и в свойствах классов, чтобы ограничить допустимые значения:
class BlogPost {
public function __construct(
public string $Headline,
public string $Content,
public PostStatus $Status=PostStatus::Draft) {}
}Пример использования класса BlogPost:
// OK
$post = new BlogPost(
"Example Post",
"An example",
PostStatus::Draft
);
// TypeError: Argument #3 ($Status) must be of type PostStatus
$post = new BlogPost(
"Broken Example",
"A broken example",
"Submitted"
);В первом случае значение PostStatus::Draft — допустимое значение enum. Во втором случае передаётся обычная строка, что приводит к ошибке времени выполнения, потому что ожидается экземпляр PostStatus.
Enums предоставляют метод cases() для перечисления всех доступных вариантов:
PostStatus::cases();// [PostStatus::Published, PostStatus::InReview, PostStatus::Draft]
Pure vs Backed Enums
Pure enum содержит только case без дополнительных данных. Backed enum — это enum, у которого каждому случаю присвоено скалярное значение (строка или целое число). Пример backed enum:
enum PostStatus : string {case Published = “S1”;
case InReview = “S2”;
case Draft = “S3”;
}
Тип значения (string или int) указывается после двоеточия в объявлении enum. Backed enum удобно использовать, когда вам нужно сохранять значение в базе данных или обмениваться им между системами.
Доступ к присвоенному значению осуществляется через свойство value у экземпляра case:
class BlogPostRepository {
public function save(BlogPost $Post) : void {
$this -> insert(
"blog_posts",
[
"headline" => $Post -> Headline,
"content" => $Post -> Content,
"status" => $Post -> Status -> value
]
);
}
}
$post = new BlogPost("Example", "Demo", PostStatus::Published);
(new BlogPostRepository()) -> save($post);В приведённом примере в таблицу сохранится строка S1, соответствующая варианту Published.
Важно:
- Backed enums принимают только
stringилиint. - Каждое значение должно быть уникальным внутри enum.
PHP предоставляет утилиту для восстановления enum по значению:
$status = PostStatus::from($record["status"]);Метод from() выбросит ValueError, если значение не совпадает ни с одним случаем. Если допустимо получить null вместо исключения, используйте tryFrom() — он возвращает null при отсутствии совпадения.
Добавление методов в enums
Enums — это по сути классы, поэтому вы можете добавлять методы, интерфейсы и использовать трейты. Это удобно для инкапсуляции поведения, связанного с конкретными случаями.
enum PostStatus {
case Published;
case Draft;
public function isPubliclyAccessible() : bool {
return ($this instanceof self::Published);
}
}Методы могут быть public, protected и private, но protected и private фактически ведут себя одинаково, потому что enums не поддерживают наследование. Статические методы также поддерживаются и могут вызываться на классе enum или его экземпляре.
Enums могут реализовывать интерфейсы. Это удобно, когда вы хотите, чтобы любой enum, отвечающий за публичный доступ, гарантировал реализацию определённого метода:
enum PostStatus implements PublicAccessGatable {
case Published;
case Draft;
public function isPubliclyAccessible() : bool {
return ($this instanceof self::Published);
}
}Пример использования в коде авторизации:
class UserAuthenticator {
function shouldAllowAccess(PublicAccessGatable $Resource) : bool {
return ($this -> User -> isAdmin() || $Resource -> isPubliclyAccessible());
}
}
$auth = new UserAuthenticator();
// get a blog post from the database
if (!$auth -> shouldAllowAccess($post -> Status)) {
http_response_code(403);
}Константы в enums
Внутри enum допустимы константы, как обычные скалярные, так и константы-ссылки на case:
enum PostStatus {
case Published;
case Draft;
public const Live = self::Published;
public const PlainConstant = "foobar";
}Такой код подчёркивает разницу между самой enum-инстанцией и обычной константой:
$published = PostStatus::Published;
$plain = PostStatus::PlainConstant;Только $published будет удовлетворять типизации PostStatus.
Когда использовать enums
Используйте enums, когда значение переменной должно быть одним из заранее известных вариантов. Примеры:
- Статусы сущностей (черновик, опубликовано, на проверке).
- Роли пользователя, если набор фиксирован.
- Типы событий или состояние конечного автомата.
Проблемы, которые решают native enums:
- Явное указание допустимых значений на уровне типов.
- Исключение ошибок, связанных с передачей произвольных чисел или строк.
- Улучшение автодополнения в IDE и статического анализа.
Прежние подходы (константы в классах, docblock-типизации, внешние пакеты) работают, но требуют дисциплины и дополнительных проверок. Native enums упрощают это.
Примеры и практические сценарии
Ниже — несколько практических рекомендаций и примеров использования enums в реальных задачах.
Пример сериализации / десериализации в базу
Для сохранения в БД используйте backed enum и свойство value. При чтении из БД — from() или tryFrom():
// чтение из БД
$status = PostStatus::tryFrom($row['status']);
if ($status === null) {
// обработать неизвестный статус
}Пример валидации входных данных
При приёме данных от внешнего клиента сразу приводите их к enum через tryFrom() и возвращайте валидационную ошибку, если значение неверно.
Пример взаимодействия с API
Если ваш API возвращает строковые коды статусов, используйте backed enum с string значениями. Это унифицирует обработку и помощь клиентам документацией типов.
Когда enums не подходят или дают ограниченный выигрыш
- Если набор значений динамический или зависит от пользовательских настроек — enum не подойдёт.
- Если значения привязаны к внешним системам, которые могут добавлять новые коды без деплоя — используйте расширяемую модель вместо строгого enum.
- Если вам нужен union type
string|intкак значение — backed enum не поддерживает union.
Важно понимать эти ограничения и выбирать enum только там, где набор вариантов стабилен и понятен.
Important: enums хороши для фиксированных наборов. Для динамических списков используйте справочники в базе или конфигурацию.
Миграция: как безопасно перейти с констант на enums
Ниже — рекомендуемая методика миграции (пошагово).
- Проанализируйте места использования текущих констант. Составьте список файлов и сценариев.
- Введи́те новый enum рядом со старым классом-константой. Сделайте баггер-тесты, которые проверяют конвертацию между ними.
- В коде постепенно заменяйте проверки и приведения типов на enum, оставив обратную совместимость на API-границах (например в контроллерах).
- На этапе миграции добавьте методы-адаптеры:
PostStatusFromLegacy(int $legacy): ?PostStatus. - Обновите сериализацию/десериализацию (API, БД). Для полей БД — сначала сохраняйте оба значения или проводите миграцию данных с откатом.
- Запустите интеграционные тесты и нагрузочные проверки.
- Уберите старые константы после завершения тестирования и деплоя.
Миграция в продакшн должна сопровождаться планом отката — см. раздел «Инцидентный плейбук».
Роль — чек-листы при внедрении enums
Разделённые по ролям чек-листы помогут с распределением ответственности.
Разработчик:
- Создал enum с документированными случаями.
- Добавил unit-тесты на
from()/tryFrom()и граничные случаи. - Обновил места сериализации/десериализации.
- Добавил комментарии о миграции и обратной совместимости.
Код-ревьюер:
- Проверил отсутствие скрытой логики в старых константах.
- Убедился, что enum используется в сигнатурах методов, а не только внутри реализации.
- Проверил наличие тестов и сценариев отката.
DevOps / DBA:
- Проверил стратегию миграции данных для полей в БД.
- Подготовил откатный сценарий для схемы/данных.
- Выделил время для запуска миграционных скриптов в безопасном окне.
Критерии приёмки
- Все public API, которые раньше принимали значения статусов, теперь принимают
PostStatusили корректно докуменированы для обратной совместимости. - Unit- и интеграционные тесты покрывают позитивные и негативные случаи (
from()с невалидным значением,tryFrom()возвращаетnull). - Миграция данных из старого формата в backed-значения проверена на тестовой базе.
- Документация обновлена (README, API-спеки).
Тест-кейсы и сценарии приёма
- TC1: Сериализация —
PostStatus::Published->valueсохраняется в БД какS1. - TC2: Десериализация —
PostStatus::from('S1')возвращаетPostStatus::Published. - TC3: Неправильное значение —
PostStatus::from('X')выбрасываетValueError. - TC4: Безопасное чтение —
PostStatus::tryFrom('X')возвращаетnull. - TC5: Контракт функции — функция, принимающая
PostStatus, отклоняет строку как аргумент.
Decision flowchart
Используйте простую диаграмму для принятия решения — применять enum или нет (Mermaid):
flowchart TD
A[Набор значений фиксирован?] -->|Да| B[Можно использовать enum]
A -->|Нет| C[Использовать справочник/конфигурацию]
B --> D{Значения нужны в БД}
D -->|Да| E[Backed enum 'string|int']
D -->|Нет| F[Pure enum]Советы по безопасности и стабильности
- Не используйте enum для данных, которыми управляют пользователи.
- Для публичных API документируйте, что клиентам нужно ожидать конкретные string/int-коды, если вы используете backed enums.
- При изменении enum избегайте удаления существующих случаев; добавление новых случаев обычно безопасно, удаление — нет.
Совместимость и заметки по версиям
- Enums появились в PHP 8.1 (релиз в ноябре 2021). Они уже доступны в beta- и RC-сборках до релиза.
- Enums зависят от новой поддержки типов и некоторых Reflection-API — проверьте, что целевая среда исполнения обновлена.
- Новые интерфейсы:
UnitEnumдля чистых enums иBackedEnumдля enums с присвоенными значениями. Эти интерфейсы нельзя реализовать вручную — они обеспечиваются ядром.
Edge-case галерея и частые ошибки
- Попытка использовать
string|intдля backed enum — невозможно. - Дублирование значений у backed enum — запрещено.
- Попытка наследовать один enum от другого — запрещено.
- Использование
privateметода в enum имеет ограниченный смысл, поскольку нет наследования.
Инцидентный плейбук: откат изменений связанный с enums
- Включите режим maintenance, при необходимости.
- Восстановите схему и данные из резервной копии, если миграция данных пошла не так.
- Разверните версию приложения, которая использует старые константы (до перехода полностью на enum).
- Выполните дополнительные тесты качества.
- После подтверждения стабильности планируйте повторную миграцию с исправленными шагами.
Факт-бокс: ключевые моменты
- Появление: PHP 8.1, ноябрь 2021.
- Типы значений для backed enums:
stringилиint. - Методы восстановления:
from()(ошибка при несоответствии) иtryFrom()(возвращаетnull). - Новые интерфейсы:
UnitEnum,BackedEnum.
Примеры кода (полный рабочий сценарий)
Ниже пример, собранный вместе для быстрого воспроизведения:
enum PostStatus : string {
case Published = "S1";
case InReview = "S2";
case Draft = "S3";
public function isPubliclyAccessible() : bool {
return $this === self::Published;
}
}
class BlogPost {
public function __construct(
public string $Headline,
public string $Content,
public PostStatus $Status = PostStatus::Draft
) {}
}
class BlogPostRepository {
public function save(BlogPost $Post) : void {
// Пример псевдо-записи в БД
$row = [
'headline' => $Post->Headline,
'content' => $Post->Content,
'status' => $Post->Status->value,
];
// insert into DB ...
}
}
$post = new BlogPost("Example", "Demo", PostStatus::Published);
(new BlogPostRepository())->save($post);Заключение
Enums в PHP 8.1 упрощают модель типов и помогают избежать целого класса ошибок, связанных с передачей произвольных значений. Они гибкие: можно использовать чистые enums для семантики и backed enums для интеграции с внешними системами. При планировании перехода учитывайте ограничения (нет union для values, уникальность значений для backed enum) и заранее подготовьте стратегию миграции и отката.
Короткая памятка:
- Используйте enums для фиксированных наборов.
- Предпочитайте backed enum, если значение хранится или передаётся как строка/число.
- Тестируйте
from()иtryFrom()на граничных случаях.
Ссылки и дополнительные материалы: изучите ReflectionEnum, функции enum_exists() и интерфейсы UnitEnum и BackedEnum в официальной документации PHP для глубокого понимания.
Итоговые выводы
- Enums делают контракт типа явным и облегчают поддержку кода.
- Backed enums удобны для сохранения в БД и API.
- Миграция требует аккуратного плана и тестов.
Похожие материалы
Как установить и настроить Wemux для совместной работы
Как вывести деньги с Payoneer
Пропустить больше 10 секунд в YouTube
Как запретить пересылку писем в Outlook
Как включить и настроить тёмную тему в Windows