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.
