PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

PHP 8.1 preview – enumy, readonly properties, intersection types, fibers

by Henryk Tews / wtorek, 09 listopada 2021 / Opublikowano w PHP

PHP 8.1 wychodzi w grudniu 2021 i przynosi kilka zmian które od razu wchodzą do codziennego kodu. Enumy to chyba największa nowość od lat – nareszcie natywne typy wyliczeniowe bez obejść przez stałe i klasy. Do tego readonly properties, intersection types i never return type. Przeglądam co jest gotowe do użycia od razu, a co wymaga ostrożności przy integracji z Magento 2.

Enums – nareszcie natywne typy wyliczeniowe

Przez lata emulowaliśmy enumy stałymi w klasach lub bibliotekami jak myclabs/php-enum. PHP 8.1 wprowadza enumy jako pełnoprawny typ języka:

<?php

// Pure enum - bez wartości, tylko nazwy przypadków
enum Status
{
    case Active;
    case Inactive;
    case Pending;
}

// Backed enum - każdy przypadek ma przypisaną wartość (string lub int)
enum OrderStatus: string
{
    case Pending   = 'pending';
    case Processing = 'processing';
    case Shipped   = 'shipped';
    case Delivered = 'delivered';
    case Cancelled = 'cancelled';

    // Metody w enumie
    public function label(): string
    {
        return match($this) {
            OrderStatus::Pending    => 'Oczekuje',
            OrderStatus::Processing => 'W realizacji',
            OrderStatus::Shipped    => 'Wysłane',
            OrderStatus::Delivered  => 'Dostarczone',
            OrderStatus::Cancelled  => 'Anulowane',
        };
    }

    public function isFinal(): bool
    {
        return in_array($this, [self::Delivered, self::Cancelled], true);
    }
}

// Użycie
$status = OrderStatus::Pending;
echo $status->value;      // 'pending'
echo $status->name;       // 'Pending'
echo $status->label();    // 'Oczekuje'

// Tworzenie z wartości (backed enum)
$fromDb = OrderStatus::from('shipped');         // OrderStatus::Shipped
$safe   = OrderStatus::tryFrom('unknown');       // null zamiast wyjątku

// Type hint
function processOrder(OrderStatus $status): void
{
    if ($status->isFinal()) {
        throw new \LogicException('Cannot process final order');
    }
    // ...
}

processOrder(OrderStatus::Processing); // ok
processOrder(OrderStatus::Delivered);  // LogicException

Enumy implementują też interfejsy, co otwiera ciekawe możliwości:

<?php

interface HasColor
{
    public function color(): string;
}

enum ProductStatus: int implements HasColor
{
    case Active   = 1;
    case Inactive = 2;
    case Draft    = 3;

    public function color(): string
    {
        return match($this) {
            self::Active   => '#28a745',
            self::Inactive => '#dc3545',
            self::Draft    => '#ffc107',
        };
    }
}

// Enum jako stałe w interfejsach - zastępuje stałe klas
enum PaymentMethod: string
{
    case CreditCard  = 'checkmo';
    case BankTransfer = 'banktransfer';
    case Paypal      = 'paypal_express';

    public static function fromMagentoCode(string $code): self
    {
        return self::from($code);
    }
}

Readonly Properties

Readonly properties można przypisać tylko raz – w konstruktorze. Próba ponownego przypisania rzuca Error. Idealne dla value objects i DTO:

<?php

declare(strict_types=1);

class Money
{
    public function __construct(
        public readonly float $amount,
        public readonly string $currency
    ) {}

    public function add(Money $other): self
    {
        if ($this->currency !== $other->currency) {
            throw new \InvalidArgumentException('Cannot add different currencies');
        }

        // Tworzymy nowy obiekt - readonly nie pozwala zmodyfikować istniejącego
        return new self($this->amount + $other->amount, $this->currency);
    }

    public function format(): string
    {
        return number_format($this->amount, 2) . ' ' . $this->currency;
    }
}

$price = new Money(29.99, 'PLN');
echo $price->amount;   // 29.99
echo $price->format(); // 29.99 PLN

$price->amount = 39.99; // Error: Cannot modify readonly property

// Kombinacja z constructor promotion - bardzo czyste DTO
class OrderItem
{
    public function __construct(
        public readonly string $sku,
        public readonly string $name,
        public readonly int $quantity,
        public readonly Money $price
    ) {}

