• Время чтения ~6 мин
  • 07.03.2023

В PHP 8.1 добавлена поддержка перечислений. Перечисление, или сокращенно Enum, — это перечисляемый тип с фиксированным числом возможных значений.

Популярная аналогия для 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);

В отличие от внутреннего использования специальных строк или чисел (т. е. магических чисел) для хранения параметров и работы с ними, перечисления делают код приложения более удобочитаемым и позволяют избежать неожиданного состояния приложения.

Синтаксис

перечисления PHP 8.1 резервирует и использует enum ключевое слово для объявления перечислений. Синтаксис аналогичен синтаксису черты/класса/интерфейса:

enum Suit {
    case Clubs;
    case Diamonds;
    case Hearts;
    case Spades;
}

Перечисления объявляются с enum помощью ключевого слова, за которым следует имя перечисления. Перечисление может быть дополнительно объявлено string или int в виде резервных значений. Перечисления также могут расширять класс и/или реализовывать интерфейсы.

Внутренне на уровне парсера PHP есть новый токен с именем T_ENUM и назначенным значением369.

Перечисления также могут содержать значение для каждого случая, что делает их перечислениями с обеспечением.

enum HTTPMethods: string {
    case GET = 'get';
    case POST = 'post';
}

Ниже приведен пример перечисления, в котором объявляется поддерживаемый тип значения и реализуется интерфейс.

enum RolesClassLikeNamespacedEnum: string implements TestFor {  
  case Admin = 'Administrator';  
  case Guest = 'Guest';  
  case Moderator = 'Moderator';  
}

Новая enum_exists функция

PHP 8.1 также добавляет новую enum_exists функцию для проверки существования данного перечисления.

function enum_exists(string $enum, bool $autoload = true): bool {}

Обратите внимание, class_exists что из-за семантики классов Enums функция также возвращает true Enum.

UnitEnum интерфейс

Перечисления, не подкрепленные значением, автоматически реализуют UnitEnum интерфейс.

interface UnitEnum {
    public static function cases(): array;
}   

Enums не может явно реализовать этот интерфейс, так как это делается внутренне движком. Это делается только для того, чтобы помочь определить тип данного перечисления. Метод UnitEnum::cases возвращает массив всех случаев заданного перечисления.

Классам PHP не разрешается реализовывать этот интерфейс, что приводит к ошибке при попытке:Несмотря на то, что перечисления допускают объявленные в них методы, объявление метода с именем не допускается:BackedEnum

class FakeEnum implements UnitEnum {}
Fatal error: Non-enum class FakeEnum cannot implement interface UnitEnum in ... on line ...

enum Foo {
    public function cases(): array{}
}
Fatal error: Cannot redeclare Foo::cases() in ... on line ...

интерфейс

Если Enum объявляет скалярные значения, то Enum автоматически является интерфейсом с именем cases BackedEnum. Как и в случае с UnitEnum интерфейсом, невозможно явно реализовать BackedEnum интерфейс.

interface BackedEnum extends UnitEnum {
    public static function from(int|string $value): static;
    public static function tryFrom(int|string $value): ?static;
}

Ознакомьтесь BackedEnum::from с BackedEnum::tryFrom информацией об их использовании.

Стандартные классы не могут реализовать этот интерфейс.

class FakeEnum implements BackedEnum {}
Fatal error: Non-enum class FakeEnum cannot implement interface BackedEnum in ... on line ...

Подобно ограничению на запрет метода с именем cases не допускается, любое поддерживаемое перечисление не должно объявлять метод или from tryFrom или:

enum Foo: int {
    public function from() {}
    public function tryFrom() {}
}
Fatal error: Cannot redeclare Foo::from() in ... on line ...
Fatal error: Cannot redeclare Foo::tryFrom() in ... on line ...

Объявление перечислений Перечисления

внутренне реализованы в лучших классах PHP и наследуют большую часть семантики класса с дополнительными ограничениями.

Перечисления поддерживают пространства имен, автозагрузку, они могут иметь методы (но не свойства), реализацию интерфейсов и многие другие варианты поведения, связанные с классами PHP.

