Реализация перечислений (Enum) в PHP: как правильно сделать?
Правильная реализация Enum в PHP (для версий до 8.1)
Привет! Ты абсолютно прав — константы через define() — это устаревший и небезопасный подход. Даже в PHP 7.4 можно сделать полноценные перечисления через паттерн ValueObject. Вот проверенное решение, которое используется в крупных проектах (Symfony, Laravel).
Базовый класс Enum
<?php
abstract class Enum
{
protected static array $cache = [];
protected $value;
final public function __construct($value)
{
if (!in_array($value, static::values(), true)) {
throw new \InvalidArgumentException(
sprintf('Недопустимое значение "%s" для перечисления %s',
$value, static::class)
);
}
$this->value = $value;
}
final public function getValue()
{
return $this->value;
}
final public static function values(): array
{
$class = static::class;
if (!isset(static::$cache[$class])) {
$ref = new \ReflectionClass($class);
static::$cache[$class] = array_values($ref->getConstants());
}
return static::$cache[$class];
}
final public static function isValid($value): bool
{
return in_array($value, static::values(), true);
}
final public function __toString(): string
{
return (string) $this->value;
}
}
Пример конкретного перечисления
<?php
class OrderStatus extends Enum
{
const NEW = 'new';
const PROCESSING = 'processing';
const SHIPPED = 'shipped';
const DELIVERED = 'delivered';
const CANCELLED = 'cancelled';
// Дополнительные методы
public function isActive(): bool
{
return in_array($this->value, [
self::NEW,
self::PROCESSING,
self::SHIPPED
], true);
}
public function label(): string
{
$labels = [
self::NEW => 'Новый',
self::PROCESSING => 'В обработке',
self::SHIPPED => 'Отправлен',
self::DELIVERED => 'Доставлен',
self::CANCELLED => 'Отменён',
];
return $labels[$this->value] ?? $this->value;
}
// Статические методы для удобства
public static function new(): self
{
return new self(self::NEW);
}
public static function processing(): self
{
return new self(self::PROCESSING);
}
}
Использование в коде
<?php
// Создание
$status = new OrderStatus(OrderStatus::NEW);
$status = OrderStatus::new(); // более читаемо
// Проверка типов (строгая)
function processOrder(OrderStatus $status): void {
if ($status->isActive()) {
echo "Заказ активен: " . $status->label();
}
}
// Получение всех значений для формы
$statuses = OrderStatus::values(); // ['new','processing',...]
// Безопасное приведение из БД
$dbValue = 'shipped';
$status = new OrderStatus($dbValue); // выбросит исключение, если значение невалидно
Преимущества этого подхода
- Type-hinting — функция принимает только объект OrderStatus, а не любое число/строку
- Самодокументирование — IDE подсказывает доступные константы
- Безопасность — конструктор выбрасывает исключение при невалидном значении
- Расширяемость — можно добавлять методы (label, isActive, canCancel)
- Сериализация — легко сохранить в БД через getValue() и восстановить через конструктор
Совет по миграции на PHP 8.1+
Когда обновитесь, замените наследование от вашего Enum на нативный enum — структура останется похожей:
<?php
enum OrderStatus: string {
case NEW = 'new';
case PROCESSING = 'processing';
// ...методы остаются почти теми же
}
<<Альтернативный подход: через SplEnum (устарел) или библиотеки
В PHP 7.x существовал встроенный класс SplEnum, но он был объявлен устаревшим в PHP 8.0 и удалён в PHP 8.1. Поэтому использовать его не рекомендую — при обновлении сломается код.
Если не хотите писать свой класс, можно использовать готовые библиотеки:
- myclabs/php-enum — популярная библиотека, работает начиная с PHP 7.4. Просто наследуете свой класс от
MyCLabs\Enum\Enumи объявляете константы. Поддерживает type-hinting, методыvalues(),isValid(). - spatie/enum — более современная библиотека от создателей Laravel-пакетов. Поддерживает магические методы (например,
OrderStatus::NEW()), строгую типизацию, можно добавлять произвольные методы.
Пример с spatie/enum:
<?php
use Spatie\Enum\Enum;
/**
* @method static self NEW()
* @method static self PROCESSING()
*/
class OrderStatus extends Enum
{
protected static function values(): array
{
return [
'NEW' => 'new',
'PROCESSING' => 'processing',
];
}
}
// Использование
$status = OrderStatus::NEW();
echo $status->value; // 'new'
Библиотеки удобны тем, что сразу дают готовую реализацию с тестами и документацией. Но если проект небольшой, проще написать свой класс Enum — так вы будете полностью контролировать поведение и не зависеть от сторонних зависимостей.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги