• Czas czytania ~15 min
  • 07.03.2023

PHP 8.1 dodaje obsługę wyliczeń. Wyliczenie, w skrócie Enum, jest typem wyliczeniowym, który ma stałą liczbę możliwych wartości.

Popularną analogią dla Enum są garnitury w talii kart do gry. Talia kart do gry ma cztery kolory i są one stałe: trefle, diamenty, serca i pik.

W PHP te kolory można wyliczyć za pomocą Enum:

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

Dzięki Suit Enum możliwe jest teraz wymuszanie typów podczas akceptowania lub zwracania wartości koloru:

function pick_card(Suit $suit) {}
pick_card(Suit::Clubs);
pick_card(Suit::Diamonds);
pick_card(Suit::Hearts);
pick_card(Suit::Spades);

W przeciwieństwie do używania specjalnych ciągów lub liczb wewnętrznie (np. liczb magicznych) do przechowywania i pracy z parametrami, wyliczenia zwiększają czytelność kodu aplikacji i pozwalają uniknąć nieoczekiwanego stanu aplikacji.

Składnia

wyliczenia PHP 8.1 rezerwuje i używa enum słowa kluczowego do deklarowania Enums. Składnia jest podobna do składni cechy/klasy/interfejsu:

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

Wyliczenia są deklarowane za pomocą enum słowa kluczowego, po którym następuje nazwa wyliczenia. Wyliczenie może opcjonalnie deklarować string wartości lub jako wartościint zabezpieczone. Wyliczenia mogą również rozszerzać klasę i/lub implementować interfejsy.

Wewnętrznie na poziomie parsera PHP istnieje nowy token o nazwie T_ENUM z przypisaną wartością369.

Wyliczenia mogą również zawierać wartość dla każdego przypadku, co czyni je Enumami wspieranymi.

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

Poniżej przedstawiono przykład Enum, który deklaruje typ wartości wspieranej i implementuje interfejs.

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

Nowa enum_exists funkcja

PHP 8.1 dodaje również nową enum_exists funkcję do sprawdzenia czy dany Enum istnieje.

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

Należy zauważyć, że ze względu na semantykę klas wyliczeń, class_exists funkcja zwraca również dla true wyliczenia.

UnitEnum interfejs

Wyliczenia, które nie są poparte wartością, automatycznie implementują UnitEnum interfejs.

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

Wyliczenia nie mogą jawnie implementować tego interfejsu, ponieważ jest to wykonywane wewnętrznie przez silnik. Ma to jedynie pomóc w określeniu rodzaju danego Enum. Metoda UnitEnum::cases zwraca tablicę wszystkich przypadków danego wyliczenia.

Klasy PHP nie mogą implementować tego interfejsu i powodują błąd, jeśli zostaną podjęte:Chociaż wyliczenia zezwalają na zadeklarowane na nich metody, deklarowanie metody o nazwie cases jest niedozwolone:

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

BackedEnum interface

Jeśli Enum deklaruje wartości oparte na skalarnie, to Enum automatycznie interfejs wywołuje BackedEnum. Podobnie jak w przypadku UnitEnum interfejsu, nie jest możliwe jawne zaimplementowanie BackedEnum interfejsu.

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

Zobacz BackedEnum::from i BackedEnum::tryFrom dla informacji o ich użyciu.

Klasy standardowe nie mogą implementować tego interfejsu.

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

Podobnie jak w przypadku ograniczenia dotyczącego niedozwolonego zezwalania na metodę o nazwie cases jest niedozwolona, żaden wspierany Enum nie może deklarować a from or tryFrom method:

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

Declaring Enums

Enums są wewnętrznie implementowane na najwyższych klasach PHP i dziedziczą większość semantyki klas z nałożonymi dodatkowymi ograniczeniami.

Wyliczenia obsługują przestrzenie nazw, automatyczne ładowanie, mogą mieć metody (ale nie właściwości), implementację interfejsów i wiele innych zachowań związanych z klasami PHP.

