Asymmetric Visibility in PHP 8.4: public get, private set
AI generated
<?php
8.4
PHP · Asymmetric Visibility · PHP 8.4
Asymmetric Visibility in PHP 8.4
public get, private set ohne Boilerplate-Getter

Jahrelang griffen Entwickler zu privaten Properties mit öffentlichem Getter, nur um Lesbarkeit von außen zu erlauben, ohne Schreibzugriff zu öffnen. Asymmetric Visibility beendet diesen Umweg: Eine Property kann öffentlich lesbar und gleichzeitig privat oder protected beschreibbar sein, direkt in der Deklaration, ohne eine einzige Zeile Getter-Code.

14 Min. Lesezeit public private(set) · public protected(set) · readonly PHP 8.4

1. Das Problem: public Property aber kontrollierte Schreibzugriffe

Vor PHP 8.4 gab es keine Möglichkeit, eine Property gleichzeitig von außen lesbar und von außen schreibgeschützt zu deklarieren. Sichtbarkeit war immer symmetrisch: public bedeutete lesbar und schreibbar für jeden, private bedeutete beides nur innerhalb der eigenen Klasse. Wollte man eine Property öffentlich lesbar machen, ohne beliebigen Schreibzugriff zu erlauben, musste man die Property als private deklarieren und zusätzlich eine öffentliche Getter-Methode schreiben, die ausschließlich den Wert zurückgibt. Diese Notlösung erzeugt reinen Boilerplate-Code: eine Zeile Property-Deklaration wird zu einer Property plus einer vollständigen Methode, nur um Lesbarkeit zu erlauben.

Das eigentliche Problem geht über die reine Zeilenzahl hinaus. In großen Codebasen mit vielen Entitäten und Value Objects vervielfacht sich dieses Muster: Für jede Property, die von außen gelesen werden soll, entsteht eine eigene getXxx()-Methode, die inhaltlich nichts weiter tut als return $this->xxx;. IDEs generieren diese Methoden automatisch, was das Problem verschleiert, aber nicht löst. Jede generierte Methode muss dokumentiert, getestet und bei Refactorings mitgepflegt werden, obwohl sie keinerlei eigene Logik enthält. Asymmetric Visibility löst genau dieses Problem an der Wurzel, indem die Sichtbarkeit für Lese- und Schreibzugriff getrennt direkt an der Property deklariert wird.

Ein weiterer Aspekt, der oft übersehen wird: Die klassische Lösung mit privater Property und Getter schützt nur den Zugriff von außerhalb der Klasse. Innerhalb der Klasse selbst bleibt die Property weiterhin uneingeschränkt beschreibbar, an jeder beliebigen Stelle im Code, auch dort, wo eine Änderung fachlich gar nicht vorgesehen war. Asymmetric Visibility mit protected(set) ermöglicht zusätzlich eine feinere Abstufung: Schreibzugriff für Unterklassen erlauben, aber weiterhin für komplett externen Code verbieten, eine Kombination, die mit reinen Gettern gar nicht abbildbar war.

Szenario Klassisch (private + Getter) Asymmetric Visibility Vorteil
Öffentlich lesbare ID private $id + getId() public private(set) string $id; Direkter Property-Zugriff, kein Methodenaufruf nötig
Schreibschutz nach außen Nur Konvention, kein Compiler-Schutz Zur Compile-Zeit erzwungen Verstoß wird sofort als Fehler erkannt, nicht erst im Review
Konstruktor-Initialisierung Property, Getter und Zuweisung an drei Stellen Direkt in Constructor Promotion kombinierbar Deklaration und Zugriffsregel an einem Ort
Statische Analyse PHPStan erkennt nur die Konvention, keine echte Kapselung Sichtbarkeitsverstoß ist ein echter Analysefehler Fehler werden vor dem Deployment gefunden
Vererbung Getter-Sichtbarkeit frei überschreibbar Set-Sichtbarkeit darf in Kindklassen nicht aufgeweicht werden Garantie der Elternklasse bleibt über Vererbung hinweg gültig
Lesbarkeit im Code-Review Zusätzliche Methode pro Property nötig Sichtbarkeit direkt in der Deklaration sichtbar Weniger Code, Absicht auf einen Blick erkennbar

2. Syntax: public private(set) und public protected(set)