Базовое перечисление — это просто структураenum, в которой каждый падеж объявляется ключевым case словом. Благодаря перечислениям, поддерживаемым в PHP 8.1, PHP теперь резервируется "enum" как зарезервированное слово и предотвращает создание каких-либо функций, классов, интерфейсов и т. д. с помощью enum. Он может быть частью пространства имен из-за изменений в том, как PHP рассматривает зарезервированные ключевые слова в значениях пространства имен.

В перечислениях может быть ноль или более падежей

Внутри enum структуры он может содержать любое количество «кейсов», от нуля до неограниченного. Оба этих объявления Enum являются допустимыми:

enum ErrorStates {
}
enum HTTPMethods {
    case GET;
    case POST;
}

Enums могут иметь необязательные значения

Каждому случаю в перечислении можно присвоить значение a string илиint. Это может быть полезно при сериализации данных, хранении их в базе данных и т. д.

Перечисления, содержащие значение, т. е. поддерживаемое перечисление, должны:

  1. Declare the scalar type in the Enum declaration. Only string or int is allowed.
  2. Assign values for all cases.
  3. Hold values of same scalar type. It is not allowed to store string and int values mixed.
  4. Cases and assigned values are unique
enum Suit: string {
    case Clubs = '♣';
    case Diamonds = '♦';
    case Hearts = '♥';
    case Spades = '♠';
}

Поддерживаемые перечисления должны объявлять скалярный тип Для того, чтобы перечисление связывало значения для каждого случая (т. е. Подкрепленное перечисление), оно должно объявить скалярный тип

в объявлении перечисления.

Если этого не сделать, возникнет ошибка:

enum HTTPMethods {
    case GET = 'get';
    case POST = 'post';
}
Fatal error: Case GET of non-scalar enum HTTPMethods must not have a value in ... on line ...

Перечисляет только вспомогательные string и int скалярные типы. Любые другие типы, включая bool, , , , или даже string|int типы объединений, ?intnull?stringне допускаются.

enum HTTPMethods: object {
    case GET = 'get';
    case POST = 'post';
}
Enum backing type must be int or string, object given in ... on line ...

Поддерживаемые перечисления должны присваивать значения для всех случаев

Этот раздел находится в стадии разработки

Если Enum объявляет скалярный тип значений, он должен задать значение для всех случаев:

enum HTTPMethods: string {
    case GET;
    case POST;
}

В приведенном выше HTTPMethods фрагменте Enum объявляется содержащим , но падежам stringне присваивается значение. Это не допускается и приводит к ошибке.

Поддерживаемые перечисления должны содержать значения одного и того же скалярного типа

При использовании Backed Enum значения, назначенные каждому регистру, должны иметь тот же тип, что и объявленный в типе.

PHP строго следит за этим, даже если strict_types не включен.

enum HTTPMethods: int {
    case GET = 1;
    case POST = '2';
}
Fatal error: Enum case type string does not match enum scalar type int in ... on line ...

Поддерживаемые регистры и значения перечисления должны быть уникальными

Этот раздел находится в стадии разработки

Допустимое перечисление не должно содержать повторяющихся регистров или повторяющихся значений. Например, оба этих объявления недопустимы:

enum Test {
    case FOO;
    case FOO;
}
enum Test: string {
    case FOO = 'baz';
    case BAR = 'baz';
}

Значения Enum также должны быть уникальными, так как BackedEnum::from поддерживает извлечение объекта Enum из заданного скалярного значения.

Чувствительность к

регистру Само имя перечисления нечувствительно к регистру, и оно следует за тем, как PHP обрабатывает классы и функции без учета регистра.

Отдельные регистры в перечислении чувствительны к регистру.

enum CaseInSenSitive {
    case bAR;
    case Bar;
}

Семантика классов в пространствах

имен перечислений

Перечисления поддерживают пространства имен. Они следуют стандартному синтаксису пространства имен, который в противном случае используется в классах, признаках, интерфейсах, функциях и т. д.

