• Час читання ~6 хв
  • 07.03.2023

PHP 8.1 додає підтримку перерахувань. Нумерація, або скорочено Енум, - це перелічений тип, який має фіксовану кількість можливих значень.

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

На відміну від використання спеціальних рядків або чисел всередині (тобто магічних чисел) для зберігання та роботи з параметрами, Enums робить код програми більш читабельним та уникає несподіваного стану програми.

Синтаксис

Enum PHP 8.1 резервує та використовує enum ключове слово для оголошення Enums. Синтаксис подібний до синтаксису ознаки/класу/інтерфейсу:

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

Enums оголошуються ключовим enum словом, за яким слідує ім'я Enum. Enum може за бажанням оголосити string або int як підкріплені значення. Enums також можуть розширювати клас та/або реалізовувати інтерфейси.

Внутрішньо на рівні аналізатора PHP з'являється новий токен з іменем T_ENUM із присвоєним значенням369.

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

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

Нижче наведено приклад Enum, який оголошує резервний тип цінності та реалізує інтерфейс.

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

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

PHP 8.1 також додає нову enum_exists функцію, щоб перевірити, чи існує даний Enum.

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

Зауважте, що через семантику класів Enums, class_exists функція також повертається true для Enum.

UnitEnum інтерфейс

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

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

Enums не може явно реалізувати цей інтерфейс, оскільки це внутрішньо зроблено движком. Це лише для того, щоб допомогти у визначенні типу даного Enum. Метод UnitEnum::cases повертає масив усіх випадків заданого Enum.

Класи PHP не допускаються до реалізації цього інтерфейсу, і призводить до помилки при спробі

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

:Хоча Enums допускають методи, оголошені на них методи, оголошення методу з іменем cases не допускається:

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

BackedEnum інтерфейс Якщо Enum оголошує скалярні значення, то Enum автоматично отримує інтерфейс

під назвою 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 не допускається, будь-який підтримуваний Enum не повинен оголошувати from a або 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 ...

Оголошення Enums Enums

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

Enums підтримують простори імен, автозавантаження, вони можуть мати методи (але не властивості), реалізацію інтерфейсів і багато інших моделей поведінки, пов'язані з класами PHP.

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

Enums можуть мати нуль або більше випадків

Всередині enum структури він може містити будь-яку кількість «справ», від нуля до необмежених. Обидва ці оголошення Enum дійсні:

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

Enums можуть мати необов'язкові значення Можна призначити string або int значення

кожному випадку в Enum. Це може бути корисно під час серіалізації даних, зберігання їх у базі даних тощо.

Enums, які містять значення, тобто backed Enum, повинні

  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 = '♠';
}

: Backed Enums повинні оголосити скалярний тип Для того, щоб Enum був пов'язаний зі значеннями для кожного випадку (тобто Backed Enum), він повинен оголосити скалярний тип

в декларації Enum.

Якщо цього не зробити, з'явиться помилка:

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 ...

Enums підтримує string int лише скалярні типи. Будь-які інші типи, включаючи bool, , null, або навіть string|int типи об'єднань, ?string?intне допускаються.

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;
}

У наведеному вище фрагменті Enum оголошується таким, що містить string, HTTPMethods але відмінкам значення не присвоюється. Це неприпустимо, і це призводить до помилки.

Резервні перерахування повинні містити значення одного скалярного типу

У резервній копії 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 мають бути унікальними

Цей розділ знаходиться в стадії розробки

Дійсний Enum не повинен містити повторюваних випадків або повторюваних значень. Наприклад, обидва ці оголошення неприпустимі:

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

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

Чутливість

до регістру Сама назва Enum не чутлива до регістру, і вона слідує за тим, як PHP розглядає класи та функції нечутливим до регістру.

Окремі випадки в Enum чутливі до регістру.

enum CaseInSenSitive {
    case bAR;
    case Bar;
}

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

Enums підтримують простори імен. Вони слідують стандартному синтаксису простору імен, який в іншому випадку використовується в класах, рисах, інтерфейсах, функціях тощо.

namespace Foo\Bar;
enum HTTPMethods {}

Автозавантаження

Так само, як PHP підтримує автозавантаження класів, рис та інтерфейсів, Enums також підтримує автозавантаження.

Зауважте, що для цього може знадобитися оновлення генераторів карт класів, які сканують файли на наявність елементів, які можна завантажувати автоматично.

магічні константи

Enums повністю підтримують всі магічні константи, які підтримує PHP для класів.

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

Функції класу/об'єкта та Enums поводяться подібно до класів, коли вони використовуються з функціями, які підтримують перевірку класів та instanceof

об'єктів.

Наприклад, функції , , , і get_class get_debug_type (нове в PHP 8.0) поводяться так, is_objectgettypeis_aніби Enum є стандартним об'єктом 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 з властивостями, методами та ознаками

