В 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
. Это может быть полезно при сериализации данных, хранении их в базе данных и т. д.
Перечисления, содержащие значение, т. е. поддерживаемое перечисление, должны:
-
Declare the scalar type in the Enum declaration. Only
string
orint
is allowed. - Assign values for all cases.
-
Hold values of same scalar type. It is not allowed to store
string
andint
values mixed. - 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
типы объединений, ?int
null
?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
gettype
is_object
is_a
get_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
). Попытка использовать неопределенное свойство (в том числе ) в настоящее время вызывает предупреждение в value
PHP 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.