Enumy (PHP 8.1)

🎯 Po tej lekcji zdefiniujesz backed enum, użyjesz from() i tryFrom() bezpiecznie i dodasz metodę do enuma.

Przed PHP 8.1 programiści używali zestawów stałych, żeby reprezentować skończony zestaw wartości: STATUS_NOWE = 1, STATUS_REALIZACJA = 2, STATUS_DOSTARCZONE = 3. Problem? Nic nie stało na przeszkodzie, żeby przekazać do funkcji liczbę 99 — PHP tego nie sprawdzał. Enum rozwiązuje ten problem: tworzy typ, który akceptuje tylko wartości z góry zdefiniowanego zbioru.

Pure enum — enum bez wartości

Najprostszy enum to lista nazwanych przypadków bez żadnych dodatkowych wartości:

<?php
declare(strict_types=1);

enum Status
{
    case Aktywny;
    case Nieaktywny;
    case Zablokowany;
}

$stan = Status::Aktywny;
echo $stan->name;  // wynik: Aktywny

// Porównanie:
$stanB = Status::Aktywny;
var_dump($stan === $stanB); // wynik: bool(true)

// Niedozwolone — PHP odmówi:
// new Status();              // Fatal Error: Cannot instantiate enum
// $stan = Status::Usunieto;  // Fatal Error: undefined enum case

Właściwość ->name zwraca nazwę case'a jako string. Jest dostępna zarówno w pure, jak i backed enum.

Backed enum — enum z wartościami

Backed enum przechowuje dodatkową wartość (ang. backing value) — string lub int. Deklarujesz typ po dwukropku:

<?php
declare(strict_types=1);

enum StatusZamowienia: string
{
    case Nowe        = 'nowe';
    case WRealizacji = 'w_realizacji';
    case Dostarczone = 'dostarczone';
    case Anulowane   = 'anulowane';
}

$status = StatusZamowienia::WRealizacji;

echo $status->name;  // wynik: WRealizacji
echo $status->value; // wynik: w_realizacji

Właściwość ->value zwraca wartość backing (string lub int). Jest dostępna tylko w backed enum.

from() i tryFrom()

Backed enum umożliwia tworzenie case'a na podstawie wartości backing. Masz do wyboru dwie metody:

<?php
declare(strict_types=1);

enum StatusZamowienia: string
{
    case Nowe        = 'nowe';
    case WRealizacji = 'w_realizacji';
    case Dostarczone = 'dostarczone';
}

// from() — rzuca \ValueError gdy wartość nie istnieje
$s1 = StatusZamowienia::from('nowe');
echo $s1->name; // wynik: Nowe

// tryFrom() — zwraca null gdy wartość nie istnieje (bezpieczna)
$s2 = StatusZamowienia::tryFrom('w_realizacji');
echo $s2?->name; // wynik: WRealizacji

$s3 = StatusZamowienia::tryFrom('nieznany');
var_dump($s3); // wynik: NULL

// from() z nieznana wartością → wyjątek:
// StatusZamowienia::from('nieznany'); // rzuca \ValueError
💡 Kiedy from(), kiedy tryFrom()? Użyj from(), gdy wartość pochodzi z Twojego kodu i powinna zawsze być prawidłowa (nieoczekiwany błąd to naprawdę błąd). Użyj tryFrom(), gdy wartość pochodzi z zewnątrz (formularz, baza danych, API) i może być nieprawidłowa — sprawdź null przed użyciem.

cases() — lista wszystkich case'ów

cases() zwraca tablicę wszystkich case'ów enuma. Przydatne np. do budowania listy wyboru:

<?php
declare(strict_types=1);

enum StatusZamowienia: string
{
    case Nowe        = 'nowe';
    case WRealizacji = 'w_realizacji';
    case Dostarczone = 'dostarczone';
    case Anulowane   = 'anulowane';
}

foreach (StatusZamowienia::cases() as $status) {
    echo $status->name . ' => ' . $status->value . PHP_EOL;
}
// wynik:
// Nowe => nowe
// WRealizacji => w_realizacji
// Dostarczone => dostarczone
// Anulowane => anulowane

Metody w enumach