Podstawowy wyliczenie to po prostu strukturaenum, w której każdy przypadek jest deklarowany za pomocą słowa kluczowegocase. Dzięki wyliczeniom obsługiwanym w PHP 8.1, PHP rezerwuje "enum" teraz jako słowo zastrzeżone i zapobiega tworzeniu jakichkolwiek funkcji, klas, interfejsów itp. za pomocą enum. Może być częścią przestrzeni nazw ze względu na zmiany w sposobie uwzględniania zarezerwowanych słów kluczowych w wartościach w przestrzeni nazw.

Wyliczenia mogą mieć zero lub więcej przypadków

W ramach enum struktury może zawierać dowolną liczbę "przypadków", od zera do nieograniczonego. Obie te deklaracje Enum są prawidłowe:

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

Wyliczenia mogą mieć wartości

opcjonalne Możliwe jest przypisanie wartości a string lub int do każdego przypadku w Enum. Może to być przydatne podczas serializacji danych, przechowywania ich w bazie danych itp.

Wyliczenia, które posiadają wartość, tj. Enum wspierany, muszą:

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

Wyliczenie oparte musi zadeklarować typ skalarny Aby wyliczenie mogło kojarzyć wartości dla każdego przypadku (tj. Enum wspierane), musi zadeklarować typ

skalarny w deklaracji Enum.

Niezastosowanie się do tego powoduje błąd:

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

Wyliczenia tylko obsługują string i int typy skalarne. Wszelkie inne typy, w tym bool, , , null?string?inta nawet string|int typy unijne są niedozwolone.

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

Wyliczenia pomocnicze muszą przypisywać wartości dla wszystkich przypadków

Ta sekcja jest w toku

Jeśli wyliczenie deklaruje wartość typu skalarnego, musi ustawić wartość dla wszystkich przypadków:

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

W powyższym fragmencie kodu zadeklarowano, że wyliczenie zawiera string, HTTPMethods ale przypadkom nie przypisano wartości. Jest to niedozwolone i powoduje błąd.

Wyliczenia z podparciem muszą zawierać wartości tego samego typu

skalarnego W przypadku wyliczenia wspieranego wartości przypisane do każdego przypadku muszą być tego samego typu, co zadeklarowane w typie.

PHP ściśle to wymusza, nawet jeśli strict_types nie jest włączony.

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

Przypadki i wartości Enum Backed muszą być unikalne

Ta sekcja jest w toku

Prawidłowa wyliczenie nie może zawierać zduplikowanych przypadków ani zduplikowanych wartości. Na przykład obie te deklaracje są nieprawidłowe:

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

Wartości Enum muszą być również unikatowe, ponieważ BackedEnum::from obsługują pobieranie obiektu Enum z danej wartości skalarnej.

Rozróżnianie

wielkości liter Sama nazwa Enum nie rozróżnia wielkości liter i wynika z tego, jak PHP traktuje klasy i funkcje w sposób niewrażliwy na wielkość liter.

W indywidualnych przypadkach w Enum rozróżniana jest wielkość liter.

enum CaseInSenSitive {
    case bAR;
    case Bar;
}

Semantyka klas w przestrzeniach nazw wyliczeń Wyliczenia obsługują przestrzenie

nazw.

Są one zgodne ze standardową składnią przestrzeni nazw, która jest używana w klasach, cechach, interfejsach, funkcjach itp.

namespace Foo\Bar;
enum HTTPMethods {}

Automatyczne ładowanie

Podobnie jak PHP obsługuje automatyczne ładowanie klas, cech i interfejsów, wyliczenia obsługują również automatyczne ładowanie.

Należy pamiętać, że może to wymagać aktualizacji generatorów map klas, które skanują pliki w poszukiwaniu elementów ładowanych automatycznie.

Stałe magiczne

Wyliczenia w pełni obsługują wszystkie stałe magiczne, które PHP obsługuje dla klas.

  • ::class stała, która odnosi się do nazwy samego Enum.
  • __CLASS__ stała magiczna, która odnosi się do nazwy Enum z wnętrza Enum.
  • __FUNCTION__ w kontekście metody wyliczenia.
  • __METHOD__ w kontekście metody wyliczenia.

Funkcje klas/obiektów i wyliczenia zachowują się podobnie do klas, gdy są używane z funkcjami obsługującymi inspekcję klas i instanceof

obiektów.

Na przykład funkcje , , i get_debug_type (nowość w PHP 8.0) zachowują się tak, gettypeis_objectis_aget_class jakby wyliczenie było standardowym obiektem 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