Die Grundsyntax von Asymmetric Visibility fügt der klassischen Sichtbarkeitsangabe eine zweite, optionale Sichtbarkeit für Schreibzugriffe hinzu: public private(set) string $sku; deklariert eine Property, die von überall lesbar ist, aber nur innerhalb der deklarierenden Klasse beschrieben werden darf. Analog erlaubt public protected(set) int $stockLevel; Lesezugriff von überall, während Schreibzugriff auf die deklarierende Klasse und ihre Unterklassen beschränkt bleibt. Wird keine Set-Sichtbarkeit angegeben, bleibt das Verhalten wie gewohnt symmetrisch: Lese- und Schreibzugriff folgen derselben Sichtbarkeit.

Eine feste Regel bestimmt, welche Kombinationen zulässig sind: Die Set-Sichtbarkeit muss mindestens so restriktiv sein wie die Get-Sichtbarkeit, niemals weniger restriktiv. Erlaubt sind daher public private(set), public protected(set) und protected private(set). Nicht erlaubt ist der umgekehrte Fall, etwa eine Property, die nach außen nur protected lesbar, aber public beschreibbar wäre, das würde bedeuten, dass mehr Code schreiben dürfte, als überhaupt lesen kann, was der ganzen Idee von Kapselung widerspricht. Bei private als Get-Sichtbarkeit ergibt eine zusätzliche Set-Sichtbarkeit keinen Sinn, da private bereits die restriktivste Stufe darstellt, der Parser lehnt eine solche redundante Angabe ab.

Wichtig ist außerdem, dass Asymmetric Visibility ausschließlich für typisierte Properties zur Verfügung steht. Untypisierte Properties, wie sie in älterem PHP-Code häufig vorkommen, können keine getrennte Set-Sichtbarkeit erhalten, ein weiterer guter Grund, bestehenden Code konsequent mit Typdeklarationen zu versehen. Innerhalb der deklarierenden Klasse verhält sich eine Property mit Asymmetric Visibility bei Lese- und Schreibzugriffen wie eine ganz normale Property, es gibt keine versteckte Methode und keinen Performance-Overhead, die Einschränkung wird ausschließlich zur Compile-Zeit vom Parser durchgesetzt.


declare(strict_types=1);

final class Product
{
    // Public get, private set: readable from anywhere, writable only inside Product
    public private(set) string $sku;

    // Public get, protected set: readable from anywhere, writable in Product and subclasses
    public protected(set) int $stockLevel = 0;

    public function __construct(string $sku)
    {
        $this->sku = $sku;
    }

    public function reduceStock(int $amount): void
    {
        // Allowed: write happens inside the declaring class
        $this->stockLevel -= $amount;
    }
}

$product = new Product('SKU-001');
echo $product->sku;         // Allowed: public get
echo $product->stockLevel;  // Allowed: public get

$product->sku = 'SKU-002';   // Fatal error: cannot modify private(set) property from global scope
$product->stockLevel = 500;  // Fatal error: cannot modify protected(set) property from global scope

3. Kombination mit readonly

Asymmetric Visibility und readonly lösen unterschiedliche Probleme und lassen sich kombinieren, um eine noch engere Kontrolle zu erreichen. readonly allein erlaubt genau eine Zuweisung aus dem deklarierenden Scope heraus, jeder weitere Schreibversuch, auch innerhalb derselben Klasse, führt zu einem Fehler. Reine Asymmetric Visibility ohne readonly erlaubt dagegen beliebig viele Schreibzugriffe, solange sie aus dem erlaubten Scope kommen, etwa wenn eine Methode denselben Wert im Laufe des Objektlebenszyklus mehrfach neu setzen darf. Die Kombination public private(set) readonly string $invoiceNumber; vereint beides: von außen nur lesbar, von innen nur ein einziges Mal beschreibbar.

Der praktische Unterschied zeigt sich bei Properties, die sich im Laufe der Objektlebensdauer legitim ändern, aber trotzdem niemals von außen verändert werden dürfen. Ein Bestellstatus, der von draft über confirmed zu shipped wechselt, braucht mehrfachen internen Schreibzugriff und darf daher kein readonly tragen, wohl aber private(set), damit ausschließlich Methoden der eigenen Klasse den Zustand verändern können. Eine Rechnungsnummer hingegen wird genau einmal im Konstruktor vergeben und soll sich danach nie wieder ändern, hier ist die Kombination aus Asymmetric Visibility und readonly die richtige Wahl.

