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

Enums в PHP 8.1 — практическое руководство

8 min read PHP Обновлено 23 Nov 2025
Enums в PHP 8.1 — практическое руководство
Enums в PHP 8.1 — практическое руководство

Логотип PHP

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

  • Основной синтаксис

  • Pure vs Backed Enums

  • Добавление методов в enums

  • Константы в enums

  • Когда использовать enums?

  • Миграция и чек-листы

  • Примеры и распространённые ошибки

  • Короткое объявление

Введение

Enums (перечисления) — типы данных, экземпляры которых могут принимать только заранее определённые значения (кейсы). В PHP 8.1 это полноценная часть языка: перечисления интегрированы в систему типов, поддерживают методы, интерфейсы и рефлексию. Коротко: enums повышают безопасность типов и читаемость кода.

Определение термина в одну строку:

  • Enum — именованный набор возможных значений для переменной или свойства.

Важно: enums — не просто «константный класс», это полноценная конструкция языка с поведением, методами и интеграцией в типовую систему.

Основной синтаксис

Ниже — минимальный пример чистого enum, переведённый на корректную форму:

enum PostStatus {
    case Published;
    case InReview;
    case Draft;
}

Ключевое слово case, до появления enums применявшееся в switch, теперь объявляет варианты перечисления. Обращение к варианту происходит как к статическому члену класса:

$published = PostStatus::Published;

Enums совместимы с системой типов. Пример конструктора, принимающего значение enum:

class BlogPost {
    public function __construct(
        public string $Headline,
        public string $Content,
        public PostStatus $Status = PostStatus::Draft
    ) {}
}

// Корректно
$post = new BlogPost(
    "Example Post",
    "An example",
    PostStatus::Draft
);

// Ошибка типов: Argument #3 must be of type PostStatus
$post = new BlogPost(
    "Broken Example",
    "A broken example",
    "Submitted"
);

Первый пример работает: аргумент соответствует варианту enum. Во втором — передаётся строка, а тип требует PostStatus, поэтому PHP выбросит ошибку времени выполнения.

Чтобы получить все значения перечисления, используйте статический метод cases():

PostStatus::cases();
// => [PostStatus::Published, PostStatus::InReview, PostStatus::Draft]

Pure vs Backed Enums

Чистые enums содержат только case-объявления. Backed enums — это перечисления, у которых каждый case связан с конкретным скалярным значением (string или int).

Пример backed enum с типом string:

enum PostStatus : string {
    case Published = "S1";
    case InReview = "S2";
    case Draft = "S3";
}

Зачем это нужно? Backed enum удобно хранить в базе данных: вместо хранения имени кейса можно сохранить короткий код (например, “S1”). Доступ к привязанному значению осуществляется через свойство value:

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);
// В БД сохранится status = "S1"

Ограничения backed enums:

  • Поддерживаются только string и int.
  • Все значения должны быть уникальны в пределах enum.
  • Нельзя использовать union string|int.

Для восстановления enum по значению есть два метода:

$status = PostStatus::from($record['status']); // бросает ValueError при неверном значении
$status = PostStatus::tryFrom($record['status']); // возвращает null при отсутствии соответствия

Добавление методов в enums

Enums — это по сути классы, поэтому в них можно определять методы, использовать трейты и реализовывать интерфейсы. Это удобно для инкапсуляции поведения, связанного с конкретными кейсами.

Пример enum с методом:

enum PostStatus {
    case Published;
    case Draft;

    public function isPubliclyAccessible(): bool {
        return ($this instanceof self::Published);
    }
}

Реализация интерфейса и использование в коде:

enum PostStatus implements PublicAccessGatable {
    case Published;
    case Draft;

    public function isPubliclyAccessible(): bool {
        return ($this instanceof self::Published);
    }
}

class UserAuthenticator {
    public function shouldAllowAccess(PublicAccessGatable $Resource): bool {
        return ($this->User->isAdmin() || $Resource->isPubliclyAccessible());
    }
}

$auth = new UserAuthenticator();
// получить пост из БД
if (!$auth->shouldAllowAccess($post->Status)) {
    http_response_code(403);
}

Замечания по методам в enums:

  • Можно использовать public, protected и private. В реальности protected/private действуют одинаково, потому что enums не наследуются.
  • Нельзя определить конструктор construct() или деструктор destruct().
  • Поддерживаются статические методы, трейты и интерфейсы.

Константы в enums

Enums могут содержать свои константы — как простые скаляры, так и ссылки на кейсы enum:

enum PostStatus {
    case Published;
    case Draft;

    public const Live = self::Published;
    public const PlainConstant = "foobar";
}

Важно понимать разницу:

$published = PostStatus::Published; // это case, тип PostStatus
$plain = PostStatus::PlainConstant; // это строка "foobar"

