PHP 8.1: Enums (Перечисления)

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

Сергей Мухин

Веб-разработчик со стажем программирования более 9 лет, всегда в процессе учебы и созидания.

Есть вопросы?

Я почти всегда в режиме онлайн

Связаться со мной