Seit PHP 8.4 wurden zudem die Regeln für readonly-Properties selbst gelockert: Eine readonly-Property kann nun auch dann noch initialisiert werden, wenn sie in der Elternklasse deklariert, aber dort noch nicht befüllt wurde, was in Kombination mit protected(set) Vererbungsszenarien ermöglicht, die vorher nur mit zusätzlichen Konstruktor-Parametern lösbar waren. Wichtig bleibt, dass readonly ausschließlich beschreibt, wie oft geschrieben werden darf, während Asymmetric Visibility beschreibt, von wo aus geschrieben werden darf, beide Achsen sind unabhängig voneinander konfigurierbar.


declare(strict_types=1);

final class Invoice
{
    // Asymmetric visibility restricts WHO can write, readonly restricts HOW OFTEN
    public private(set) readonly string $invoiceNumber;

    // Without readonly: internal code may reassign multiple times
    public private(set) string $status = 'draft';

    public function __construct(string $invoiceNumber)
    {
        $this->invoiceNumber = $invoiceNumber; // Allowed: first and only assignment
    }

    public function finalize(): void
    {
        $this->status = 'finalized'; // Allowed: multiple internal writes remain possible
    }

    public function attemptReassign(): void
    {
        // $this->invoiceNumber = 'X-99'; // Fatal error: cannot modify readonly property
    }
}

4. Asymmetric Visibility in Constructor Property Promotion

Constructor Property Promotion und Asymmetric Visibility ergänzen sich besonders gut, weil beide Features darauf abzielen, Boilerplate zu reduzieren, ohne Klarheit zu opfern. Statt eine Property zu deklarieren, sie im Konstruktor entgegenzunehmen und danach explizit zuzuweisen, schreibt man die vollständige Sichtbarkeitsangabe direkt im Parameter des Konstruktors: public function __construct(public private(set) int $amountInCents) {}. PHP erzeugt daraus automatisch eine Property mit exakt dieser asymmetrischen Sichtbarkeit und übernimmt die Zuweisung, ganz ohne zusätzliche Zeile im Methodenkörper.

Diese Kombination ist besonders wertvoll für Value Objects, die aus mehreren unveränderlichen oder zumindest von außen unveränderlichen Feldern bestehen. Eine Money-Klasse mit Betrag und Währung lässt sich in wenigen Zeilen so definieren, dass beide Felder öffentlich lesbar, aber ausschließlich innerhalb der Klasse selbst schreibbar sind, etwa um in einer add()-Methode ein neues Money-Objekt mit korrekt validierter Währung zurückzugeben. Der Konstruktor bleibt dabei so kompakt wie bei einem reinen Daten-Container, während die Zugriffskontrolle vollständig erhalten bleibt.

Ein Detail, das in der Praxis häufig übersehen wird: Auch bei promovierten Properties gelten dieselben Kombinationsregeln wie bei regulär deklarierten Properties. Die Set-Sichtbarkeit darf nicht offener sein als die Get-Sichtbarkeit, und ohne explizite Typangabe funktioniert Property Promotion mit Asymmetric Visibility ebenfalls nicht, da promovierte Properties in PHP grundsätzlich typisiert sein müssen. Wer mehrere promovierte Parameter mit unterschiedlichen Sichtbarkeitskombinationen mischt, etwa ein öffentlich beschreibbares Flag neben mehreren private(set)-Feldern, sollte das bewusst und konsistent im Klassendesign begründen, damit die Absicht für andere Entwickler nachvollziehbar bleibt.


declare(strict_types=1);

final class Money
{
    public function __construct(
        public private(set) int $amountInCents,
        public private(set) string $currency,
    ) {
    }

    public function add(Money $other): self
    {
        if ($this->currency !== $other->currency) {
            throw new InvalidArgumentException('Currency mismatch');
        }

        // Internal write is allowed because we are inside the declaring class
        return new self($this->amountInCents + $other->amountInCents, $this->currency);
    }
}

$price = new Money(1999, 'EUR');
echo $price->amountInCents; // Allowed: public get
// $price->amountInCents = 0; // Fatal error: private(set) from outside the class

5. Vererbung und Sichtbarkeitsregeln bei überschriebenen Properties

