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
Benannte Argumente in PHP 8 – Schluss mit true, false, null, true
Ein Aufruf wie erstelle($a, true, false, null, true) sagt nichts darüber, was die Wahrheitswerte bedeuten. Mit benannten Argumenten steht es im Aufruf – und optionale Parameter lassen sich überspringen.
Beträge und Zahlen in PHP formatieren – number_format, sprintf und Intl
1234.5 ist keine Anzeige, sondern ein Wert. Auf der Rechnung soll 1.234,50 € stehen, in der API 1234.50 und im Dateinamen 1234-50. Drei Aufgaben, drei Werkzeuge – und ein Fehler, den fast jeder einmal macht.
Reguläre Ausdrücke in PHP – benannte Gruppen statt $m[3]
Ein Treffer-Array aus Zahlen liest sich nach zwei Wochen wie eine Geheimschrift. Benannte Gruppen geben jedem Teil einen Namen – und preg_replace_callback ersetzt damit Dinge, die kein einfacher Ersetzungsstring kann.