CONST умер! Да здравствует ENUM!
Леонид Суматохин
Исторически так сложилось, что в PHP, до недавнего времени, для списочных значений использовались константы. И это казалось вершиной удобства.
class Status {
const ACTIVE = 1;
const INACTIVE = 0;
}
Но время шло, трава становилась всё менее зелёной, а требования к коду – всё выше. Да и ошибки могли нет-нет, да и проскочить при использовании констант:
- Ошибки в значениях: легко случайно присвоить неправильный числовой идентификатор или строку;
- Отсутствие встроенного контроля типов: компилятор или интерпретатор не могли гарантировать правильность передаваемых значений;
И удобного механизма сравнения при работе с константами тоже не имелось: приходилось постоянно сравнивать строки или числа вручную.
if ($status === Status::CANCELLED) { ... }
В данном случае PHP об ошибке не скажет, но и нужного результата мы не получим: константа CANCELLED просто не определена.
Всё это и привело к появлению стандартизированного способа объявления перечислений.
Как всё начиналось
Впервые эта идея появилась примерно в 2017–2018 годах, однако процесс согласования новой функциональности занял достаточно много времени. Причина задержки заключалась в сложности интеграции нового типа данных с существующей экосистемой PHP и необходимостью обеспечить обратную совместимость.
Важным моментом было также согласование интерфейсов и поведения новых конструкций с остальными элементами языка. Необходимо было решить такие вопросы, как:
- Будет ли новая конструкция наследоваться от базового класса?
- Какие операции будут разрешены над экземплярами перечислений?
- Нужно ли поддерживать возможность кастинга между разными типами?
В итоге полноценная реализация перечислений состоялась в PHP 8.1, выпущенном осенью 2021 года. Эта версия добавила новый специальный тип данных — enum.
Теперь вместо обычных констант стали использоваться настоящие объекты, обладающие строгими правилами безопасности и чёткими возможностями проверки типов:
enum Status {
case Active;
case Inactive;
}
В PHP перечисления относятся к виду объектов. Сами перечисления — классы, а каждый возможный случай перечисления — объект одного экземпляра этого класса. Поэтому варианты перечисления — допустимые объекты, которые будут работать там же, где работают объекты, включая проверки типов.
Что умеет ENUM
Перечисления могут быть обычные/чистые:
enum Status {
case Active;
case Inactive;
}
А могут быть типизированными:
enum Status: string {
case Active = ‘active’;
case Inactive = ‘inactive’;
}
Причём перечисления могут быть или только чистыми или только типизированными, а их пересечение не допускается.
Так же есть ограничения и по типу скалярных значений для типизированных перечислений – это integer и string. При этом типы, в рамках одного enum, не могут пересекаться.
Значения в рамках одного enum могут быть или только integer или только stringТак же типизированные enum имеют два специфичных метода:
from(int|string): selfвозьмёт скаляр и вернёт вариант перечисления, которому он принадлежит.tryFrom(int|string): ?selfвозьмёт скаляр и вернёт вариант перечисления, которому он принадлежит. Если вариант, который соответствует варианту перечисления, не найден, метод вернёт null.
А ещё в перечислениях разрешено объявлять публичные, приватные и защищённые методы. Они могут реализовывать интерфейсы, разрешено использование констант и трейтов, вот наследование перечислений не разрешено.
Благодаря встроенному методу cases, enum удобно перебирать:
foreach (Status::cases() as $status) {
echo $status->value;
}
Например для построения выпадающих списков.
Так же enum можно использовать для подсказки типов в функциях:
function setStatus(Status $status) {
// сохранение статуса в базу данных
}
Благодаря этому аргумент метода ожидает опцию только из перечисления Status.
И ещё один не маловажный аспект перечислений – это уникальность их значений.
----------------
Таким образом применение перечислений облегчает разработку, снижает вероятность ошибок и делает приложение стабильнее и надежнее, кроме того повысилась производительность, поскольку интерпретация объектов стала эффективнее, чем работа с обычными строковыми или целочисленными константами. Поэтому использование перечислений можно считать лучшей практикой в современном PHP.
В качестве послесловия
Я при разработке к enum добавляю удобный trait, который прокачивает enum ещё немного:
interface EnumArrayableInterface
{
public static function names(): array;
public static function values(): array;
public static function array(): array;
}
trait EnumAsArray
{
public static function names(): array
{
return array_column(self::cases(), 'name');
}
public static function values(): array
{
return array_column(self::cases(), 'value');
}
public static function array(): array
{
return array_combine(self::names(), self::values());
}
}
enum ReportStatus: string implements Arrayable
{
use EnumAsArray;
case NEW = 'new';
case PROCESSED = 'processed';
case DONE = 'done';
}
Источники: Документация, Medium