PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

PHP 8.4 RC – property hooks w praktyce, niespodzianki, asymmetric visibility

by Henryk Tews / wtorek, 02 lipca 2024 / Opublikowano w PHP

PHP 8.4 wychodzi w listopadzie 2024, ale RC1 jest dostępne już teraz. Przez kilka tygodni testowałem property hooks i asymmetric visibility na realnych projektach – data transfer objects, value objects, moduły Magento. Czas na uczciwy raport: co weszło do kodu od razu, co wymaga ostrożności i gdzie są niespodzianki.

Property Hooks – pierwsze wrażenia po RC1

<?php

declare(strict_types=1);

// Przed PHP 8.4 – ~40 linii boilerplate
class Email
{
    private string $value;

    public function __construct(string $email)
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException("Invalid email: {$email}");
        }
        $this->value = mb_strtolower(trim($email));
    }

    public function getValue(): string { return $this->value; }
    public function getDomain(): string { return explode('@', $this->value)[1]; }
}

// PHP 8.4 – ta sama funkcjonalność, ~15 linii
class Email
{
    public string $value {
        set(string $email) {
            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                throw new \InvalidArgumentException("Invalid email: {$email}");
            }
            $this->value = mb_strtolower(trim($email));
        }
    }

    // Computed property - tylko get, bez set
    public string $domain {
        get => explode('@', $this->value)[1];
    }

    public function __construct(string $email)
    {
        $this->value = $email; // wywołuje hook set
    }
}

$email = new Email('  JAN@EXAMPLE.COM  ');
echo $email->value;  // jan@example.com
echo $email->domain; // example.com

$email->domain = 'test'; // Error: Cannot set hook-only property

Niespodzianki w RC1

<?php

declare(strict_types=1);

// 1. Operator ++ i -- działają przez get+set - bez niespodzianek
class Counter
{
    public int $count = 0 {
        set(int $v) {
            if ($v < 0) throw new \RangeException('Cannot be negative');
            $this->count = $v;
        }
    }
}

$c = new Counter();
$c->count++;     // wywołuje get (0) + set (1) - działa
$c->count += 5;  // get (1) + set (6) - działa
$c->count = -1;  // RangeException - działa

// 2. readonly class + property hooks = błąd kompilacji
// readonly class nie może mieć set hook - sprzeczność
// readonly class Foo { public string $bar { set => ... } } // Fatal Error

// 3. serialize() i var_export() - zachowują się jak bez hooków
// Blackfire i Xdebug wyświetlają wartości właściwości poprawnie

// 4. Refleksja - ReflectionProperty::hasHook() nowe API
$rf = new \ReflectionProperty(Email::class, 'value');
var_dump($rf->hasHook(\PropertyHookType::Set)); // true
var_dump($rf->hasHook(\PropertyHookType::Get)); // false

// 5. Interface hooks - interfejsy mogą deklarować wymagane hooki
interface HasPrice
{
    public float $price { get; set; }
}

// Implementacja MUSI zapewnić get i set dla $price
class Product implements HasPrice
{
    public float $price {
        get => $this->price;
        set(float $v) {
            $this->price = max(0.0, round($v, 2));
        }
    }
}

Asymmetric Visibility w praktyce

<?php

declare(strict_types=1);

// Wzorzec który najczęściej stosowałem: domain aggregate z publicznym odczytem
class ShoppingCart
{
    public private(set) float $subtotal = 0.0;
    public private(set) int $itemCount = 0;
    public private(set) array $items = [];
    public private(set) bool $locked = false;

    public function __construct(
        public readonly string $cartId
    ) {}

    public function addItem(string $sku, float $price, int $qty): void
    {
        if ($this->locked) {
            throw new \LogicException('Cart is locked for checkout');
        }

        $this->items[]      = compact('sku', 'price', 'qty');
        $this->subtotal    += $price * $qty;
        $this->itemCount   += $qty;
    }

    public function lock(): void
    {
        $this->locked = true; // ok - wewnątrz klasy
    }
}

$cart = new ShoppingCart('cart-abc123');
$cart->addItem('SKU-001', 29.99, 2);
$cart->addItem('SKU-002', 9.99, 1);

echo $cart->subtotal;   // 69.97 - public odczyt ok
echo $cart->itemCount;  // 3 - public odczyt ok

$cart->subtotal = 0;    // Error: Cannot modify private(set) from outside
$cart->locked   = true; // Error: Cannot modify private(set) from outside

Praktyczne porównanie: property hooks vs asymmetric visibility

Chcę… Użyj Przykład
Walidować wartość przy zapisie set hook Email, Price, Quantity
Obliczać wartość dynamicznie get hook $order->total, $url->domain
Publiczny odczyt, prywatny zapis public private(set) $order->status, $cart->total
Wartość niezmienialną po init readonly $order->id, $user->createdAt
Walidację + prywatny zapis private(set) + set hook Złożone domain objects

Chaining new – drobna ale wygodna zmiana

<?php

// Przed PHP 8.4 - nawiasy wymagane przy chainingu po new
$result = (new QueryBuilder())
    ->from('orders')
    ->where('status', '=', 'pending')
    ->build();

// PHP 8.4 - nawiasy przy new opcjonalne
$result = new QueryBuilder()
    ->from('orders')
    ->where('status', '=', 'pending')
    ->build();

// Szczególnie czytelne przy prostych obiektach
$email   = new Email('jan@example.com')->normalized();
$request = new HttpRequest('GET', '/api/products')->withHeader('Accept', 'application/json');

Kiedy migrować?

# Sprawdź kompatybilność projektu z PHP 8.4 już teraz na RC
docker run --rm -v $(pwd):/app php:8.4-rc-cli \
    php -l /app/src/Model/Order.php

# PHPStan z PHP 8.4
vendor/bin/phpstan analyse \
    --level=8 \
    --php-version=8.4 \
    src/

# Deprecacje usunięte w PHP 8.4 na które warto uważać:
# - implicitly nullable parameters (function f(string $x = null) -> wymaga ?string)
# - GMP i BCMath zmiany API

Podsumowanie

PHP 8.4 RC1 potwierdza że property hooks i asymmetric visibility są dopracowane. Property hooks natychmiast wchodzą do nowych Value Objects – eliminują boilerplate getterów/setterów przy zachowaniu enkapsulacji. Asymmetric visibility rozwiązuje realne ograniczenie readonly w klasach domenowych. Warto przetestować migrację na RC żeby wykryć problemy zanim przyjdzie final release w listopadzie.

About Henryk Tews

Co możesz przeczytać następne

AI workflow 2026 – 2 lata później, co działa, co nie, Claude 200k w praktyce
REST API – zasoby vs akcje, kody HTTP, struktura odpowiedzi, wersjonowanie
PHPUnit – testy jednostkowe, mocki, data providers, testy w Magento 2
  • 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}