Как правильно реализовать перечисления (Enum) в PHP без встроенной поддержки?
Всем привет! Я начинающий PHP-разработчик, и столкнулся с проблемой. В других языках (Java, C#) есть удобная конструкция enum, которая позволяет задать фиксированный набор констант. Например, статусы заказа: "новый", "в обработке", "отправлен", "доставлен".
В PHP я не нашел встроенного enum. Пытался использовать обычные константы класса:
class OrderStatus {
const NEW = 'new';
const PROCESSING = 'processing';
const SHIPPED = 'shipped';
const DELIVERED = 'delivered';
}
$status = OrderStatus::NEW;
Но это неудобно. Я не могу проверить, что переменная $status содержит только одно из допустимых значений. Можно случайно присвоить $status = 'invalid_status', и никакой ошибки не будет. Также нет возможности добавить методы к каждому значению (например, получить описание статуса на русском).
Как правильно организовать перечисления в PHP? Есть ли какие-то паттерны или библиотеки? Хочется получить типобезопасное решение с возможностью добавлять поведение.
<<Решение: абстрактный класс-перечисление с магическими методами
В PHP нет нативной поддержки enum до версии 8.1, но вы можете создать гибкий и типобезопасный класс-перечисление. Вот проверенный паттерн, который используют в крупных проектах:
abstract class Enum {
private static $cache = [];
private $value;
private $key;
protected function __construct($value) {
$this->value = $value;
$this->key = array_search($value, static::getConstants(), true);
}
final public static function __callStatic($method, $args) {
$constants = static::getConstants();
if (isset($constants[$method])) {
return new static($constants[$method]);
}
throw new \InvalidArgumentException("Недопустимый метод: $method");
}
public function getValue() {
return $this->value;
}
public function getKey() {
return $this->key;
}
public function equals($enum) {
return $this->value === $enum->getValue();
}
public function __toString() {
return (string) $this->value;
}
private static function getConstants() {
$class = get_called_class();
if (!isset(self::$cache[$class])) {
$reflection = new \ReflectionClass($class);
self::$cache[$class] = $reflection->getConstants();
}
return self::$cache[$class];
}
}
// Пример использования
class OrderStatus extends Enum {
const NEW = 'new';
const PROCESSING = 'processing';
const SHIPPED = 'shipped';
const DELIVERED = 'delivered';
}
// Создание через статический вызов
$status = OrderStatus::NEW();
echo $status->getValue(); // 'new'
// Сравнение
if ($status->equals(OrderStatus::NEW())) {
echo "Статус: новый";
}
// Проверка типа
function processOrder(OrderStatus $status) {
// Типобезопасно: только OrderStatus
}
Добавление поведения
Вы можете расширить функциональность, добавив методы в конкретный класс:
class OrderStatus extends Enum {
const NEW = 'new';
const PROCESSING = 'processing';
const SHIPPED = 'shipped';
const DELIVERED = 'delivered';
public function getLabel(): string {
$labels = [
self::NEW => 'Новый заказ',
self::PROCESSING => 'В обработке',
self::SHIPPED => 'Отправлен',
self::DELIVERED => 'Доставлен',
];
return $labels[$this->getValue()] ?? 'Неизвестный статус';
}
public function canCancel(): bool {
return in_array($this->getValue(), [self::NEW, self::PROCESSING], true);
}
}
$status = OrderStatus::SHIPPED();
echo $status->getLabel(); // 'Отправлен'
echo $status->canCancel() ? 'Можно отменить' : 'Нельзя отменить'; // 'Нельзя отменить'
Почему это работает?
- Типобезопасность: функция принимает только
OrderStatus, а не любую строку - Гарантия валидности: экземпляр создаётся только через статические методы, соответствующие константам
- Гибкость: легко добавить методы, связанные с каждым значением
- Производительность: кэширование констант через Reflection
Этот паттерн используется в фреймворках (например, Symfony ChoiceType) и в продакшн-проектах. Он решает проблему "магических строк" и делает код самодокументируемым.
<<Альтернативный подход: использование SplEnum (устарел) и PHP 8.1 нативных Enum
Если вы используете PHP 8.1 и выше, задача решается встроенной конструкцией enum:
enum OrderStatus: string {
case NEW = 'new';
case PROCESSING = 'processing';
case SHIPPED = 'shipped';
case DELIVERED = 'delivered';
public function getLabel(): string {
return match($this) {
self::NEW => 'Новый заказ',
self::PROCESSING => 'В обработке',
self::SHIPPED => 'Отправлен',
self::DELIVERED => 'Доставлен',
};
}
}
$status = OrderStatus::NEW;
echo $status->value; // 'new'
echo $status->getLabel(); // 'Новый заказ'
Для старых версий PHP (7.x) можно использовать SplEnum из PECL, но он не рекомендуется к применению из-за проблем с производительностью и отсутствием поддержки в современных фреймворках.
Рекомендация: если проект на PHP 8.1+, используйте нативные enum. Если на более старой версии — абстрактный класс из первого ответа. Избегайте SplEnum и простых констант без проверок.