Sobald eine Klasse mit Asymmetric Visibility vererbt wird, greifen Regeln, die den klassischen Kovarianz-Regeln bei Methoden-Signaturen ähneln. Die Get-Sichtbarkeit darf in einer Unterklasse wie gewohnt aufgeweitet werden, eine als protected deklarierte Property darf in einer Kindklasse also public lesbar gemacht werden. Für die Set-Sichtbarkeit gilt jedoch die entgegengesetzte Regel: Sie darf niemals aufgeweitet werden, nur beibehalten oder weiter verengt. Eine Property, die in der Elternklasse als protected(set) deklariert wurde, kann in der Kindklasse also höchstens private(set) werden, niemals aber public beschreibbar.

Diese Einschränkung ist kein willkürliches Sprachdetail, sondern eine notwendige Konsequenz aus dem Liskovschen Substitutionsprinzip. Wenn Code mit einer Referenz auf die Elternklasse arbeitet und sich darauf verlässt, dass eine Property nicht von außen beschrieben werden kann, dürfte eine Unterklasse dieses Versprechen nicht heimlich aufheben, nur weil sie an derselben Stelle im Vererbungsbaum steht. Asymmetric Visibility behandelt dieses Versprechen deshalb als Teil des öffentlichen Vertrags der Elternklasse, genauso wie Parametertypen und Rückgabetypen bei Methoden.

In der Praxis bedeutet das: Wer eine Basisklasse mit protected(set)-Properties entwirft, gibt allen Unterklassen kontrollierten Schreibzugriff, kann aber sicher sein, dass keine Unterklasse diesen Zugriff versehentlich für komplett externen Code öffnet. Das ist besonders wertvoll bei Domain-Modellen mit Klassenhierarchien, etwa einer Employee-Basisklasse mit mehreren spezialisierten Rollen, bei denen das Abteilungsfeld zwar von jeder Rolle intern verändert werden darf, aber niemals direkt von einem Controller oder einer Formularverarbeitung überschrieben werden soll.


declare(strict_types=1);

class Employee
{
    // Set-visibility is protected(set): subclasses may write, outside code may not
    public protected(set) string $department;

    public function __construct(string $department)
    {
        $this->department = $department;
    }
}

final class Manager extends Employee
{
    public function reassign(string $department): void
    {
        // Allowed: protected(set) is inherited, Manager can write internally
        $this->department = $department;
    }

    // Not allowed in a redeclaration further down the hierarchy:
    // public string $department; // Fatal error: cannot widen inherited set-visibility
}

6. Unterschied zu Property Hooks

Asymmetric Visibility wird in der Praxis oft mit Property Hooks verwechselt, weil beide Features Lese- und Schreibzugriff auf Properties genauer steuern. Der Unterschied ist grundlegend: Asymmetric Visibility ist eine reine Sichtbarkeitsregel ohne jede eigene Logik, sie entscheidet ausschließlich, wer eine Property lesen und wer sie beschreiben darf, führt aber selbst keinen Code aus. Property Hooks fügen dagegen ausführbaren Code beim Lesen oder Schreiben hinzu, etwa berechnete Werte, Validierung oder Seiteneffekte, was ein eigenständiges Thema mit eigener Tiefe ist und hier bewusst nicht weiter vertieft wird.

Ähnlich verhält es sich mit readonly: Wo readonly nach der ersten Zuweisung jeden weiteren Schreibzugriff kategorisch verbietet, egal aus welchem Scope er kommt, erlaubt Asymmetric Visibility weiterhin Schreibzugriffe aus dem deklarierenden oder erweiterten Scope, nur eben nicht von außerhalb. Wer also eine Property braucht, die sich im Laufe ihres Lebens legitim mehrfach ändert, aber niemals von außen, greift zu Asymmetric Visibility, wer eine Property braucht, die sich nach der Initialisierung überhaupt nicht mehr ändern soll, greift zu readonly, und wer Berechnungslogik beim Zugriff selbst benötigt, verwendet Property Hooks.

Diese klare Trennung der Verantwortlichkeiten ist einer der größten Vorteile der PHP 8.4-Property-Erweiterungen: Statt eines einzigen überladenen Mechanismus gibt es drei orthogonale Werkzeuge, die sich frei kombinieren lassen. Asymmetric Visibility beantwortet ausschließlich die Frage nach dem Wer, nicht nach dem Wie oder dem Wie oft, und genau diese Beschränkung macht die Syntax so vorhersehbar und leicht zu überblicken.

7. Praxisbeispiel: Value Objects und Entities mit kontrolliertem State