namespace Foo\Bar;
enum HTTPMethods {}

Автозагрузка

Точно так же, как PHP поддерживает автозагрузку классов, признаков и интерфейсов, Enums также поддерживает автозагрузку.

Обратите внимание, что для этого могут потребоваться обновления генераторов сопоставлений классов, которые сканируют файлы на наличие автоматически загружаемых элементов.

Магические константы

Enums полностью поддерживают все магические константы, которые PHP поддерживает для классов.

  • ::class константа, относящаяся к имени самого перечисления.
  • __CLASS__ магическая константа, которая относится к имени Энума изнутри Энума.
  • __FUNCTION__ в контексте метода Enum.
  • __METHOD__ в контексте метода Enum.

Функции классов/объектов и перечисления ведут себя аналогично классам, когда они используются с функциями, поддерживающими проверку классов и instanceof

объектов.

Например, функции , , , и (новые в PHP 8.0) ведут себя так,get_debug_type gettypeis_objectis_aget_class как если бы перечисление было стандартным объектом PHP.

enum Suit {
    case Clubs;
    case Diamonds;
    case Hearts;
    case Spades;
}
gettype(Suit::Clubs); // "object"
is_object(Suit::Spades); // true
is_a(Suit::Clubs, Suit::class); // true
get_class(Suit::Clubs); // "Suit"
get_debug_type(Suit::Clubs); // "Suit"
Suit::Clubs instanceof Suit; // true
Suit::Clubs instanceof UnitEnum; // true
Suit::Clubs instanceof object; // false

Перечисления со свойствами, методами и признаками

Перечисления создаются таким образом, чтобы можно было сравнивать один случай перечисления с другим. Перечисления должны быть без сохранения состояния, так как они не позволяют хранить в них свойства.

Методы

Enums allow Перечисления могут содержать методы. Они также поддерживают стандартные модификаторы видимости методов, а также статические методы.

Это может быть весьма полезным вариантом использования, таким как объявление методаlabel(): string, возвращающего удобную метку.

enum HTTPStatus: int {
    case OK = 200;
    case ACCESS_DENIED = 403;
    case NOT_FOUND = 404;
    public function label(): string {
        return static::getLabel($this);
    }
    public static function getLabel(self $value): string {
        return match ($value) {
            HTTPStatus::OK => 'OK',
            HTTPStatus::ACCESS_DENIED => 'Access Denied',
            HTTPStatus::NOT_FOUND => 'Page Not Found',
        };
    }
}
echo HTTPStatus::ACCESS_DENIED->label(); // "Access Denied"
echo HTTPStatus::getLabel(HTTPStatus::ACCESS_DENIED); // "Access Denied"

В приведенном выше фрагменте используются match выражения, добавленные в PHP 8.0

Enums могутреализовывать интерфейсы Enums могут реализовывать интерфейсы

. Перечисления должны выполнять контракты интерфейсов так же, как и стандартный класс.

interface Foo {}
enum FooEnum implements Foo {}

Перечисления не должны содержать свойств

Одно из наиболее важных различий между перечислением и классом заключается в том, что перечислениям не разрешается иметь какое-либо состояние. Объявление свойств или задание свойств не допускается. Статические свойства также не допускаются.

enum Foo {
    private string $test;
    private static string $test2;
}
Fatal error: Enums may not include properties in ... on line ...

Кроме того, динамическая установка свойств также не допускается:

enum Foo {
    case Bar;
}
$bar = Foo::Bar;
$bar->test = 42;
Error: Error: Cannot create dynamic property Foo::$test in ...:...

Создание экземпляров с new помощью не допускается

Хотя сами случаи перечисления являются объектами, их создание с помощью 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 "$"

Перечисления не могут быть расширены и не должны наследовать

Этот раздел находится в стадии разработки

Перечисления внутренне объявляются окончательными, и перечисление не может наследоваться от другого перечисления или класса.

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 cannot extend final class Foo in ... on line ...

Перечисления поддерживают черты

без свойств Перечисления могут use быть признаками, если признак не объявляет никаких свойств.