Wyliczenia z właściwościami, metodami i cechami

Wyliczenia są wykonane w taki sposób, że można porównać jeden przypadek Enum z innym. Wyliczenia muszą być bezstanowe, ponieważ nie pozwalają na przechowywanie w nich właściwości.

Wyliczenia pozwalają na metody

Wyliczenia mogą zawierać metody. Obsługują również modyfikatory widoczności metod standardowych, a także metody statyczne.

Może to być bardzo przydatne przypadki użycia, takie jak deklarowanie label(): string metody, która zwraca przyjazną dla użytkownika etykietę.

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"

Powyższy fragment kodu używa match wyrażeń dodanych w PHP 8.0

Enums może implementować interfejsyEnums może implementować interfejsy

. Wyliczenia muszą spełniać kontrakty interfejsów, tak jak musi to robić klasa standardowa.

interface Foo {}
enum FooEnum implements Foo {}

Wyliczenie nie może zawierać właściwości

Jedną z najważniejszych różnic między Enum a klasą jest to, że Enums nie mogą mieć żadnego stanu. Deklarowanie właściwości lub ustawianie właściwości jest niedozwolone. Właściwości statyczne również nie są dozwolone.

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

Ponadto dynamiczne ustawianie właściwości również nie jest dozwolone:

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

Tworzenie instancji za pomocą nie jest dozwolone

Chociaż przypadki wyliczenia same w sobie są obiektami, nie wolno tworzyć ich instancji za pomocą new new konstrukcji.

Obie poniższe new konstrukcje są niedozwolone:

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 "$"

Wyliczenia nie mogą być rozszerzane i nie mogą dziedziczyć Ta sekcja jest w toku

Ta sekcja jest w toku

po innym Enum lub klasie.

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

Jeśli klasa próbuje rozszerzyć Enum, spowoduje to również błąd, ponieważ wszystkie Enum są uznane za ostateczne.

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

Wyliczenia wspierają cechy

pozbawione właściwości Wyliczenie może use cechować, o ile cecha nie deklaruje żadnych właściwości.

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

Jeśli użyte cechy zawierają jakiekolwiek właściwości (statyczne lub inne), użycie tej cechy powoduje błąd

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

krytyczny:Niedozwolone metody

magiczne Aby zapobiec posiadaniu jakiegokolwiek stanu przez obiekty Enum i zapewnić porównywalność dwóch wyliczeń, wyliczenia nie pozwalają na implementację kilku metod magicznych:

  • __get(): Aby zapobiec utrzymywaniu stanu w obiektach Enum.
  • __set(): Aby zapobiec dynamicznemu przypisywaniu właściwości i utrzymywaniu stanu.
  • __construct(): Wyliczenia nie obsługują new Foo() konstrukcji wszystkich.
  • __destruct(): Wyliczenie nie może utrzymywać stanu.
  • __clone(): Wyliczenie jest obiektem nieklonowalnym.
  • __sleep(): Wyliczenia nie obsługują metod cyklu życia.
  • __wakeup(): Wyliczenia nie obsługują metod cyklu życia.
  • __set_state(): Aby zapobiec przymusowemu stanowi obiektów Enum.

Wszystkie poniższe deklaracje metod magicznych są niedozwolone.

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() {}
}

Jeśli są zadeklarowane, spowoduje to błąd krytyczny z komunikatem podobnym do tego:

Fatal error: Enum may not include __get in ... on line ...

Klasy vs Enums

Wyliczenia przechowują stałe wartości, z opcjonalnymi wartościami tła, i nie zezwalają na stan. Służą one wyraźnie różnym celom, ale wyliczenia mają pewną semantykę z klasami.

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

Korzystanie z Enums Głównym przypadkiem użycia Enums

jest bezpieczeństwo typu. Silnik PHP zapewni, że przekazana lub zwrócona wartość należy do jednej z dozwolonych wartości. Bez konieczności sprawdzania poprawności przekazywanych wartości należących do jednego z dozwolonych typów, silnik PHP wymusza to i pozwala IDE i analizatorom statycznym również na podkreślenie potencjalnych błędów.

Dzięki wyliczeniom opartym możliwe jest przechowywanie wyliczonych wartości w bazie danych lub innym magazynie i przywracanie programowo identycznego obiektu.