Только $published удовлетворяет typehint PostStatus. $plain — простой скаляр.

Когда использовать enums?

Enums подходят для ситуаций, где значение должно быть одним из заранее известных вариантов. Примеры:

  • Состояние сущности (post status, order status, task state).
  • Предопределённые роли/права.
  • Форматы вывода (json, xml), каналы коммуникации и т.п.

Проблема традиционного подхода с константами состоит в том, что тип остается общим (int/string) и принимает любые значения. Enums решают это, делая контракт строгим и понятным.

Пример старого подхода и его недостатки:

class PostStatus {
    const Published = 0;
    const Draft = 1;
}

class BlogPost {
    public function __construct(
        public string $Headline,
        public int $Status
    ) {}
}

$post = new BlogPost("My Headline", PostStatus::Published);
// Но ничего не мешает сделать:
$post = new BlogPost("My Headline", 9000);

Док-блоки и внешние библиотеки помогают, но добавляют сложность. Enums — нативный, явный способ обозначить набор допустимых значений.

Примеры использования и шаблоны

  1. Персистентность: использовать backed enum для сохранения компактного кода в БД и восстановления enum через from()/tryFrom().

  2. Поведение: добавлять методы в enum для локальной логики (isPubliclyAccessible, canBeEditedBy(user) и т.д.).

  3. Конфигурация: использовать enum как контракт для конфигураций и feature flags, если набор стабилен.

  4. Интеграция: если внешняя система присылает коды, используйте tryFrom() для безопасного преобразования и ветвления при null.

Когда enums не подходят — примеры неудач

  1. Часто меняющийся набор значений. Если варианты динамически добавляются пользователями, enum неудобен — лучше таблицу в БД и связную логику.

  2. Большие пространства значений. Если нужно сотни уникальных значений с множеством атрибутов — enum быстро станет неудобным.

  3. Полиморфная модель с множеством свойств. Если каждый вариант имеет собственные поля и поведение, возможно, лучше использовать отдельные классы и паттерн State/Strategy.

Альтернативные подходы

  • Класс с константами (традиционный PHP): прост, но слабая типизация.

  • Класс-обёртка с валидацией значений: можно получить преимущества, но придётся писать много шаблонного кода.

  • Библиотеки «fake enum»: дают дополнительные фичи, но ненужны с появлением нативных enums.

Ментальная модель: enums — это «встроенная безопасная множественность», в то время как класс с константами — «неявная множественность».

Модель зрелости (maturity levels)

  • Уровень 0 — константы в классе (небезопасно).
  • Уровень 1 — валидация значений вручную / helper-методы.
  • Уровень 2 — использование внешних библиотек-эмуляторов (fake enums).
  • Уровень 3 — нативные enums в PHP 8.1 (рекомендуется для статичных наборов значений).

Факт-бокс: что важно знать

  • В PHP 8.1 enums добавлены в ноябре 2021.
  • Есть два интерфейса: UnitEnum (чистые enum) и BackedEnum (enum с привязанными значениями).
  • Backed enums поддерживают только string или int.
  • Методы: cases(), from(), tryFrom(), свойство value для backed.
  • enum_exists() и ReflectionEnum доступны для рефлексии.

Миграция: мини-методология и чек-лист

Мини-методология для перевода проекта на enums:

  1. Идентифицировать наборы значений, где допустимые варианты статичны.
  2. Выбрать type: pure или backed (для взаимодействия с БД предпочитайте backed).
  3. Создать enum с тестовым покрытием для всех мест использования.
  4. Обновить сигнатуры методов/свойств на тип enum.
  5. Запустить тесты и отловить все места, где передавались «сырая» строки/числа.
  6. Обновить сериализацию/персистенцию (Repository/ORM mapping).
  7. Проверить совместимость на окружениях и инструментах CI.

Чек-лист ролей:

  • Разработчик: заменить константы на enum, добавить методы инкапсуляции, обновить места вызова.
  • Архитектор: проверить, где enum не подходит и где лучше использовать таблицы/классы.
  • QA: покрыть сценарии восстановления из БД, tryFrom и ошибки ValueError.

Playbook миграции — пошагово

  1. Найдите все классы/файлы, где используются повторяющиеся наборы скалярных констант.
  2. Создайте enum в отдельном файле с тем же набором вариантов.
  3. Сначала замените объявления свойств/параметров в небольших модулях и прогоните тесты.
  4. Для БД: добавьте трансформер в репозиторий, который при записи использует ->value, а при чтении — tryFrom/from.
  5. Обновите документацию и типы в API (OpenAPI/JSON Schema), если применимо.
  6. Разверните на staging и следите за ValueError в логах.
  7. Поэтапно заменяйте по проекту, фиксируя regressions.

Примеры тест-кейсов и критерии приёмки