trait NamedDocumentStatus {
    public function getStatusName(): string {}
}
enum DocumentStats {
    use NamedDocumentStatus;
    case DRAFT;
    case PUBLISHED;
}

Если используемые черты содержат какие-либо свойства (статические или иные), использование этого признака приводит к неустранимой ошибке:Запрещенные магические методы

trait NotGood {
    public string $value;
}
enum Foo {
    use NotGood;
}
Fatal error: Enum "Foo" may not include properties in ... on line ...

Чтобы объекты Enum не имели какое-либо состояние и чтобы обеспечить сопоставимость двух перечислений, Enums запрещают реализацию нескольких магических методов:

  • __get(): Чтобы предотвратить сохранение состояния в объектах перечисления.
  • __set(): для предотвращения динамического назначения свойств и поддержания состояния.
  • __construct(): Enums не поддерживают new Foo() конструкцию all.
  • __destruct(): Перечисления не должны поддерживать состояние.
  • __clone(): Перечисления являются неклонируемыми объектами.
  • __sleep(): Перечисления не поддерживают методы жизненного цикла.
  • __wakeup(): Перечисления не поддерживают методы жизненного цикла.
  • __set_state(): Чтобы предотвратить приведение состояния к объектам 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 ...

Классы против перечислений

Перечисления хранят фиксированные значения с необязательными поддерживаемыми значениями и не разрешают состояние. Они служат совершенно разным целям, но Enums имеют некоторую общую семантику с классами.

Classes Enums
Syntax class Foo {} enum Foo {}
Properties
Static Properties
Methods
Static Methods
Autoloading
Instantiating: new Foo()
Implement interfaces
Inheritance: Foo extends Bar
Magic Constants: ::class, __CLASS__, etc.
Object Comparison Foo === Foo Not equal Equals
Traits Supports Supports without properties

Использование перечислений Основным вариантом использования перечислений

является безопасность типов. Движок PHP гарантирует, что переданное или возвращенное значение принадлежит одному из разрешенных значений. Без необходимости проверять принадлежность передаваемых значений к одному из разрешенных типов, движок PHP применяет его, а также позволяет IDE и статическим анализаторам также выделять потенциальные ошибки.

С помощью резервных перечислений можно хранить перечисляемые значения в базе данных или другом хранилище, а также восстанавливать программно идентичный объект.

Сравнение значений

перечисления В любой момент времени два перечисления, содержащие одно и то же значение, считаются идентичными, точно так же, как PHP считает две строки идентичными. Это основное различие между двумя объектами, так как два объекта из созданного экземпляра класса не считаются идентичными, даже если они содержат одинаковые значения.

new stdClass() === new stdClass(); // false
Suit::Hearts === Suit::Hearts; // true

Перечисления в виде типов параметров, свойств и возвращаемых значений

Перечисления можно использовать в качестве типов параметров, возвращаемых данных и свойств. При использовании имени перечисления передаваемые/устанавливаемые/возвращаемые значения должны быть одним из перечисляемых значений.

enum Suit {
    case Clubs;
    case Diamonds;
    case Hearts;
    case Spades;
}
function play(Suit $suit, string $value) {}

Функция play принимает перечисляемое значение Suit, а передача любого другого значения приведет к значению \TypeError. Это может значительно улучшить код, поскольку PHP проверяет переданные значения без необходимости писать дополнительные проверки внутри play функции.

play(Suit::Clubs, 'J');
play(Suit::Hearts, 'Q');

Если будет передано какое-либо значение, отличное от Suit значения перечисления, это вызовет ошибку типа:

play('clubs', 'J');
TypeError: play(): Argument #1 ($suit) must be of type Suit, string given

Это также не позволяет использовать значения из другого перечисления:

play(UnoColors::Blue, 'J');
TypeError: play(): Argument #1 ($suit) must be of type Suit, UnoColors given

Кроме того, если используется неопределенное значение перечисления, это приведет к неопределенной константе класса, поскольку значения перечисления и константы класса имеют один и тот же синтаксис.