Ein typisches Einsatzgebiet für Asymmetric Visibility sind Entities, deren Zustand nur über explizit definierte Geschäftsoperationen verändert werden darf. Eine Order-Klasse mit einer Liste von Bestellzeilen und einem Status-Feld soll von außen jederzeit lesbar sein, etwa um den aktuellen Bestellwert anzuzeigen, darf aber niemals direkt manipuliert werden, ohne dass Invarianten wie Mindestbestellwert oder Statuswechsel-Reihenfolge geprüft werden. Mit klassischen Gettern hätte man für jedes Feld eine eigene Methode gebraucht, mit Asymmetric Visibility genügt die Deklaration public private(set) array $lines = []; zusammen mit dedizierten Methoden wie addLine() und complete(), die als einzige Stellen im Code direkten Schreibzugriff besitzen.

Der Vorteil gegenüber einer rein konventionsbasierten Kapselung zeigt sich besonders bei größeren Teams: Ein neuer Entwickler, der versehentlich $order->lines[] = $line; von außerhalb der Klasse schreibt, statt die vorgesehene addLine()-Methode zu benutzen, bekommt sofort einen Fatal Error, nicht erst einen schwer nachvollziehbaren Bug in der Produktion, wenn eine Bestellung Zeilen enthält, die nie durch die Validierungslogik gelaufen sind. Asymmetric Visibility verwandelt eine Konvention, die bisher nur im Code-Review durchgesetzt werden konnte, in eine vom Parser erzwungene Garantie.

Auch für unveränderliche Value Objects wie das bereits gezeigte Money-Objekt ist das Muster zentral: Operationen wie add() erzeugen stets eine neue Instanz, statt die bestehende zu verändern, was durch die kombinierte Nutzung von Asymmetric Visibility und Constructor Promotion ohne zusätzlichen Code erzwungen wird. Zusammen mit der Order-Klasse entsteht ein Domain-Modell, in dem jede Zustandsänderung über eine benannte, fachlich sinnvolle Methode läuft, während der Lesezugriff auf alle Felder uneingeschränkt bleibt.


declare(strict_types=1);

final class Order
{
    /** @var OrderLine[] */
    public private(set) array $lines = [];

    public private(set) string $status = 'open';

    public function __construct(
        public private(set) string $orderNumber,
    ) {
    }

    public function addLine(OrderLine $line): void
    {
        if ($this->status !== 'open') {
            throw new LogicException('Cannot modify a closed order');
        }

        // Allowed: array mutation happens inside the declaring class
        $this->lines[] = $line;
    }

    public function complete(): void
    {
        if ($this->lines === []) {
            throw new LogicException('Cannot complete an order without lines');
        }

        $this->status = 'completed';
    }

    public function total(): Money
    {
        return array_reduce(
            $this->lines,
            static fn (Money $carry, OrderLine $line): Money => $carry->add($line->subtotal()),
            new Money(0, 'EUR'),
        );
    }
}

8. Statische Analyse und IDE-Unterstützung

PHPStan und Psalm haben ihre Sichtbarkeitsprüfung um Asymmetric Visibility erweitert und werten einen Schreibversuch von außerhalb des erlaubten Scopes als echten Analysefehler, nicht nur als Laufzeitfehler. Das ist besonders wertvoll, weil ein Verstoß gegen die Set-Sichtbarkeit bereits während der CI-Pipeline auffällt, lange bevor der Code überhaupt ausgeführt wird. Wer bin/analyse auf Level 5 oder höher gegen ein Modul laufen lässt, das Asymmetric Visibility konsequent einsetzt, bekommt bei jedem versehentlichen externen Schreibzugriff eine klare Fehlermeldung mit Dateiname und Zeilennummer, statt den Fehler erst im Review oder im Produktivbetrieb zu bemerken.

Auf Reflection-Ebene bringt PHP 8.4 neue Methoden mit, um Asymmetric Visibility zur Laufzeit zu inspizieren: ReflectionProperty::isPrivateSet() und ReflectionProperty::isProtectedSet() liefern die Set-Sichtbarkeit getrennt von der klassischen Get-Sichtbarkeit, die weiterhin über isPublic(), isProtected() und isPrivate() abgefragt wird. Frameworks und Serializer, die per Reflection auf Objektstrukturen zugreifen, müssen diese Unterscheidung kennen, sonst behandeln sie eine öffentlich lesbare, aber privat beschreibbare Property fälschlich wie eine vollständig private Property und übersehen sie beim Serialisieren.

