readonly und Constructor Promotion – Wertobjekte in PHP ohne Boilerplate
Früher waren das dreißig Zeilen: Eigenschaften deklarieren, Konstruktor schreiben, alles zuweisen, Getter dazu. Seit PHP 8.1 ist derselbe unveränderliche Wertetyp eine einzige Zeile – und niemand kann ihn nachträglich kaputt machen.
So sah eine kleine Datenklasse in PHP lange aus:
class Adresse
{
private string $strasse;
private string $plz;
private string $ort;
public function __construct(string $strasse, string $plz, string $ort)
{
$this->strasse = $strasse;
$this->plz = $plz;
$this->ort = $ort;
}
public function getStrasse(): string { return $this->strasse; }
public function getPlz(): string { return $this->plz; }
public function getOrt(): string { return $this->ort; }
}Zwanzig Zeilen, in denen jeder Feldname dreimal steht. Seit PHP 8.1 ist dasselbe:
final class Adresse
{
public function __construct(
public readonly string $strasse,
public readonly string $plz,
public readonly string $ort,
) {}
}Zwei Dinge passieren hier gleichzeitig.
Constructor Property Promotion
public string $strasse im Konstruktor deklariert die Eigenschaft und weist sie zu, in einem Schritt. Das ist reine Schreibarbeit, die wegfällt — die Klasse verhält sich exakt wie vorher.
readonly
Das ist der interessantere Teil. Eine readonly-Eigenschaft darf genau einmal gesetzt werden, und zwar aus der Klasse selbst heraus. Danach ist sie fest:
$a = new Adresse('Hauptstr. 1', '90402', 'Nürnberg');
echo $a->ort; // Nürnberg
$a->ort = 'Fürth'; // Error: Cannot modify readonly propertyKein privates Feld plus Getter mehr nötig, nur um Schreibzugriff zu verhindern. Die Eigenschaft darf public sein — lesen ist erlaubt, ändern nicht.
Warum mir das wirklich hilft
Der Gewinn ist nicht die gesparte Tipparbeit, sondern das, was danach nicht mehr passieren kann. Ein Objekt, das man nach dem Erzeugen nicht mehr verändern kann, hat keinen Zustand, den irgendein anderer Teil des Codes heimlich umbiegt. Wenn ich eine Adresse in eine Funktion gebe, weiß ich: Was zurückkommt, ist noch dieselbe Adresse.
Genau deshalb setze ich das bei allem ein, was einen Wert darstellt und nicht ein veränderliches Ding: Geldbeträge, Zeiträume, Konfigurationen, API-Antworten, Zeilen aus einer Datenbank auf dem Weg in die Ausgabe.
Ändern heißt: ein neues Objekt
Wenn man doch etwas ändern will, baut man eine Kopie:
final class Adresse
{
public function __construct(
public readonly string $strasse,
public readonly string $plz,
public readonly string $ort,
) {}
public function mitOrt(string $ort): self
{
return new self($this->strasse, $this->plz, $ort);
}
}
$neu = $alt->mitOrt('Fürth'); // $alt bleibt unangetastetDas ist derselbe Gedanke wie DateTimeImmutable gegenüber DateTime: Nicht das Original verändern, sondern ein neues Ergebnis liefern.
Zwei Stolpersteine
readonlygilt nicht in die Tiefe. Ist die Eigenschaft ein Array, kannst du das Array nicht austauschen — aber ist sie ein Objekt, lässt sich dessen Inneres weiterhin verändern. Wenn es wirklich fest sein soll, müssen auch die enthaltenen Objektereadonlysein.- Ein Standardwert ist nicht erlaubt.
public readonly string $ort = 'Nürnberg'geht nicht — der Wert muss im Konstruktor kommen. Standardwerte setzt man deshalb am Parameter.
Seit PHP 8.2 darf man übrigens auch die ganze Klasse als readonly markieren, dann gilt es automatisch für alle Eigenschaften.
Du hast ein PHP-Projekt, in dem Daten unterwegs unerwartet ihren Wert ändern? Das ist ein typischer Fall dafür. Melde dich, ich schaue es mir an.
Quellen
Du brauchst mehr als ein Snippet?
Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.
Projekt anfragen →Verwandte Snippets
First-Class-Callable-Syntax in PHP: aus strlen(...) wird ein echter Wert
Funktionen als Zeichenketten weiterzureichen – 'strlen', [$objekt, 'methode'] – hat nie jemand gemocht: kein Tippfehlerschutz, keine Sprungmarke in der IDE. Seit PHP 8.1 schreibst du drei Punkte und bekommst ein Closure-Objekt.
array_find, array_any und array_all in PHP 8.4 – Arrays durchsuchen ohne Schleife
„Gibt es einen inaktiven Eintrag?", „Sind alle bezahlt?", „Hol mir den ersten Treffer" – dafür hast du bisher eine foreach-Schleife mit break geschrieben. PHP 8.4 macht daraus je eine Zeile, die genau sagt, was sie meint.
@layer in CSS: Kaskadenebenen statt !important-Wettrüsten
Irgendwann steht in jedem gewachsenen Stylesheet das erste !important – und ein halbes Jahr später das dreißigste. Mit @layer legst du die Reihenfolge einmal fest, und ab da gewinnt nicht mehr der längste Selektor, sondern die Ebene, die du bestimmt hast.