PHP 8.1: Enums (Перечисления)
Вот и подошли вкусности, Enums точно появятся!
Встроенная поддержка перечислений будет добавлена в PHP 8.1. Кто-то считает, что перечисления не нужны, а если совсем уж приспичило, то можно воспользоваться сторонними пакетами, как например Spatie/Enum , ну а кто-то, в том числе и я, считает, что PHP не хватает нативного функционала по Enums. И я рад, что контрибьютерам PHP удалось наконец-то воплотить это в реальность.
Итак, Enumeration или для краткости Enum - это перечислимый тип, который имеет фиксированное количество возможных значений.
Например, масти в колоде игральных карт. В колоде есть четыре масти, и они фиксированы: трефы , бубны , черви и пики. В PHP эти масти можно перечислить с помощью Enum:
enum Suit {
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
С Suit Enum теперь возможно принудительно применять типы при принятии или возврате значения масти:
function pick_card(Suit $suit) {}
pick_card(Suit::Clubs);
pick_card(Suit::Diamonds);
pick_card(Suit::Hearts);
pick_card(Suit::Spades);
Или пример использования, что чаще всего мне как раз и приходиться использовать, статусы заказа в интернет-магазине:
enum Status
{
case CREATED;
case COMPLETED;
case CANCELED;
}
Теперь в модели заказа Order мы можем использовать статусы следующим образом:
class Order
{
public function __construct(
public Status $status,
) {}
}
$post = new Order(Status::CREATED);
В отличие от внутреннего использования специальных строк или чисел для хранения и работы с параметрами, перечисления делают код приложения более читабельным и позволяют избежать неожиданного состояния приложения.
Enum синтаксис
PHP 8.1 резервирует и использует ключевое слово enum для объявления Enums. Синтаксис аналогичен синтаксису трейта/класса/интерфейса:
enum Status
{
case CREATED;
case COMPLETED;
case CANCELED;
case REFUNDED;
case FAILED;
}
Чувствительность к регистру
Само имя Enum нечувствительно к регистру , и оно соответствует тому, как PHP обрабатывает классы и функции без учета регистра. Отдельные регистры в Enum нечувствительны к регистру.
enum Status
{
case created;
case Completed;
case canCeleD;
}
Enum методы
Перечисления могут содержать методы, как и классы. Это очень мощная функция, особенно в сочетании с оператором match:
enum Status
{
case CREATED;
case COMPLETED;
case CANCELED;
public function color(): string
{
return match($this)
{
Status::CREATED => 'grey',
Status::COMPLETED => 'green',
Status::CANCELED => 'red'
};
}
}
и далее использовать в коде как:
$status = Status::COMPLETED;
$status->color(); // "green"
Разрешено использовать статические методы. Также обратите внимание, что вы можете использовать в перечислении self:
enum Status
{
// …
public function color(): string
{
return match($this)
{
self::CREATED => 'grey',
self::COMPLETED => 'green',
self::CANCELED=> 'red',
};
}
}
Enum интерфейсы
Перечисления могут реализовывать интерфейсы, как и обычные классы:
interface HasColor
{
public function color(): string;
}
enum Status implements HasColor
{
case CREATED;
case COMPLETED;
case CANCELED;
public function color(): string { /* … */ }
}
Перечисления не должны содержать свойств
Одно из наиболее важных различий между Enum и классом заключается в том, что Enums не могут иметь никакого состояния. Объявление или установка свойств не разрешены. Статические свойства также не допускаются.
enum Foo {
private string $test;
private static string $test2;
}
// Fatal error: Enums may not include member variables in ... on line ...
Кроме того, не допускается динамическая установка свойств:
enum Foo {
case Bar;
}
$bar = Foo::Bar;
$bar->test = 42;
// Error: Enum properties are immutable in ...:...
Enum значения или "Поддерживаемые перечисления"
Изначально значения Enum представлены внутри объектами, но вы можете присвоить им какое-то значение, например для сериализации их в базу данных.
enum Status: string
{
case CREATED = 'created';
case COMPLETED = 'completed';
case CANCELED = 'canceled';
}
Обратите внимание на объявление типа в определении перечисления. Он указывает, что все значения перечисления имеют данный тип. Вы также можете сделать их целочисленными - int. Обратите внимание, что разрешены только типы int и string, как значения перечисления.
enum Status: int
{
case CREATED = 1;
case COMPLETED = 2;
case CANCELED = 3;
}
Технический термин для такого ввода перечислений называется «поддерживаемые перечисления», поскольку они «подкреплены» более простым значением. Если вы решили присвоить значения перечисления, тогда все варианты должны иметь какое-то значение. Вы не можете смешивать и сочетать их. Перечисления без "поддержки" называются "чистыми перечислениями".
Сериализация поддерживаемых перечислений
Если вы присвоите значения enum, вам вероятно понадобится способ их сериализации и десериализации. Их сериализация означает, что вам нужен способ доступа к значению перечисления. Это делается с помощью public свойства value, обратите внимание, что оно только для чтения:
$value = Status::CANCELED->value; // 3
Получить перечисление из значения можно с помощью Enum::from
$status = Status::from(2); // Status::COMPLETED
Также можно использовать tryFrom для возврата null при неизвестном значении. Если бы вы использовали from, то возникло бы исключение.
$status = Status::from('unknown'); // ValueError
$status = Status::tryFrom('unknown'); // null
Вы также можете использовать встроенные функции serialize и unserialize на перечислениях. Кроме того, вы можете использовать json_serialize в сочетании с поддерживаемыми перечислениями, ее результатом будет значение перечисления. Это поведение можно изменить, реализовав JsonSerializable.
Список значений перечисления
Вы можете использовать статический метод cases(), чтобы получить список всех доступных "случаев" в перечислении: Enum::cases()
Status::cases();
// [Status::CREATED, Status::COMPLETED, Status::CANCELED]
Перечисления - это объекты
Как уже выше упоминалось, значения перечислений представлены как объекты, хотя на самом деле это одноэлементные объекты. Это означает, что вы можете сравнивать их так:
$statusA = Status::COMPLETED;
$statusB = Status::COMPLETED;
$statusC = Status::CANCELED;
$statusA === $statusB; // true
$statusA === $statusC; // false
$statusC instanceof Status; // true
Перечисления как ключи массива
Поскольку значения перечислений на самом деле являются объектами, в настоящее время их невозможно использовать в качестве ключей массива. Следующий код приведет к ошибке:
$list = [
Status::CREATED => 'created',
// …
];
Существует RFC для изменения этого поведения, но он еще не был принят.
Это означает, что вы можете использовать только перечисления в качестве ключей в SplObjectStorage и WeakMaps.
Перечисления могут иметь ноль или более значений
Внутри структуры enum может содержаться любое количество case от нуля до бесконечности. Оба этих объявления Enum валидны:
enum ErrorStates {
}
enum HTTPMethods {
case GET;
case POST;
}
Создание экземпляров с помощью new запрещено
Хотя кеймы Enum сами по себе являются объектами, их нельзя создавать с помощью конструкции new. Обе следующие new конструкции не допускаются:
enum Foo {
case Bar;
}
new Foo(); // Fatal error: Uncaught Error: Cannot instantiate enum Foo
new Foo::Bar(); Parse error: syntax error, unexpected identifier "Bar", expecting variable or "$"
Перечисления не могут быть расширены и не должны наследовать
Перечисления объявляются как final, и Enum не может наследоваться от другого Enum или класса.
enum Foo extends Bar {}
// Parse error: syntax error, unexpected token "extends", expecting "{" in ... on line ...
enum Foo {}
class Bar extends Foo {}
// Fatal error: Class Bar may not inherit from final class (Foo) in ... on line ...
Запрещенные магические методы
Чтобы объекты Enum не имели какого-либо состояния и чтобы два Enum были сопоставимы, Enum запрещает реализацию нескольких магических методов. Все следующие объявления магических методов недопустимы.
enum Foo {
public function __get() {}
public function __set() {}
public function __construct() {}
public function __destruct() {}
public function __clone() {}
public function __sleep() {}
public function __wakeup() {}
public function __set_state() {}
}
// Fatal error: Enum may not include __get in ... on line ...
Рефлексии и атрибуты
Как и ожидалось, были добавлены несколько классов рефлексии для работы с перечислениями: ReflectionEnum, ReflectionEnumUnitCase и ReflectionEnumBackedCase. Также есть новая функция enum_exists, которая делает то, что предполагает ее название - проверяет является ли переданное значение именем класса Enum.
Как и обычные классы и свойства, перечисления и их случаи можно аннотировать с помощью атрибутов . Обратите внимание, что фильтр TARGET_CLASS также будет включать перечисления.
И последнее: перечисления имеют свойство только для чтения, которое в RFC упоминается как деталь реализации и, вероятно, должно использоваться только для целей отладки. Однако об этом все же стоит упомянуть. $enum->name
Что думаешь?