Тесты, которые нужно покрыть:

  • Создание сущности с корректным enum — проходит.
  • Попытка создать сущность со «сырой» строкой — бросает TypeError.
  • Сохранение в БД записывает корректный код для backed enum.
  • Восстановление через from() и tryFrom() даёт ожидаемые случаи/null.
  • Рефакторинг не нарушил публичный API (если это критично).

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

  • Все тесты проходят локально и в CI.
  • Нет необработанных ValueError в логах после деплоя на staging.
  • Документация API обновлена с указанием новых типов.

Совместимость и заметки по миграции

  • Enums требуют PHP >= 8.1. Для проектов, поддерживающих старые версии, переход потребует планирования миграции интерпретатора.
  • ORM/провайдеры: многие ORM (Doctrine, Eloquent) уже имеют плагины/трансформеры для работы с enums, но может потребоваться обновление адаптеров.
  • CLI/console: при передаче enum через аргументы командной строки, используйте строковые коды и затем tryFrom() для безопасного преобразования.

Безопасность и приватность

Enums сами по себе не добавляют уязвимостей, но изменяют поведение при приведении/сериализации. Обратите внимание:

  • Не доверяйте внешним данным без валидации: при восстановлении через from() обработайте возможный ValueError.
  • Для публичных API документируйте допустимые значения и используйте tryFrom() + явные ошибки 400 вместо необработанных исключений.
  • Если enum-backed значения используются в ссылках/токенах, подумайте о контроле доступа и о том, не раскрывают ли коды лишнюю информацию.

Примеры кода из реального мира

Сохранение и восстановление с Doctrine-подходом (псевдокод):

// трансформер поля status
public function convertToDatabaseValue(PostStatus $status = null) {
    return $status?->value;
}

public function convertToPHPValue($value): ?PostStatus {
    return $value === null ? null : PostStatus::tryFrom($value);
}

CLI-парсер и безопасное преобразование:

$code = $input->getArgument('status');
$status = PostStatus::tryFrom($code);
if ($status === null) {
    throw new 
untimeException("Неверный статус: $code");
}

Частые ошибки при работе с enums

  • Ожидание, что значения enum можно сравнивать с string/int напрямую. Для backed enum используйте ->value.
  • Попытка наследовать enum (не поддерживается).
  • Использование конструкций, которые сериализуют enum как объект без явного преобразования (предпочитайте value при персистенции).

Короткое объявление

PHP 8.1 добавляет native enums — удобный и строгий способ описывать наборы допустимых значений. Enums можно использовать как простые наборы (pure) или с привязанными значениями для персистенции (backed). Они поддерживают методы, интерфейсы и рефлексию. Enums упрощают поддержку и уменьшают количество ошибок, связанных с некорректными значениями. Для проектов, готовых перейти на PHP 8.1, внедрение enums стоит планировать по этапам: определить кандидатов, создать enum, адаптировать репозитории и обновить тесты.

Итоги

  • Enums делают контракт значений явным и проверяемым языком на уровне типов.
  • Для персистенции используйте backed enums и методы from()/tryFrom().
  • Добавляйте поведение в enum через методы и интерфейсы, не распыляйте логику по всему коду.
  • Планируйте миграцию по шагам, тщательно тестируя преобразования и сереализацию.

Важно: enum — мощный инструмент, но он не заменяет базы данных или полиморфные модели там, где наборы значений динамически расширяются или содержат много метаданных.

Дополнительно — чек-листы и шаблоны

  • Чек-лист для разработчика:

    • Найти кандидатов на enum
    • Выбрать pure или backed
    • Создать enum-файл
    • Обновить объявления типов
    • Обновить репозитории/трансформеры
    • Написать тесты на from()/tryFrom
    • Проверить сериализацию
  • Шаблон migration commit message:

    “chore: migrate PostStatus constants to native enum PostStatus (PHP 8.1) — update repositories, tests”

Социальные превью (рекомендации):

  • OG title: Enums в PHP 8.1 — быстрое руководство
  • OG description: Как и когда использовать pure и backed enums в PHP, примеры миграции и советы по персистенции.

Спасибо за внимание — если нужно, могу подготовить готовый diff-мигратор для конкретного репозитория или сгенерировать правила для CI, которые отлавливают передачу «сырых» значений вместо enum.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

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

Серийный номер жёсткого диска в Windows 10
Windows

Серийный номер жёсткого диска в Windows 10

PIN в Windows 10: настройка и безопасность
Windows

PIN в Windows 10: настройка и безопасность

Организация контактов на Android
Android.

Организация контактов на Android

Быстрый запуск MySQL в Docker
DevOps

Быстрый запуск MySQL в Docker

Виджет «Новости и интересы» в Windows 10
Windows

Виджет «Новости и интересы» в Windows 10

Microsoft Store не скачивает игры — как исправить
Windows

Microsoft Store не скачивает игры — как исправить