Гид по технологиям

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

8 min read PHP Обновлено 02 Dec 2025
Enums в PHP 8.1: руководство и примеры
Enums в PHP 8.1: руководство и примеры

Логотип PHP

Быстрые ссылки

  • Базовый синтаксис
  • 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

Ниже — рекомендуемая методика миграции (пошагово).

  1. Проанализируйте места использования текущих констант. Составьте список файлов и сценариев.
  2. Введи́те новый enum рядом со старым классом-константой. Сделайте баггер-тесты, которые проверяют конвертацию между ними.
  3. В коде постепенно заменяйте проверки и приведения типов на enum, оставив обратную совместимость на API-границах (например в контроллерах).
  4. На этапе миграции добавьте методы-адаптеры: PostStatusFromLegacy(int $legacy): ?PostStatus.
  5. Обновите сериализацию/десериализацию (API, БД). Для полей БД — сначала сохраняйте оба значения или проводите миграцию данных с откатом.
  6. Запустите интеграционные тесты и нагрузочные проверки.
  7. Уберите старые константы после завершения тестирования и деплоя.

Миграция в продакшн должна сопровождаться планом отката — см. раздел «Инцидентный плейбук».

Роль — чек-листы при внедрении enums

Разделённые по ролям чек-листы помогут с распределением ответственности.

Разработчик:

  • Создал enum с документированными случаями.
  • Добавил unit-тесты на from()/tryFrom() и граничные случаи.
  • Обновил места сериализации/десериализации.
  • Добавил комментарии о миграции и обратной совместимости.

Код-ревьюер:

  • Проверил отсутствие скрытой логики в старых константах.
  • Убедился, что enum используется в сигнатурах методов, а не только внутри реализации.
  • Проверил наличие тестов и сценариев отката.

DevOps / DBA:

  • Проверил стратегию миграции данных для полей в БД.
  • Подготовил откатный сценарий для схемы/данных.
  • Выделил время для запуска миграционных скриптов в безопасном окне.

Критерии приёмки

  1. Все public API, которые раньше принимали значения статусов, теперь принимают PostStatus или корректно докуменированы для обратной совместимости.
  2. Unit- и интеграционные тесты покрывают позитивные и негативные случаи (from() с невалидным значением, tryFrom() возвращает null).
  3. Миграция данных из старого формата в backed-значения проверена на тестовой базе.
  4. Документация обновлена (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

  1. Включите режим maintenance, при необходимости.
  2. Восстановите схему и данные из резервной копии, если миграция данных пошла не так.
  3. Разверните версию приложения, которая использует старые константы (до перехода полностью на enum).
  4. Выполните дополнительные тесты качества.
  5. После подтверждения стабильности планируйте повторную миграцию с исправленными шагами.

Факт-бокс: ключевые моменты

  • Появление: 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.
  • Миграция требует аккуратного плана и тестов.
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Как установить и настроить Wemux для совместной работы
DevOps

Как установить и настроить Wemux для совместной работы

Как вывести деньги с Payoneer
Финансы

Как вывести деньги с Payoneer

Пропустить больше 10 секунд в YouTube
Гайды

Пропустить больше 10 секунд в YouTube

Как запретить пересылку писем в Outlook
Безопасность почты

Как запретить пересылку писем в Outlook

Как включить и настроить тёмную тему в Windows
Windows

Как включить и настроить тёмную тему в Windows

Будильник, таймер и секундомер на Android
Android.

Будильник, таймер и секундомер на Android