    public function total(): Money
    {
        return new Money($this->price->amount * $this->quantity, $this->price->currency);
    }
}

Intersection Types

Union types z PHP 8.0 pozwalały na „A lub B”. Intersection types z PHP 8.1 wymagają „A i B” – obiekt musi implementować oba interfejsy:

<?php

interface Countable
{
    public function count(): int;
}

interface Stringable
{
    public function __toString(): string;
}

// Parametr musi implementować oba interfejsy
function processCollection(Countable&Stringable $collection): string
{
    return "Kolekcja '{$collection}' ma {$collection->count()} elementów";
}

// Przydatne przy pracy z kolekcjami Magento które implementują wiele interfejsów
function processItems(
    \Magento\Framework\Data\Collection & \Countable $items
): void {
    echo "Elementów: " . $items->count() . PHP_EOL;
    // korzystaj z metod Collection i Countable
}

Never Return Type

Typ zwracany never oznacza że funkcja nigdy nie wraca normalnie – albo rzuca wyjątek, albo wywołuje exit(). Przydatny przy helper functions i metodach rzucających wyjątki:

<?php

// PHPStan i IDE wiedzą że po tej funkcji kod nie kontynuuje
function throwNotFound(string $entityType, int $id): never
{
    throw new \Magento\Framework\Exception\NoSuchEntityException(
        __('%1 with ID "%2" does not exist.', $entityType, $id)
    );
}

function abort(int $code): never
{
    http_response_code($code);
    exit();
}

// Użycie
function getProduct(int $id): ProductInterface
{
    $product = $this->productRepository->getById($id);

    if (!$product->getId()) {
        throwNotFound('Product', $id); // PHPStan wie że tu nie wraca
    }

    return $product; // PHPStan wie że produkt na pewno istnieje
}

Fibers – asynchroniczność w PHP

Fibers to korutyny – lekkie wątki współdzielące pamięć, które mogą zawieszać i wznawiać wykonanie. To fundamentalna zmiana dla PHP jako języka, choć dla standardowych aplikacji Magento jej wpływ na razie jest ograniczony:

<?php

// Fiber - można zawiesić i wznowić wykonanie
$fiber = new Fiber(function(): string {
    echo "Fiber: start\n";

    $value = Fiber::suspend('pierwsza wartość'); // zawieś i oddaj wartość
    echo "Fiber: wznowiony z wartością: {$value}\n";

    return 'wynik końcowy';
});

$first = $fiber->start();     // uruchom fiber, zatrzyma się na suspend()
echo "Główny: otrzymano: {$first}\n"; // 'pierwsza wartość'

$result = $fiber->resume('hello'); // wznów fiber
echo "Główny: fiber zakończony\n";
echo "Wynik: " . $fiber->getReturn() . "\n"; // 'wynik końcowy'

// Output:
// Fiber: start
// Główny: otrzymano: pierwsza wartość
// Fiber: wznowiony z wartością: hello
// Główny: fiber zakończony
// Wynik: wynik końcowy

Fibers są podstawą dla bibliotek asynchronicznych jak ReactPHP i Amp w ich nowych wersjach. Dla Magento 2 na razie to ciekawostka – platforma nie jest zaprojektowana pod asynchroniczność.

Podsumowanie

PHP 8.1 to jedna z bardziej wartościowych wersji. Enumy od razu zastąpią wzorzec stałych w klasach wszędzie gdzie masz skończony zestaw wartości – status zamówienia, typ produktu, metoda płatności. Readonly properties porządkują value objects i DTO. Intersection types zwiększają precyzję type hintów. Wszystkie te zmiany działają świetnie z PHPStan który potrafi z nich w pełni korzystać do statycznej analizy kodu.

About Henryk Tews

Co możesz przeczytać następne

Blackfire – instalacja w DDEV, profilowanie HTTP i CLI, asercje w CI/CD
Laravel vs Symfony – DI, Doctrine vs Eloquent, kiedy który framework
PHP 7.2 – object type hint, sodium zamiast mcrypt, deprecacje
  • Publikacje
  • O autorze
  • Kontakt

© 2026 Created by

GÓRA
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Zawsze aktywne
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Zarządzaj opcjami
  • Zarządzaj serwisami
  • Zarządzaj {vendor_count} dostawcami
  • Przeczytaj więcej o tych celach
Zobacz preferencje
  • {title}
  • {title}
  • {title}