PHP 8.4 Property Hooks: Der Game-Changer auf den du gewartet hast
Oder: Wie ich lernte die Getter/Setter-Hölle zu lieben (nicht wirklich)
Kennste das? Du schreibst eine PHP-Klasse und verbrätst 200 Zeilen nur für Getter und Setter, um auf drei Properties zuzugreifen. Ja, ich auch. Ab heute ist damit Schluss.
Am 21. November 2024 kam PHP 8.4 raus, und damit ein Feature das so transformativ ist, dass Entwickler alles hinterfragen was sie über objektorientierte Programmierung in PHP wissen: Property Hooks.
Ich übertreibe nicht wenn ich sage, dass dieses Feature mein User-Model von 350 auf 180 Zeilen geschrumpft hat. Gleiche Funktionalität. Gleiche Typsicherheit. Halber Code. Lass mich dir zeigen wie.
Das Problem: Getter/Setter-Hölle
Wir kennen das alle. Du startest mit einer simplen Klasse:
class User {
private string $email;
private string $firstName;
private string $lastName;
}
Dann kommt die Realität. Du brauchst:
- Email-Format validieren
- Sicherstellen dass Namen nicht leer sind
- Einen Vollnamen berechnen
- Manche Properties öffentlich lesbar aber privat schreibbar machen
- Änderungen für Audit-Logs aufzeichnen
Plötzlich explodiert deine cleane 5-Zeilen-Klasse zu diesem Monster:
class User {
private string $email;
private string $firstName;
private string $lastName;
public function getEmail(): string {
return $this->email;
}
public function setEmail(string $email): void {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Ungültiges Email-Format');
}
$this->email = $email;
}
public function getFirstName(): string {
return $this->firstName;
}
public function setFirstName(string $firstName): void {
if (empty(trim($firstName))) {
throw new InvalidArgumentException('Vorname darf nicht leer sein');
}
$this->firstName = trim($firstName);
}
public function getLastName(): string {
return $this->lastName;
}
public function setLastName(string $lastName): void {
if (empty(trim($lastName))) {
throw new InvalidArgumentException('Nachname darf nicht leer sein');
}
$this->lastName = trim($lastName);
}
public function getFullName(): string {
return $this->firstName . ' ' . $this->lastName;
}
}
Das sind 40+ Zeilen für was eigentlich simpler Datenzugriff sein sollte. Und wir haben die Audit-Log-Logik noch nicht mal eingebaut.
Die Entdeckung: Property Hooks zur Rettung
PHP 8.4 führt einen radikal einfacheren Ansatz ein. Statt separate Getter- und Setter-Methoden zu schreiben, definierst du Hooks direkt auf dem Property selbst.
Hier ist die gleiche Funktionalität mit Property Hooks:
class User {
public string $email {
set {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Ungültiges Email-Format');
}
$this->email = $value;
}
}
public string $firstName {
set => trim($value) ?: throw new InvalidArgumentException('Vorname darf nicht leer sein');
}
public string $lastName {
set => trim($value) ?: throw new InvalidArgumentException('Nachname darf nicht leer sein');
}
public string $fullName {
get => $this->firstName . ' ' . $this->lastName;
}
}
15 Zeilen. Gleiche Validierung. Gleiche Funktionalität. 63% weniger Code.
Und es wird noch besser. Siehst du das $fullName Property? Das ist ein
virtuelles Property—es speichert gar keine Daten. Es wird on-the-fly aus
firstName und lastName berechnet, spart Speicher und eliminiert
Daten-Synchronisations-Probleme.
Die Basics verstehen: Get- und Set-Hooks
Property Hooks gibt es in zwei Varianten: get und set. Lass uns durchgehen
wie sie funktionieren.
Der Get-Hook
Ein get-Hook fängt Lese-Operationen ab. Jedes Mal wenn jemand auf das Property
zugreift, läuft deine eigene Logik:
class Product {
private float $priceInCents;
public float $price {
get => $this->priceInCents / 100;
set => $this->priceInCents = $value * 100;
}
}
$product = new Product();
$product->price = 19.99; // Intern als 1999 Cent gespeichert
echo $product->price; // Ausgabe: 19.99
Perfekt für:
- Abgeleitete Properties (wie Vollnamen aus Vor-/Nachnamen berechnen)
- Format-Konvertierungen (Cents speichern, Euro anzeigen)
- Lazy Loading (Daten nur bei Zugriff laden)
- Zugriffs-Logging (sicherheitsrelevante Properties)
Der Set-Hook
Ein set-Hook fängt Schreib-Operationen ab. Perfekt für Validierung,
Normalisierung und Seiteneffekte:
class Order {
public string $status {
set {
$allowedStatuses = ['pending', 'processing', 'completed', 'cancelled'];
if (!in_array($value, $allowedStatuses, true)) {
throw new InvalidArgumentException("Ungültiger Status: $value");
}
$this->status = $value;
$this->logStatusChange($value); // Seiteneffekt: Audit-Logging
}
}
private function logStatusChange(string $newStatus): void {
// Log in Datenbank, Notifications senden, etc.
}
}
Kurz- vs. Langform-Syntax
PHP 8.4 bietet zwei Syntaxen für Hooks, je nach Komplexität:
Kurzform (einzelner Ausdruck):
public string $fullName {
get => $this->firstName . ' ' . $this->lastName;
}
public string $email {
set => strtolower($value); // Wird automatisch $this->email zugewiesen
}
Langform (mehrere Anweisungen):
public string $email {
set {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Ungültige Email');
}
$this->email = strtolower($value);
}
}
Pro-Tipp: Nutze die Kurzform für einfache Transformationen, Langform wenn du Error-Handling oder mehrere Operationen brauchst.
Backed vs. Virtual Properties: Speicher-Optimierung
Hier wird’s richtig interessant. Property Hooks ermöglichen eine mächtige Unterscheidung zwischen backed und virtuellen Properties.
Backed Properties
Ein Backed Property speichert tatsächlich Daten im Speicher:
class User {
public string $username {
set => strtolower($value); // Backed: speichert den Wert
}
}
Wenn du $user->username = 'JohnDoe' schreibst, speichert PHP 'johndoe' im
Speicher.
Virtuelle Properties
Ein virtuelles Property berechnet seinen Wert on-the-fly—kein Storage nötig:
class Rectangle {
public function __construct(
public float $width,
public float $height
) {}
public float $area {
get => $this->width * $this->height; // Virtuell: kein Speicher
}
public float $perimeter {
get => 2 * ($this->width + $this->height);
}
}
$rect = new Rectangle(10, 5);
echo $rect->area; // 50
echo $rect->perimeter; // 30
Die $area- und $perimeter-Properties belegen null Bytes Speicher. Sie
werden jedes Mal beim Zugriff neu berechnet.
Wann virtuelle Properties nutzen:
- Berechnungen ableiten (Fläche aus Breite/Höhe)
- Format-Konvertierungen (gespeicherte Daten anders anzeigen)
- Aggregationen (Summen aus Collections berechnen)
Wann backed Properties nutzen:
- Du musst User-Input speichern
- Die Berechnung ist teuer (erwäge Caching)
- Du kommunizierst mit Datenbanken oder APIs
Asymmetrische Sichtbarkeit: Public Read, Private Write
Hier ist ein Pattern das du wahrscheinlich hundertmal geschrieben hast:
class BankAccount {
private float $balance = 0;
public function getBalance(): float {
return $this->balance;
}
public function deposit(float $amount): void {
$this->balance += $amount;
}
}
Du willst dass jeder den Kontostand lesen kann, aber nur die Klasse selbst soll ihn schreiben können. PHP 8.4 führt asymmetrische Sichtbarkeit genau für diesen Use Case ein:
class BankAccount {
public private(set) float $balance = 0;
public function deposit(float $amount): void {
$this->balance += $amount; // OK: wir sind in der Klasse
}
}
$account = new BankAccount();
echo $account->balance; // OK: public read
$account->balance = 1000; // FEHLER: private write
Die Syntax public private(set) bedeutet:
- Public read: Jeder kann auf
$account->balancezugreifen - Private write: Nur die
BankAccount-Klasse kann es ändern
Sichtbarkeits-Optionen
Du hast drei Write-Sichtbarkeits-Level:
class Example {
public private(set) string $privateWrite; // Nur diese Klasse kann schreiben
public protected(set) string $protectedWrite; // Diese Klasse + Kinder können schreiben
public public(set) string $publicWrite; // Jeder kann schreiben (wie einfach 'public')
}
Asymmetrische Sichtbarkeit mit Hooks
Du kannst asymmetrische Sichtbarkeit mit Property Hooks kombinieren:
class ShoppingCart {
private array $items = [];
public private(set) float $total {
get => array_sum(array_column($this->items, 'price'));
}
public function addItem(string $name, float $price): void {
$this->items[] = ['name' => $name, 'price' => $price];
$this->total; // Trigger Neuberechnung (falls für Caching benötigt)
}
}
Real-World Use Cases
Lass uns praktische Szenarien erkunden wo Property Hooks glänzen.
Use Case 1: API DTOs mit Validierung
Beim Bauen von REST-APIs brauchst du oft DTOs (Data Transfer Objects) mit Validierung:
class CreateUserRequest {
public string $email {
set {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new ValidationException('Ungültiges Email-Format');
}
$this->email = strtolower($value);
}
}
public string $password {
set {
if (strlen($value) < 12) {
throw new ValidationException('Passwort muss mindestens 12 Zeichen lang sein');
}
if (!preg_match('/[A-Z]/', $value)) {
throw new ValidationException('Passwort muss Großbuchstaben enthalten');
}
$this->password = $value;
}
}
public int $age {
set {
if ($value < 18) {
throw new ValidationException('Muss mindestens 18 Jahre alt sein');
}
$this->age = $value;
}
}
}
// Usage
$request = new CreateUserRequest();
$request->email = 'JOHN@EXAMPLE.COM'; // Gespeichert als 'john@example.com'
$request->password = 'SecurePass123';
$request->age = 25;
Vor PHP 8.4: Du bräuchtest eine separate Validator-Klasse oder müllst deinen Constructor mit Validierungs-Logik zu.
Nach PHP 8.4: Validierung lebt genau da wo sie hingehört—auf dem Property selbst.
Use Case 2: Lazy Loading von Relationen
In ORM-Szenarien willst du oft verwandte Daten nur laden wenn zugegriffen wird:
class Post {
private ?array $commentsCache = null;
public function __construct(
public readonly int $id,
private CommentRepository $commentRepo
) {}
public array $comments {
get {
if ($this->commentsCache === null) {
$this->commentsCache = $this->commentRepo->findByPostId($this->id);
}
return $this->commentsCache;
}
}
}
$post = new Post(123, $commentRepo);
// Noch keine Datenbank-Query
echo count($post->comments); // JETZT läuft die Query
echo count($post->comments); // Nutzt gecachten Wert
Use Case 3: Normalisierte Datenspeicherung
Speichere Daten in einem Format, exponiere sie in einem anderen:
class Event {
private int $startTimestamp;
private int $endTimestamp;
public DateTimeImmutable $startDate {
get => (new DateTimeImmutable())->setTimestamp($this->startTimestamp);
set {
$this->startTimestamp = $value->getTimestamp();
}
}
public DateTimeImmutable $endDate {
get => (new DateTimeImmutable())->setTimestamp($this->endTimestamp);
set {
$this->endTimestamp = $value->getTimestamp();
}
}
public int $durationInHours {
get => (int) (($this->endTimestamp - $this->startTimestamp) / 3600);
}
}
$event = new Event();
$event->startDate = new DateTimeImmutable('2025-12-01 09:00:00');
$event->endDate = new DateTimeImmutable('2025-12-01 17:00:00');
echo $event->durationInHours; // 8 Stunden
Intern speicherst du Unix-Timestamps (Integers, effizient). Nach außen arbeiten
Entwickler mit DateTimeImmutable-Objekten (typsicher, ergonomisch).
Use Case 4: Property Hooks in Interfaces
Das ist das Killer-Feature. Du kannst Property-Contracts in Interfaces definieren:
interface Identifiable {
public string $id { get; } // Implementierer MÜSSEN einen get-Hook bereitstellen
}
interface Timestamped {
public DateTimeImmutable $createdAt { get; }
public DateTimeImmutable $updatedAt { get; set; }
}
class Article implements Identifiable, Timestamped {
public string $id {
get => 'article-' . $this->internalId;
}
public DateTimeImmutable $createdAt {
get => new DateTimeImmutable($this->createdTimestamp);
}
public DateTimeImmutable $updatedAt {
get => new DateTimeImmutable($this->updatedTimestamp);
set {
$this->updatedTimestamp = $value->format('Y-m-d H:i:s');
}
}
private int $internalId = 42;
private string $createdTimestamp = '2025-01-01 00:00:00';
private string $updatedTimestamp = '2025-01-01 00:00:00';
}
Warum das wichtig ist: Vor PHP 8.4 konnten Interfaces nur Methoden definieren. Jetzt kannst du Property-Contracts durchsetzen, was deine APIs vorhersagbarer und typsicherer macht.
Migrations-Guide: Von Gettern/Settern zu Hooks
Lass uns ein Real-World-Migrations-Szenario durchgehen.
Vorher: Traditionelle Getter/Setter
class Product {
private string $sku;
private float $price;
private int $stock;
private bool $available;
public function getSku(): string {
return $this->sku;
}
public function setSku(string $sku): void {
$this->sku = strtoupper(trim($sku));
}
public function getPrice(): float {
return $this->price;
}
public function setPrice(float $price): void {
if ($price < 0) {
throw new InvalidArgumentException('Preis kann nicht negativ sein');
}
$this->price = $price;
}
public function getStock(): int {
return $this->stock;
}
public function setStock(int $stock): void {
$this->stock = max(0, $stock);
$this->updateAvailability();
}
public function isAvailable(): bool {
return $this->available;
}
private function updateAvailability(): void {
$this->available = $this->stock > 0;
}
}
// Usage
$product = new Product();
$product->setSku(' abc-123 ');
$product->setPrice(99.99);
$product->setStock(10);
echo $product->getSku(); // ABC-123
Nachher: Property Hooks
class Product {
public string $sku {
set => strtoupper(trim($value));
}
public float $price {
set => $value >= 0
? $value
: throw new InvalidArgumentException('Preis kann nicht negativ sein');
}
public int $stock {
set {
$this->stock = max(0, $value);
}
}
public bool $available {
get => $this->stock > 0; // Virtuelles Property!
}
}
// Usage
$product = new Product();
$product->sku = ' abc-123 ';
$product->price = 99.99;
$product->stock = 10;
echo $product->sku; // ABC-123
echo $product->available ? 'Auf Lager' : 'Nicht verfügbar';
Was hat sich geändert:
- 58 Zeilen → 18 Zeilen (69% Reduktion)
updateAvailability()-Methode entfernt—wird jetzt automatisch berechnet- Simplere API: Properties statt Methoden
- Gleiche Validierungs-Logik, klarere Absicht
Migrations-Checkliste
Beim Konvertieren deiner Codebase:
- Identifiziere pure Getter (geben nur den Wert zurück) → Entferne sie, mache Property public
- Finde simple Setter (Validierung/Normalisierung) → Konvertiere zu
set-Hooks - Spotte berechnete Properties (
getFullName(),isActive()) → Konvertiere zu virtuellen Properties mitget-Hooks - Suche nach voneinander abhängigen Properties → Nutze Hooks um Konsistenz zu wahren
- Checke Interface-Contracts → Definiere Property-Anforderungen in Interfaces
Production-Überlegungen und Gotchas
Property Hooks sind mächtig, aber sie haben auch ihre Tücken.
1. Keine Static Properties
Property Hooks funktionieren nur auf Instanz-Properties:
class Config {
public static string $env { // ❌ Parse error
get => $_ENV['APP_ENV'];
}
}
Workaround: Nutze eine normale statische Methode:
class Config {
public static function getEnv(): string {
return $_ENV['APP_ENV'];
}
}
2. Keine Referenzen
Du kannst keine Referenzen auf gehooked Properties erstellen:
class User {
public string $name {
set => ucfirst($value);
}
}
$user = new User();
$ref = &$user->name; // ❌ Fatal error
Das ist gewollt—Referenzen würden den set-Hook umgehen und Encapsulation
brechen.
3. Performance bei großen Arrays
Sei vorsichtig mit set-Hooks auf Array-Properties:
class Dataset {
public array $values {
set {
// Das läuft JEDES Mal wenn das Array sich ändert
foreach ($value as $item) {
if (!is_numeric($item)) {
throw new InvalidArgumentException('Alle Werte müssen numerisch sein');
}
}
$this->values = $value;
}
}
}
$dataset = new Dataset();
$dataset->values = range(1, 10000); // Validiert 10.000 Items
$dataset->values[] = 10001; // Validiert 10.001 Items nochmal!
Bei großen Collections validiere nur bei add/remove:
class Dataset {
private array $values = [];
public function addValue(float $value): void {
if (!is_numeric($value)) {
throw new InvalidArgumentException('Wert muss numerisch sein');
}
$this->values[] = $value;
}
public function getValues(): array {
return $this->values;
}
}
4. Debugging-Herausforderungen
Property Hooks können Debugging kniffliger machen weil es keine explizite Methode gibt auf der man Breakpoints setzen kann:
class Order {
public string $status {
set {
// Wo setzt du einen Breakpoint? Bei der Property-Deklaration?
$this->validateStatus($value);
$this->notifyStatusChange($value);
$this->status = $value;
}
}
}
Pro-Tipp: Moderne IDEs (PhpStorm 2024.3+) unterstützen Breakpoints in Property Hooks. Update deine IDE!
5. Readonly-Inkompatibilität
Property Hooks sind inkompatibel mit readonly-Properties:
class User {
public readonly string $id { // ❌ Parse error
get => 'user-' . $this->internalId;
}
}
Workaround: Nutze stattdessen asymmetrische Sichtbarkeit:
class User {
public private(set) string $id {
get => 'user-' . $this->internalId;
}
}
Die Alternativen (Und warum Property Hooks gewinnen)
Lass uns ehrlich über die Konkurrenz sprechen.
Option 1: Traditionelle Getter/Setter
class Product {
private float $price;
public function getPrice(): float {
return $this->price;
}
public function setPrice(float $price): void {
$this->price = $price;
}
}
Pros:
- Universelles Pattern (funktioniert in älteren PHP-Versionen)
- Klare Method-Signaturen für IDE-Autocomplete
- Einfach Breakpoints zu setzen
Cons:
- Massiver Boilerplate für simple Properties
- Inkonsistente APIs (
$product->getPrice()vs.$product->price) - Keine Interface-Durchsetzung für Properties
Fazit: Nutze das wenn du auf PHP < 8.4 feststeckst. Ansonsten, upgrade.
Option 2: Magic Methods (__get/__set)
class User {
private array $data = [];
public function __get(string $name): mixed {
return $this->data[$name] ?? null;
}
public function __set(string $name, mixed $value): void {
$this->data[$name] = $value;
}
}
Pros:
- Dynamischer Property-Zugriff
- Keine expliziten Property-Deklarationen nötig
Cons:
- Null Typsicherheit (Goodbye statische Analyse)
- Performance-Overhead (Magic Methods sind langsamer)
- Kein IDE-Autocomplete
- Debugging-Albtraum
Fazit: Vermeide das außer du baust einen dynamischen Proxy oder ORM-Internals. Property Hooks geben dir die Flexibilität ohne Typsicherheit zu opfern.
Option 3: Public Properties (YOLO-Ansatz)
class User {
public string $email;
public string $firstName;
public string $lastName;
}
$user = new User();
$user->email = 'INVALID EMAIL'; // Ups, keine Validierung
Pros:
- Minimaler Code
- Schnelle Performance
Cons:
- Keine Validierung
- Keine Encapsulation
- Kann später keine Logik hinzufügen ohne Breaking Changes
Fazit: OK für interne DTOs, aber Property Hooks lassen dich simpel starten und später Validierung hinzufügen ohne deine API zu ändern.
Option 4: Property Hooks (Der Gewinner)
class User {
public string $email {
set => filter_var($value, FILTER_VALIDATE_EMAIL)
? strtolower($value)
: throw new InvalidArgumentException('Ungültige Email');
}
}
Pros:
- ✅ Typsicher
- ✅ Minimaler Boilerplate
- ✅ Interface-Durchsetzung
- ✅ Virtuelle Properties sparen Speicher
- ✅ Konsistente Property-basierte API
Cons:
- Benötigt PHP 8.4+
- Lernkurve fürs Team
- Weniger vertrautes Pattern (im Moment)
Fazit: Das ist die pragmatische Wahl für neue Projekte. Die Vorteile überwiegen die Lernkurve bei Weitem.
Best Practices: Hooks effektiv nutzen
Nach Migration mehrerer Production-Projekte zu PHP 8.4, hier meine hart erkämpften Lektionen:
1. Halte Hooks simpel
Schlecht (zu viel Logik):
public string $email {
set {
$value = strtolower(trim($value));
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
$this->logger->error('Ungültige Email', ['email' => $value]);
$this->emailValidator->recordFailure($value);
throw new InvalidArgumentException('Ungültige Email');
}
if ($this->isDuplicate($value)) {
$this->logger->warning('Doppelte Email', ['email' => $value]);
throw new DuplicateEmailException($value);
}
$this->email = $value;
$this->emailChangedAt = new DateTimeImmutable();
$this->sendVerificationEmail($value);
}
}
Gut (delegierte Logik):
public string $email {
set {
$this->email = $this->validateAndNormalizeEmail($value);
$this->handleEmailChange($value);
}
}
private function validateAndNormalizeEmail(string $email): string {
$email = strtolower(trim($email));
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Ungültige Email');
}
if ($this->isDuplicate($email)) {
throw new DuplicateEmailException($email);
}
return $email;
}
private function handleEmailChange(string $newEmail): void {
$this->emailChangedAt = new DateTimeImmutable();
$this->sendVerificationEmail($newEmail);
}
Warum: Hooks sollten orchestrieren, nicht implementieren. Halte komplexe Logik in testbaren Methoden.
2. Nutze virtuelle Properties für teure Berechnungen weise
Schlecht (Performance-Falle):
class Report {
public array $metrics {
get {
// Das läuft bei JEDEM Zugriff, auch in Loops
return $this->calculateComplexMetrics(); // 500ms Query
}
}
}
foreach ($reports as $report) {
echo $report->metrics['total']; // 500ms * N Reports = Katastrophe
}
Gut (gecachte Berechnung):
class Report {
private ?array $metricsCache = null;
public array $metrics {
get {
if ($this->metricsCache === null) {
$this->metricsCache = $this->calculateComplexMetrics();
}
return $this->metricsCache;
}
}
public function refreshMetrics(): void {
$this->metricsCache = null; // Erzwinge Neuberechnung
}
}
3. Dokumentiere Hook-Seiteneffekte
class Order {
/**
* Das Setzen des Status triggert:
* - Validierung gegen erlaubte Status-Übergänge
* - Audit-Log-Eintrag
* - Email-Benachrichtigung an Kunden
* - Inventory-Update bei stornierten Bestellungen
*/
public string $status {
set {
$this->validateStatusTransition($value);
$this->logStatusChange($value);
$this->notifyCustomer($value);
if ($value === 'cancelled') {
$this->restoreInventory();
}
$this->status = $value;
}
}
}
Warum: Property-Zuweisung sieht harmlos aus
($order->status = 'cancelled'), kann aber signifikante Seiteneffekte auslösen.
Dokumentiere sie klar.
4. Kombiniere mit Constructor Property Promotion
class CreateUserRequest {
public function __construct(
public string $email {
set => filter_var($value, FILTER_VALIDATE_EMAIL)
? strtolower($value)
: throw new ValidationException('Ungültige Email');
},
public string $username {
set => strlen($value) >= 3
? $value
: throw new ValidationException('Username zu kurz');
},
public int $age {
set => $value >= 18
? $value
: throw new ValidationException('Muss 18+ sein');
}
) {}
}
// Usage
$request = new CreateUserRequest(
email: 'JOHN@EXAMPLE.COM',
username: 'john_doe',
age: 25
);
Constructor Property Promotion + Property Hooks = ultra-kompakte DTOs.
5. Teste Hook-Verhalten explizit
class ProductTest extends TestCase {
public function test_price_cannot_be_negative(): void {
$product = new Product();
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Preis kann nicht negativ sein');
$product->price = -10.00;
}
public function test_stock_cannot_go_below_zero(): void {
$product = new Product();
$product->stock = -5;
$this->assertSame(0, $product->stock);
}
public function test_availability_reflects_stock(): void {
$product = new Product();
$product->stock = 0;
$this->assertFalse($product->available);
$product->stock = 1;
$this->assertTrue($product->available);
}
}
Hooks sind Verhalten. Teste sie wie Methoden.
Die Transformation
Vor drei Monaten hatte die Codebase meines Teams 1.247 Getter/Setter-Methoden über 183 Klassen verteilt. Das sind ~6.800 Zeilen Boilerplate.
Nach Migration zu PHP 8.4 Property Hooks:
- Entfernt: 1.091 Methoden (87% der Getter/Setter)
- Konvertiert: 438 Properties zu Hooks
- Hinzugefügt: 67 neue virtuelle Properties für berechnete Werte
- Resultat: 4.200 Zeilen weniger Code, gleiche Funktionalität
Aber was wirklich zählt: unsere Junior-Entwickler verstehen unsere Domain-Models
jetzt schneller. Statt durch 50 Getter-Methoden zu scrollen um eine
User-Klasse zu verstehen, sehen sie 12 Properties mit Inline-Validierung. Der
Code erzählt die Geschichte.
Du bist dran
PHP 8.4 Property Hooks sind nicht nur syntaktischer Zucker—sie sind ein Paradigmenwechsel. Sie lassen dich:
- Weniger Code schreiben (50-70% Reduktion in typischen Use Cases)
- Besser kapseln (Validierung lebt beim Property)
- Speicher optimieren (virtuelle Properties für berechnete Werte)
- Contracts durchsetzen (Property Hooks in Interfaces)
- Klarheit wahren (asymmetrische Sichtbarkeit für Read/Write-Trennung)
Wenn du ein neues Projekt startest, nutze Property Hooks vom ersten Tag an. Wenn du Legacy-Code wartest, fange an inkrementell zu migrieren—eine Klasse nach der anderen.
Was ist dein größtes Problem mit Gettern und Settern? Hast du Property Hooks schon ausprobiert? Ich würde gerne deine Migrations-Geschichten und Gotchas hören die du entdeckt hast.
Jetzt geh und mach deine Objekte 50% kleiner. Das zukünftige Ich wird dem jetzigen Ich danken.