PHP 8.4 planowane jest na listopad 2024 i ma przynieść dwie zmiany które zmienią sposób pisania klas PHP – property hooks i asymmetric visibility. Property hooks to coś czego brakowało od lat: możliwość definiowania logiki get/set bezpośrednio przy właściwości, bez osobnych metod. Asymmetric visibility pozwala na readonly-like zachowanie z możliwością wewnętrznej modyfikacji. Przeglądam RFC które przeszły głosowanie.
Property Hooks – get i set przy właściwości
Do PHP 8.4 jeśli chciałeś kontrolować odczyt lub zapis właściwości, pisałeś gettery i settery. Property hooks przenoszą tę logikę bezpośrednio do deklaracji właściwości:
<?php
declare(strict_types=1);
// Przed PHP 8.4 - klasyczne gettery/settery
class Product
{
private float $_price;
private string $_name;
public function getPrice(): float
{
return $this->_price;
}
public function setPrice(float $price): void
{
if ($price < 0) {
throw new \InvalidArgumentException('Price cannot be negative');
}
$this->_price = round($price, 2);
}
public function getName(): string
{
return $this->_name;
}
public function setName(string $name): void
{
$this->_name = trim($name);
}
}
<?php
declare(strict_types=1);
// PHP 8.4 - property hooks eliminują boilerplate
class Product
{
// Hook get i set bezpośrednio przy właściwości
public float $price {
get {
return $this->price;
}
set(float $value) {
if ($value < 0) {
throw new \InvalidArgumentException('Price cannot be negative');
}
$this->price = round($value, 2);
}
}
public string $name {
// Skrócona forma dla prostych transformacji
get => trim($this->name);
set => trim($value);
}
// Tylko get hook - właściwość computed, bez zapisu
public string $slug {
get => strtolower(str_replace(' ', '-', $this->name));
}
public function __construct(string $name, float $price)
{
$this->name = $name; // wywołuje hook set
$this->price = $price; // wywołuje hook set
}
}
$product = new Product(' Widget Pro ', 29.999);
echo $product->name; // Widget Pro (trimmed)
echo $product->price; // 30.0 (rounded)
echo $product->slug; // widget-pro (computed)
$product->price = -5.0; // InvalidArgumentException
Property Hooks w interfejsach
<?php
declare(strict_types=1);
// Interfejsy mogą deklarować wymagane hooki
interface ProductInterface
{
// Wymaga implementacji hooka get i set dla price
public float $price { get; set; }
// Wymaga tylko hooka get (computed property)
public string $slug { get; }
}
// Implementacja musi dostarczyć zadeklarowane hooki
class SimpleProduct implements ProductInterface
{
public float $price {
get => $this->_price;
set => $this->_price = max(0.0, round($value, 2));
}
public string $slug {
get => strtolower(str_replace(' ', '-', $this->name));
}
private float $_price = 0.0;
public function __construct(
public string $name,
float $price
) {
$this->price = $price;
}
}
Asymmetric Visibility
Asymmetric visibility pozwala ustawić różną widoczność dla odczytu i zapisu właściwości. Rozwiązuje problem readonly – chcesz żeby właściwość była publiczna do odczytu, ale modyfikowalna tylko wewnątrz klasy:
<?php
declare(strict_types=1);
class Order
{
// public odczyt, private zapis - zamiast readonly + metod
// Składnia: widoczność_odczytu(widoczność_zapisu)
public private(set) int $id;
public private(set) string $status = 'pending';
public protected(set) float $total = 0.0;
public private(set) array $items = [];
public function __construct(int $id)
{
$this->id = $id; // ok - jesteśmy wewnątrz klasy
}
public function addItem(array $item): void
{
$this->items[] = $item; // ok - private(set) dostępne w klasie
$this->total += $item['price']; // ok
}
public function process(): void
{
$this->status = 'processing'; // ok - modyfikacja wewnątrz klasy
}
}
class ExtendedOrder extends Order
{
public function applyDiscount(float $percent): void
{
// protected(set) - dostępne w podklasie
$this->total *= (1 - $percent / 100);
}
}
$order = new Order(42);
echo $order->id; // ok - public odczyt
echo $order->status; // ok - public odczyt
echo $order->total; // ok - public odczyt
$order->id = 99; // Error: Cannot modify private(set) property from outside
$order->status = 'cancelled'; // Error: Cannot modify private(set) property from outside
Porównanie readonly vs asymmetric visibility
<?php
declare(strict_types=1);
// readonly - można ustawić tylko raz, nigdy nie zmienić
readonly class ImmutableConfig
{
public function __construct(
public string $apiKey,
public string $endpoint
) {}
// Nie można zmodyfikować po inicjalizacji - nigdy
}
// asymmetric visibility - można modyfikować wewnątrz, nie z zewnątrz
class MutableInternally
{
public private(set) string $status = 'draft';
public function publish(): void
{
$this->status = 'published'; // ok - modyfikacja wewnątrz
}
}
// Różnica: readonly = nigdy nie zmienisz po new
// private(set) = możesz zmieniać wewnątrz klasy wielokrotnie
Inne nowości w PHP 8.4
<?php
// Chaining new bez nawiasów
// Przed PHP 8.4
$result = (new MyBuilder())->setFoo('bar')->build();
// PHP 8.4 - nawiasy niewymagane przy new + chain
$result = new MyBuilder()->setFoo('bar')->build();
// array_find() dostępne od PHP 8.4 (przesunięte z 8.3 RFC)
$found = array_find($products, fn($p) => $p['price'] > 100);
// Nowe funkcje bcmath
$sum = bcadd('1.234567890', '9.876543210', scale: 9);
// Lazy Objects - obiekty tworzone dopiero przy pierwszym użyciu właściwości
// (nowe API Reflection - szczegóły w dokumentacji PHP 8.4)
Kiedy to trafi do Magento 2?
Magento 2 wspiera nowe wersje PHP zwykle 6-12 miesięcy po ich premierze. PHP 8.4 (listopad 2024) prawdopodobnie oficjalnie pojawi się w Magento 2.4.8 lub 2.4.9 (2025). Można używać PHP 8.4 features w własnym kodzie modułów wcześniej, ale trzeba pamiętać że moduł nie będzie wtedy kompatybilny z PHP 8.3.
Podsumowanie
Property hooks to największa zmiana w sposobie pisania klas PHP od readonly properties. Eliminują gettery i settery przy jednoczesnym zachowaniu enkapsulacji – kod jest krótszy i czytelniejszy. Asymmetric visibility rozwiązuje realne ograniczenie readonly – teraz można mieć właściwość publiczną do odczytu i modyfikowalną wewnętrznie. Razem te dwie zmiany znacząco odchudzą typowe klasy domenowe w PHP 8.4.