Moderne IDEs wie PhpStorm erkennen die Syntax vollständig und markieren einen Schreibzugriffsversuch außerhalb des erlaubten Scopes bereits während des Tippens, nicht erst beim nächsten Analyse-Lauf. Die Autovervollständigung schlägt bei einer Property mit private(set) außerhalb der Klasse konsequent keinen Zuweisungs-Kontext mehr vor, während der Lesezugriff unverändert vorgeschlagen wird. Das reduziert die Fehlerquote zusätzlich zur reinen statischen Analyse, weil viele Verstöße gar nicht erst geschrieben werden, statt sie nachträglich zu korrigieren.

9. Migration von klassischen private-Properties mit Gettern

Eine Migration bestehender Klassen auf Asymmetric Visibility beginnt am besten mit einer Bestandsaufnahme: Welche Getter enthalten wirklich nur return $this->property;, ohne Validierung, Berechnung oder Seiteneffekt? Genau diese Kandidaten lassen sich direkt auf Asymmetric Visibility umstellen. Getter mit zusätzlicher Logik, etwa Formatierung oder einem Fallback-Wert, sind dagegen keine Kandidaten für Asymmetric Visibility, sie gehören eher in den Bereich der Property Hooks, der in diesem Beitrag bewusst nicht vertieft wird.

Der eigentliche Umbau erfolgt in kleinen, überprüfbaren Schritten: Zunächst wird die private Property durch public private(set) oder public protected(set) ersetzt, je nachdem, ob Unterklassen ebenfalls Schreibzugriff benötigen. Der bisherige Getter bleibt zunächst als deprecated markierte Delegation bestehen, die lediglich return $this->property; zurückgibt, damit bestehende Aufrufer nicht sofort angepasst werden müssen. Erst in einem zweiten Schritt werden die Aufrufstellen im Code schrittweise von $objekt->getProperty() auf den direkten Zugriff $objekt->property umgestellt, während PHPStan mit einer eigenen Regel den verbleibenden Gebrauch des veralteten Getters aufspürt.

Abwärtskompatibilität verdient besondere Aufmerksamkeit bei Bibliotheken und öffentlichen APIs: Der Wechsel von private zu public private(set) ist aus Sicht von externem Code, der die Property vorher gar nicht direkt ansprechen konnte, unkritisch, weil neuer direkter Lesezugriff eine reine Erweiterung der Fähigkeiten darstellt, keine Einschränkung. Kritischer wird es, wenn eine Property vorher vollständig public war und nun auf public private(set) reduziert wird, denn dann bricht jeder externe Code, der bisher direkt geschrieben hat, was in einem Major-Release klar kommuniziert und mit einer Übergangsfrist versehen werden sollte. Werkzeuge wie Rector bieten bereits automatisierte Regeln an, die geeignete Getter-Muster erkennen und in Asymmetric Visibility umwandeln, was den manuellen Aufwand bei größeren Codebasen deutlich reduziert.

10. Zusammenfassung

Asymmetric Visibility löst ein Problem, das PHP-Entwickler jahrzehntelang mit privaten Properties und öffentlichen Gettern umgehen mussten: eine Property soll öffentlich lesbar sein, aber nur eingeschränkt beschreibbar. Mit public private(set) und public protected(set) wird diese Anforderung direkt in der Property-Deklaration ausgedrückt, ohne eine einzige Zeile Getter-Code, ohne Laufzeit-Overhead und mit vollständiger Unterstützung durch statische Analyse und moderne IDEs. Die Kombination mit readonly ermöglicht zusätzlich, nicht nur wer schreiben darf, sondern auch wie oft geschrieben werden darf, präzise zu steuern.

In Constructor Property Promotion, bei Vererbungshierarchien und in Value Objects zeigt Asymmetric Visibility ihren vollen Nutzen: kompaktere Klassen, klarere Verträge zwischen Basisklasse und Unterklasse und ein Domain-Modell, dessen Zustandsänderungen ausschließlich über benannte, fachlich sinnvolle Methoden erfolgen. Wer bestehenden Code migriert, sollte zunächst reine, logikfreie Getter identifizieren, diese schrittweise durch Asymmetric Visibility ersetzen und dabei Abwärtskompatibilität für öffentliche APIs im Blick behalten.