Enum może mieć metody — zarówno zwykłe (dla konkretnego case'a przez $this), jak i statyczne:

<?php
declare(strict_types=1);

enum StatusZamowienia: string
{
    case Nowe        = 'nowe';
    case WRealizacji = 'w_realizacji';
    case Dostarczone = 'dostarczone';
    case Anulowane   = 'anulowane';

    public function etykieta(): string
    {
        return match($this) {
            self::Nowe        => 'Nowe zamówienie',
            self::WRealizacji => 'W realizacji',
            self::Dostarczone => 'Dostarczone',
            self::Anulowane   => 'Anulowane',
        };
    }

    public function czyAktywne(): bool
    {
        return match($this) {
            self::Nowe, self::WRealizacji => true,
            default                       => false,
        };
    }
}

$status = StatusZamowienia::WRealizacji;
echo $status->etykieta();            // wynik: W realizacji
echo $status->czyAktywne() ? 'aktywne' : 'zakończone'; // wynik: aktywne

$zakonczone = StatusZamowienia::Dostarczone;
echo $zakonczone->czyAktywne() ? 'aktywne' : 'zakończone'; // wynik: zakończone

Enum implementujący interfejs

Enum może implementować interfejsy — to przydatne do typowania:

<?php
declare(strict_types=1);

interface MaEtykiete
{
    public function etykieta(): string;
}

enum Priorytet: int implements MaEtykiete
{
    case Niski   = 1;
    case Sredni  = 2;
    case Wysoki  = 3;

    public function etykieta(): string
    {
        return match($this) {
            self::Niski  => 'Niski',
            self::Sredni => 'Średni',
            self::Wysoki => 'Wysoki',
        };
    }
}

echo Priorytet::Wysoki->etykieta(); // wynik: Wysoki
echo Priorytet::Wysoki->value;      // wynik: 3
⚠️ Enum nie może mieć regularnych właściwości: Enum może mieć metody, stałe (const) i implementować interfejsy — ale nie może mieć zwykłych właściwości instancji (np. public string $opis;). Każdy case to singleton — nie przechowuje stanu. Wszystkie dane enuma muszą wynikać z jego wartości backing lub const.

Int backed enum

Backed enum może też używać int zamiast string:

<?php
declare(strict_types=1);

enum Priorytet: int
{
    case Niski   = 1;
    case Sredni  = 5;
    case Wysoki  = 10;
}

$p = Priorytet::from(5);
echo $p->name;  // wynik: Sredni
echo $p->value; // wynik: 5

var_dump(Priorytet::tryFrom(99)); // wynik: NULL

Enumy to jeden z najpotężniejszych dodatków PHP 8.1. Zastępują błędogenne stałe klasowe i zapewniają pełne bezpieczeństwo typów. W kolejnej lekcji poznasz przestrzenie nazw — mechanizm organizowania klas w większych projektach.

Sprawdź się

Co zwraca StatusZamowienia::tryFrom('nieistniejaca_wartosc') dla backed enum?

Jak odczytać wartość backing (np. 'nowe') z obiektu backed enum $status = StatusZamowienia::Nowe?

Czy można stworzyć obiekt enuma przez new StatusZamowienia()?

Ćwiczenie

Zdefiniuj backed enum RolUzytkownika: string z case'ami Admin = 'admin', Redaktor = 'redaktor', Czytelnik = 'czytelnik'. Dodaj metodę mozeDodawac(): bool zwracającą true dla Admin i Redaktor. Sprawdź tryFrom() dla 'admin', 'redaktor' i 'brak'. Wylistuj wszystkie role przez cases().

Pokaż rozwiązanie
<?php
declare(strict_types=1);

enum RolUzytkownika: string
{
    case Admin     = 'admin';
    case Redaktor  = 'redaktor';
    case Czytelnik = 'czytelnik';

    public function mozeDodawac(): bool
    {
        return match($this) {
            self::Admin, self::Redaktor => true,
            self::Czytelnik            => false,
        };
    }
}

var_dump(RolUzytkownika::tryFrom('admin'));    // wynik: enum(RolUzytkownika::Admin)
var_dump(RolUzytkownika::tryFrom('redaktor')); // wynik: enum(RolUzytkownika::Redaktor)
var_dump(RolUzytkownika::tryFrom('brak'));     // wynik: NULL

$rola = RolUzytkownika::Admin;
echo $rola->mozeDodawac() ? 'może' : 'nie może'; // wynik: może

foreach (RolUzytkownika::cases() as $r) {
    echo $r->value . ': ' . ($r->mozeDodawac() ? 'może' : 'nie może') . PHP_EOL;
}
// wynik:
// admin: może
// redaktor: może
// czytelnik: nie może