PHP 8.2 planowane jest na grudzień 2022. Najważniejsza nowość to readonly classes – możliwość oznaczenia całej klasy jako readonly bez dekorowania każdej właściwości z osobna. Do tego Disjunctive Normal Form types które rozszerzają system typów o bardziej złożone kombinacje. Przeglądam co wchodzi, co wypada i co to oznacza dla projektów Magento.
Readonly Classes
PHP 8.1 dało nam readonly properties – możliwość oznaczenia pojedynczej właściwości jako niezmienialnej po inicjalizacji. PHP 8.2 idzie krok dalej i pozwala oznaczyć całą klasę jako readonly. Wszystkie właściwości automatycznie stają się readonly bez konieczności dekorowania każdej z osobna:
<?php
declare(strict_types=1);
// PHP 8.1 - readonly per właściwość
class MoneyPhp81
{
public function __construct(
public readonly int $amount,
public readonly string $currency,
public readonly \DateTimeImmutable $createdAt
) {}
}
// PHP 8.2 - readonly class - wszystkie właściwości automatycznie readonly
readonly class Money
{
public function __construct(
public int $amount,
public string $currency,
public \DateTimeImmutable $createdAt
) {}
// Metody działają normalnie - readonly dotyczy tylko właściwości
public function add(self $other): self
{
if ($this->currency !== $other->currency) {
throw new \InvalidArgumentException('Currency mismatch');
}
return new self(
$this->amount + $other->amount,
$this->currency,
new \DateTimeImmutable()
);
}
public function format(): string
{
return number_format($this->amount / 100, 2, ',', ' ') . ' ' . $this->currency;
}
}
$price = new Money(2999, 'PLN', new \DateTimeImmutable());
$tax = new Money(690, 'PLN', new \DateTimeImmutable());
$total = $price->add($tax);
echo $total->format(); // 36,89 PLN
$price->amount = 100; // Fatal error: Cannot modify readonly property
Readonly class ma kilka ograniczeń: właściwości nie mogą mieć wartości domyślnych innych niż przez konstruktor, klasa nie może rozszerzać niereadonly klas (i odwrotnie – niereadonly nie może rozszerzać readonly), oraz właściwości statyczne nie są dozwolone w readonly class.
<?php
declare(strict_types=1);
// Readonly class świetna dla DTO i Value Objects
readonly class ProductDto
{
public function __construct(
public int $id,
public string $sku,
public string $name,
public float $price,
public bool $isActive,
public array $categories
) {}
public static function fromArray(array $data): self
{
return new self(
id: (int) $data['entity_id'],
sku: (string) $data['sku'],
name: (string) $data['name'],
price: (float) $data['price'],
isActive: (bool) $data['status'],
categories: $data['categories'] ?? []
);
}
}
readonly class OrderItemDto
{
public function __construct(
public string $sku,
public string $name,
public int $quantity,
public float $price,
public float $rowTotal
) {}
}
// Klonowanie z modyfikacją - zamiast setterów
readonly class UserDto
{
public function __construct(
public int $id,
public string $email,
public string $name,
public string $role
) {}
public function withRole(string $role): self
{
return new self($this->id, $this->email, $this->name, $role);
}
public function withEmail(string $email): self
{
return new self($this->id, $email, $this->name, $this->role);
}
}
$user = new UserDto(1, 'jan@example.com', 'Jan Kowalski', 'user');
$admin = $user->withRole('admin');
echo $admin->role; // admin
echo $user->role; // user - oryginalny niezmieniony
DNF Types – Disjunctive Normal Form
PHP 8.0 dał union types (A|B). PHP 8.1 dał intersection types (A&B). PHP 8.2 łączy oba w DNF (Disjunctive Normal Form) – możesz pisać (A&B)|C:
<?php
declare(strict_types=1);
interface Stringable
{
public function __toString(): string;
}
interface Countable
{
public function count(): int;
}
interface Serializable
{
public function serialize(): string;
}
// DNF type - parametr musi być:
// (Countable i Stringable) LUB Serializable
function processCollection((Countable&Stringable)|Serializable $collection): string
{
if ($collection instanceof Serializable) {
return $collection->serialize();
}
// Tu wiemy że $collection implementuje Countable i Stringable
return "Kolekcja '{$collection}' ma {$collection->count()} elementów";
}
// Praktyczne użycie - nullable intersection type
// Przed PHP 8.2 nie można było pisać ?(A&B)
// PHP 8.2 - (A&B)|null zamiast ?(A&B) - to samo, nowa składnia
function findItem((Countable&Stringable)|null $collection): ?string
{
if ($collection === null) {
return null;
}
return "Znaleziono kolekcję z {$collection->count()} elementami";
}
Deprecacje w PHP 8.2 – na co uważać w Magento
PHP 8.2 deprecjonuje kilka rzeczy które mogą boleć przy starszych projektach Magento:
<?php
// 1. Dynamiczne właściwości - deprecated w PHP 8.2, Error w PHP 9.0
class Product
{
public string $name;
// brak deklaracji $customAttribute
}
$product = new Product();
$product->customAttribute = 'wartość'; // PHP 8.2: Deprecated warning
// PHP 9.0: Error
// Rozwiązanie - zadeklaruj właściwość lub użyj atrybutu #[AllowDynamicProperties]
#[\AllowDynamicProperties]
class LegacyModel
{
// Klasy z tym atrybutem mogą nadal mieć dynamiczne właściwości
// Użyteczne przy migracji starszego kodu Magento
}
// Magento\Framework\DataObject używa dynamicznych właściwości przez _data[]
// Core Magento ma już obsługę #[AllowDynamicProperties] dla PHP 8.2
// 2. Callables przez string - niektóre formy deprecated
$fn = 'strlen';
$fn('test'); // ok
call_user_func('strlen', 'test'); // ok
// Deprecated: tworzenie Closure przez nie-statyczne metody przez string
// $closure = Closure::fromCallable([$obj, 'method']); // ok
// 'ClassName::method' jako callable - deprecated w niektórych kontekstach
// 3. ${var} w stringach - deprecated
$var = 'world';
echo "Hello ${var}"; // Deprecated - użyj {$var}
echo "Hello {$var}"; // ok
echo "Hello $var"; // ok
Nowe funkcje w PHP 8.2
<?php // mysqli_execute_query() - prepared statement w jednej linii $result = mysqli_execute_query($connection, 'SELECT * FROM users WHERE id = ?', [$id]); // curl_upkeep() - utrzymanie połączenia curl curl_upkeep($curlHandle); // Random\Randomizer - OOP API dla generowania liczb losowych $randomizer = new \Random\Randomizer(); echo $randomizer->getInt(1, 100); // losowa liczba 1-100 echo $randomizer->shuffleArray([1,2,3,4,5])[0]; // przetasowana tablica // Bardziej przewidywalne niż mt_rand() przy testach - można wstrzyknąć seed $seededRandomizer = new \Random\Randomizer(new \Random\Engine\Mt19937(42)); echo $seededRandomizer->getInt(1, 100); // zawsze ta sama wartość dla seed=42 // fibers - ulepszenia API (nie nowy feature, ale poprawki) // array_is_list() - dostępne od PHP 8.1, działa bez zmian var_dump(array_is_list([1, 2, 3])); // true var_dump(array_is_list(['a' => 1, 'b' => 2])); // false
Kiedy przejść na PHP 8.2 w projekcie Magento?
Magento 2.4.6 (planowane Q1 2023) ma oficjalnie wspierać PHP 8.2. Przed migracją warto sprawdzić:
- Zewnętrzne moduły – czy vendor obsługuje PHP 8.2
- Dynamiczne właściwości – czy własny kod nie polega na nich bez
#[AllowDynamicProperties] - Uruchomić
vendor/bin/phpstan analyse --level=6z regułami PHP 8.2 - Sprawdzić PHP Compatibility Checker:
vendor/bin/phpcs --standard=PHPCompatibility --runtime-set testVersion 8.2
Podsumowanie
PHP 8.2 to ewolucja, nie rewolucja. Readonly classes od razu wejdą do nowych Value Objects i DTO – eliminują powtarzanie readonly przy każdej właściwości. DNF types domkną system typów i pozwolą wyrażać złożone kombinacje typów które wcześniej wymagały komentarzy. Deprecacja dynamicznych właściwości to sygnał do uporządkowania starszego kodu – szczególnie w kontekście modeli opartych na DataObject w Magento.