Перечисление name и value свойства Каждое перечисление содержит свойствоname, которое ссылается на имя свойства

. Это значение доступно только для чтения.

enum Suit {
    case Clubs;
    case Diamonds;
    case Hearts;
    case Spades;
}
echo Suit::Clubs->name; // "Clubs"

В Backed Enum также value есть свойство для поддерживаемого значения (либо или string int).

enum Suit: string {
    case Clubs = '♣';
    case Diamonds = '♦';
    case Hearts = '♥';
    case Spades = '♠';
}
echo Suit::Clubs->name; // "Clubs"
echo Suit::Clubs->value; // "♣"

Попытка изменить name свойства или value приведет к ошибке:

Suit::Clubs->name = 'Hearts';
Cannot use temporary expression in write context in

Кроме того, перечисления без поддерживаемого значения (UnitEnum) не имеют свойства.value Свойство есть value только у Backed Enums (BackedEnum). Попытка использовать неопределенное свойство (в том числе ) в настоящее время вызывает предупреждение в valuePHP 8.1.

enum Suit {
    case Clubs;
    case Diamonds;
    case Hearts;
    case Spades;
}
echo Suit::Clubs->value;
Warning: Undefined property: Suit::$value in ... on line ...

Получение всех значений перечисления

Оба UnitEnum и BackedEnum Enums поддерживают ::cases метод, который возвращает все значения Enum.

enum Suit {
    case Clubs;
    case Diamonds;
    case Hearts;
    case Spades;
}
Suit::cases();
// [Suit::Clubs, Suit::Diamonds, Suit::Hearts, Suit::Spaces]

Метод Suit::cases() возвращает массив всех возможных перечислений. Возвращаемые значения являются самими объектами перечисления, а не свойствами имени или значения.

Получение перечисления по значению с резервным копированием

Поддержка всех поддерживаемых from перечислений (BackedEnum) и tryFrom методы, позволяющие получить экземпляр из резервного значения.

enum Suit: string {
    case Clubs = '♣';
    case Diamonds = '♦';
    case Hearts = '♥';
    case Spades = '♠';
}
$clubs = Suit::from('♥');
var_dump($clubs); // enum(Suit::Hearts)
echo $clubs->name; // "Hearts";
echo $clubs->value; // "♥"

Если по этому значению нет Enum, PHP выдаст исключениеValueError.


Существует также tryFrom метод, который возвращаетnull, если перечисление не существует по этому значению.

enum Suit: string {
    case Clubs = '♣';
    case Diamonds = '♦';
    case Hearts = '♥';
    case Spades = '♠';
}
$clubs = Suit::tryFrom('not-existing');
var_dump($clubs); // null

Сериализация/десериализация перечислений

Как резервные, так и модульные перечисления могут быть сериализованы и несериализованы с помощью встроенных serialize функций и unserialize функций.

Сериализованная форма будет иметь идентификатор Eи имя перечисления.

enum PostStatus {
    case DRAFT;
}
serialize(PostStatus::DRAFT);
E:16:"PostStatus:DRAFT";

В Backed Enums сериализованные значения по-прежнему являются именем элемента; не резервное значение:

enum FooEnum: string {
    case Foo = 'bartest';
}
serialize(FooEnum::Foo)
E:11:"FooEnum:Foo";

Обратите внимание, что синтаксис десериализации несовместим со старыми версиями PHP. Любые сериализованные строки перечислений не могут быть десериализованы в старых версиях PHP. Попытка сделать это приводит к предупреждению (PHP Notice: unserialize(): Error at offset ...) и возврату unserialize false.


Перечисления, влияющие на

обратную совместимость, используют новый синтаксис, и невозможно использовать перечисления в версиях PHP до 8.1.

Собственные подходы PHP, такие как myclabs/php-enum, несовместимы с перечислениями PHP 8.1.

Синтаксис Enum приведет к появлению в ParserError PHP < 8.1. Кроме того, сериализованный Enum невозможенunserialize в старых версиях PHP.


Реализация обсуждения RFC

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297