Enums створені таким чином, щоб він міг порівняти один випадок Enum з іншим. Енуми повинні бути особами без стану, оскільки вони не дозволяють зберігати в них майно.

Enums дозволяють методи

Енумс може містити методи. Вони також підтримують стандартні модифікатори видимості методів, а також статичні методи.

Це можуть бути досить корисні випадки використання, такі як оголошення методу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 можуть реалізовувати інтерфейси

. Enums повинні виконувати контракти інтерфейсів так само, як і стандартний клас.

interface Foo {}
enum FooEnum implements Foo {}

Enums не повинні містити властивостей

Одна з найважливіших відмінностей між Enum і класом полягає в тому, що Enums не дозволяється мати будь-який стан. Оголошення властивостей, або налаштування властивостей не допускається. Не допускаються і статичні властивості.

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 ...:...

Створення екземпляра за допомогою не допускається Хоча випадки Enum самі по собі є об'єктами, не допускається

створення екземплярів їх за 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 "$"

Enums не можуть бути розширені і не повинні успадковуватися Цей розділ знаходиться в процесі розробки

Цей розділ знаходиться в стадії розробки

від іншого Enum або класу.

enum Foo extends Bar {}
Parse error: syntax error, unexpected token "extends", expecting "{" in ... on line ...

Якщо клас спробує розширити Enum, це також призведе до помилки, оскільки всі Enums оголошуються остаточними.

enum Foo {}
class Bar extends Foo {}
Fatal error: Class Bar cannot extend final class Foo in ... on line ...

Enums підтримують риси

без власності Енумс може 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 мати будь-який стан, і щоб забезпечити порівнянність двох Enum, Enums забороняє реалізацію декількох магічних методів:

  • __get(): Запобігти підтримці стану в об'єктах Enum.
  • __set(): Щоб запобігти динамічному присвоєнню та підтримці стану власності.
  • __construct(): Enums не підтримують new Foo() construct all.
  • __destruct(): Перерахування не повинні підтримувати стан.
  • __clone(): Enums - це неклоновані об'єкти.
  • __sleep(): Enums не підтримують методи життєвого циклу.
  • __wakeup(): Enums не підтримують методи життєвого циклу.
  • __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 ...

Classes vs Enums Enums

зберігають фіксовані значення з необов'язковими резервними значеннями, і вони не дозволяють стан. Вони служать абсолютно різним цілям, але 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

Використання Enums Основним випадком використання Enums

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

За допомогою резервних енумів можна зберігати перелічені значення в базі даних або іншому сховищі та відновлювати програмно ідентичний об'єкт.

Порівняння значень

Enum У будь-який момент часу два Enum, що містять однакове значення, вважаються однаковими, так само, як PHP вважає два рядки ідентичними. Це основна відмінність між двома об'єктами, оскільки два об'єкти зі створеного класу не вважаються ідентичними, навіть якщо вони мають однакові значення.

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

Enums як типи параметрів, властивостей та повернення

Enums можна використовувати як типи параметрів, повернення та властивостей. Якщо використовується ім'я Enum, значення Passed/set/return мають бути одним із перелічених значень.

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 значення Enum, це спричинить помилку типу:

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

Це також не дозволяє використовувати значення з іншого Enum:

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

Крім того, якщо використовується невизначене значення Enum, це призведе до невизначеної константи класу, оскільки значення Enum та константи класу мають однаковий синтаксис.

Enum і value властивості Кожен Enum name містить name властивість, яка посилається на ім'я властивості

. Це значення доступне лише для читання.

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

У резервному енумі також 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

Крім того, Enums без резервного значення (UnitEnum) не мають value властивості. Власність мають value лише резервні енуми (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 ...

Отримання всіх значень

Enum І Enums, і 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() повертає масив всіх можливих Enums. Повернуті значення - це самі об'єкти Enum, а не властивості імені або значення.

Отримання Enum за резервним значенням Усі резервні Enums (BackedEnum) підтримують from і 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 не існує за цим значенням.

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

Серіалізація / несеріалізація Enums

Як Backed, так і Unit Enums можна серіалізувати та несеріалізувати за допомогою вбудованих serialize функцій та unserialize функцій.

Серіалізована форма матиме ідентифікатор E, і ім'я Enum.

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. Будь-які серіалізовані рядки Enums не можуть бути несеріалізовані на старих версіях PHP. Спроба зробити це призводить до попередження (PHP Notice: unserialize(): Error at offset ...), і unserialize повернення false.


Зворотна сумісність Enums

використовують новий синтаксис, і неможливо використовувати Enums у версіях PHP до 8.1.

Нативні підходи PHP, такі як myclabs/php-enum, несумісні з перерахуваннями PHP 8.1.

Синтаксис Enum призведе до ParserError < PHP 8.1. Крім того, це неможливо unserialize серіалізований Enum у старих версіях 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