Asymmetric Visibility in PHP 8.4 - Das Wichtigste auf einen Blick

Syntax

public private(set) und public protected(set) trennen Lese- von Schreibzugriff. Die Set-Sichtbarkeit muss immer mindestens so restriktiv sein wie die Get-Sichtbarkeit.

Reine Sichtbarkeitsregel

Keine eigene Logik wie bei Property Hooks. Kein absolutes Schreibverbot wie bei readonly, sondern kontrollierter Schreibzugriff aus dem erlaubten Scope.

Vererbung

Get-Sichtbarkeit darf in Kindklassen aufgeweitet werden, Set-Sichtbarkeit darf nur beibehalten oder weiter verengt werden.

Tooling

PHPStan, Psalm und moderne IDEs erkennen Verstöße bereits vor der Ausführung. Neue Reflection-Methoden wie isPrivateSet() machen die Set-Sichtbarkeit zur Laufzeit prüfbar.

11. FAQ: Asymmetric Visibility in PHP 8.4

1Was ist Asymmetric Visibility in PHP 8.4?
Sie erlaubt getrennte Sichtbarkeiten für Lese- und Schreibzugriff auf eine typisierte Property, etwa öffentlich lesbar, aber nur privat beschreibbar, und ersetzt das klassische Muster aus privater Property und öffentlichem Getter ohne eigene Logik.
2public private(set) vs. public protected(set)?
Bei private(set) darf nur die deklarierende Klasse schreiben, bei protected(set) zusätzlich Unterklassen. Beide erlauben Lesezugriff von überall.
3private get und public set kombinierbar?
Nein. Die Set-Sichtbarkeit muss immer mindestens so restriktiv sein wie die Get-Sichtbarkeit, der umgekehrte Fall wird vom Parser abgelehnt.
4Was passiert bei externem Schreibzugriff auf private(set)?
PHP wirft einen Fatal Error zur Laufzeit. PHPStan und Psalm erkennen solche Verstöße bereits vor der Ausführung in der CI-Pipeline.
5Kombination mit readonly?
Direkt kombinierbar, etwa public private(set) readonly string $id. readonly begrenzt die Anzahl der Schreibzugriffe auf einen, Asymmetric Visibility begrenzt zusätzlich den erlaubten Scope.
6Funktioniert das mit Constructor Property Promotion?
Ja. Die vollständige Sichtbarkeit kann direkt im Konstruktor-Parameter stehen, etwa public private(set) string $sku, PHP erzeugt daraus automatisch die Property.
7Darf eine Kindklasse die Set-Sichtbarkeit aufweiten?
Nein. Die Set-Sichtbarkeit darf nur beibehalten oder weiter verengt werden, die Get-Sichtbarkeit darf wie gewohnt aufgeweitet werden.
8Unterschied zu Property Hooks?
Asymmetric Visibility ist eine reine Sichtbarkeitsregel ohne eigene Logik. Property Hooks fügen ausführbaren Code beim Lesen oder Schreiben hinzu, etwa für berechnete Werte oder Validierung.
9Unterstützung durch PHPStan und Psalm?
Ja, beide werten einen unerlaubten Schreibzugriff als echten Analysefehler. Neue Reflection-Methoden wie isPrivateSet() und isProtectedSet() ergänzen die Laufzeit-Introspektion.
10Wie migriere ich bestehende Getter?
Logikfreie Getter identifizieren, private Property durch public private(set) ersetzen, alten Getter übergangsweise als deprecated Delegation belassen und Aufrufstellen schrittweise umstellen.

Mironsoft

PHP 8.4 Modernisierung und Code-Reviews für Magento- und PHP-Projekte

Bereit für Asymmetric Visibility in eurem PHP-Code?

Wir analysieren bestehende Klassen, identifizieren Getter-Boilerplate und migrieren eure Value Objects und Entities gezielt auf Asymmetric Visibility, readonly und Constructor Property Promotion, mit vollständiger PHPStan-Absicherung.

Code-Review

Bestandsaufnahme klassischer Getter-Muster und PHPStan-Analyse auf Sichtbarkeitsverstöße

Migration

Schrittweiser Umbau auf Asymmetric Visibility mit Abwärtskompatibilität für öffentliche APIs

PHP 8.4 Upgrade

Vollständige Modernisierung eurer Codebasis inklusive Constructor Property Promotion und Value Objects