Porównywanie wartości

wyliczenia W dowolnym momencie dwa wyliczenia posiadające tę samą wartość są uważane za identyczne, tak jak PHP uważa dwa ciągi za identyczne. Jest to zasadnicza różnica między dwoma obiektami, ponieważ dwa obiekty z instancjonowanej klasy nie są uważane za identyczne, mimo że mają dokładnie te same wartości.

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

Wyliczenia jako parametry, właściwości i typy

zwrotów Wyliczenia mogą być używane jako typy parametrów, zwrotów i właściwości. Jeśli używana jest nazwa Enum, wartości passed/set/return muszą być jedną z wartości wyliczonych.

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

Funkcja play akceptuje wartość wyliczeniową , Suita przekazanie dowolnej innej wartości spowoduje wyświetlenie \TypeErrorpliku . Może to znacznie poprawić kod, ponieważ PHP sprawdza poprawność przekazywanych wartości bez konieczności pisania dodatkowych walidacji wewnątrz play funkcji.

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

Jeśli zostanie przekazana jakakolwiek wartość inna niż Suit wartość wyliczenia, spowoduje to błąd typu:

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

Nie pozwala również na użycie wartości z innego Enum:

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

Ponadto, jeśli użyta zostanie niezdefiniowana wartość Enum, spowoduje to powstanie niezdefiniowanej stałej klasy, ponieważ wartości wyliczenia i stałe klas mają tę samą składnię.

Wyliczenie i value właściwości Każde wyliczenie name zawiera właściwośćname, która odwołuje się do nazwy właściwości

. Ta wartość jest tylko do odczytu.

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

W Backed Enum istnieje również value właściwość wartości backed (albo string lub int).

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

Próba zmiany name właściwości spowoduje value błąd:

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

Ponadto wyliczenia bez wartości wspieranej (UnitEnum) nie mają value właściwości. Tylko Backed Enums (BackedEnum) mają value własność. Próba użycia niezdefiniowanej właściwości (w tym ) obecnie powoduje wyświetlenie ostrzeżenia w 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 ...

Uzyskiwanie wszystkich wartości

wyliczenia Zarówno wyliczenia, jak i BackedEnum wyliczenia obsługują metodę::cases, która zwraca wszystkie wartości wyliczeniaUnitEnum.

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

Metoda Suit::cases() zwraca tablicę wszystkich możliwych wyliczeń. Zwracane wartości są samymi obiektami Enum, a nie nazwami lub właściwościami wartości.

Pobieranie wyliczenia przez wartość

wspieraną Wszystkie obsługiwane from wyliczenia (BackedEnum) i tryFrom metody umożliwiające pobieranie instancji z wartości wspieranej.

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; // "♥"

Jeśli nie ma Enum przez tę wartość, PHP zgłosi ValueError wyjątek.


Istnieje również tryFrom metoda, która zwraca null jeśli Enum nie istnieje przez tę wartość.

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

Serializacja/Unserializacja wyliczeń

Zarówno Backed jak i Unit Enum mogą być serializowane i nieserializowane przy użyciu wbudowanych serialize funkcji i funkcjiunserialize.

Formularz serializowany będzie miał identyfikator Ei nazwę wyliczenia.

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

W przypadku wyliczeń opartych wartości serializowane nadal są nazwą członka; nie wartość wspierana:

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

Zauważ, że składnia unserializacji nie jest zgodna ze starszymi wersjami PHP. Żadnych serializowanych ciągów wyliczeń nie można cofnąć serializacji w starszych wersjach PHP. Próba wykonania tej czynności powoduje wyświetlenie ostrzeżenia (PHP Notice: unserialize(): Error at offset ...) i unserialize zwrócenie falsepliku .


Wpływ

zgodności wstecznej Wyliczenia używają nowej składni i nie można ich używać w wersjach PHP wcześniejszych niż 8.1.

Natywne podejścia PHP, takie jak myclabs/php-enum, nie są kompatybilne z PHP 8.1 Enums.

Składnia Enum spowoduje ParserError powstanie w PHP < 8.1. Co więcej, nie jest możliwe serializowaneunserialize Enum w starszych wersjach PHP.


Implementacja dyskusji RFC

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

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

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297