diff --git a/language/oop5/property-hooks.xml b/language/oop5/property-hooks.xml new file mode 100644 index 000000000..9406a3685 --- /dev/null +++ b/language/oop5/property-hooks.xml @@ -0,0 +1,662 @@ + + + + + Property Hooks + + + Property Hooks, in einigen anderen Sprachen auch als + "Property-Accessors" bekannt, sind eine Möglichkeit, das Lese- und + Schreibverhalten einer Eigenschaft abzufangen und zu überschreiben. + Diese Funktionalität erfüllt zwei Zwecke: + + + + + Sie ermöglicht es, Eigenschaften direkt zu verwenden, ohne get- und + set-Methoden, wobei die Möglichkeit offen bleibt, in Zukunft zusätzliches + Verhalten hinzuzufügen. Dadurch werden die meisten standardmäßigen + get/set-Methoden überflüssig, selbst ohne Hooks zu verwenden. + + + + + Sie ermöglicht Eigenschaften, die ein Objekt beschreiben, ohne dass ein + Wert direkt gespeichert werden muss. + + + + + Für nicht-statische Eigenschaften stehen zwei Hooks zur Verfügung: + get und set. + Sie ermöglichen es, das Lese- bzw. Schreibverhalten einer Eigenschaft zu + überschreiben. Hooks sind sowohl für typisierte als auch für untypisierte + Eigenschaften verfügbar. + + + Eine Eigenschaft kann "hinterlegt" (backed) oder "virtuell" + sein. Eine hinterlegte Eigenschaft ist eine, die tatsächlich einen Wert + speichert. Jede Eigenschaft, die keine Hooks besitzt, ist hinterlegt. + Eine virtuelle Eigenschaft ist eine, die Hooks besitzt, wobei diese Hooks + nicht mit der Eigenschaft selbst interagieren. + In diesem Fall verhalten sich die Hooks im Wesentlichen wie Methoden, und das + Objekt belegt keinen Speicherplatz, um einen Wert für diese Eigenschaft zu + speichern. + + + Property Hooks sind mit readonly-Eigenschaften + inkompatibel. Wenn es notwendig ist, den Zugriff auf eine + get- oder set-Operation zusätzlich zur + Änderung ihres Verhaltens einzuschränken, ist die + asymmetrische Sichtbarkeit von Eigenschaften + zu verwenden. + + + + Versionsinformationen + + Property Hooks wurden in PHP 8.4 eingeführt. + + + + + Grundlegende Hook-Syntax + + Die allgemeine Syntax zur Deklaration eines Hooks lautet wie folgt. + + + Property Hooks (vollständige Version) + +modified) { + return $this->foo . ' (modified)'; + } + return $this->foo; + } + set(string $value) { + $this->foo = strtolower($value); + $this->modified = true; + } + } +} + +$example = new Example(); +$example->foo = 'changed'; +print $example->foo; +?> +]]> + + + + Die Eigenschaft $foo endet mit {} + anstatt mit einem Semikolon. Dies zeigt das Vorhandensein von Hooks an. + Es werden sowohl ein get- als auch ein + set-Hook definiert, obwohl es zulässig ist, nur den einen + oder den anderen zu definieren. Beide Hooks besitzen einen durch + {} gekennzeichneten Rumpf, der beliebigen Code enthalten + darf. + + + Der set-Hook erlaubt es zusätzlich, den Typ und den Namen + eines eingehenden Werts anzugeben, wobei dieselbe Syntax wie bei einer + Methode verwendet wird. Der Typ muss entweder mit dem Typ der Eigenschaft + übereinstimmen oder zu diesem + kontravariant + (weiter) sein. Beispielsweise könnte eine Eigenschaft vom Typ + string einen set-Hook besitzen, der + stringStringable + akzeptiert, aber keinen, der nur array akzeptiert. + + + Mindestens einer der Hooks verweist auf $this->foo, die + Eigenschaft selbst. Das bedeutet, dass die Eigenschaft + "hinterlegt" sein wird. Beim Aufruf von + $example->foo = 'changed' wird die übergebene Zeichenkette + zunächst in Kleinbuchstaben umgewandelt und dann im hinterlegten Wert + gespeichert. Beim Lesen aus der Eigenschaft kann an den zuvor gespeicherten + Wert bedingt zusätzlicher Text angehängt werden. + + + Es gibt außerdem eine Reihe von Kurzschreibweisen, um häufige Fälle zu + behandeln. + + + Wenn der get-Hook aus einem einzigen Ausdruck besteht, + können die {} weggelassen und durch einen Pfeilausdruck + ersetzt werden. + + + Property-get-Ausdruck + + Dieses Beispiel ist äquivalent zum vorherigen. + + + $this->foo . ($this->modified ? ' (modified)' : ''); + + set(string $value) { + $this->foo = strtolower($value); + $this->modified = true; + } + } +} +?> +]]> + + + + Wenn der Parametertyp des set-Hooks mit dem Typ der + Eigenschaft übereinstimmt (was typisch ist), kann er weggelassen werden. + In diesem Fall erhält der zu setzende Wert automatisch den Namen + $value. + + + Property-set-Standardwerte + + Dieses Beispiel ist äquivalent zum vorherigen. + + + $this->foo . ($this->modified ? ' (modified)' : ''); + + set { + $this->foo = strtolower($value); + $this->modified = true; + } + } +} +?> +]]> + + + + Wenn der set-Hook nur eine veränderte Version des + übergebenen Werts setzt, kann er ebenfalls zu einem Pfeilausdruck + vereinfacht werden. Der Wert, zu dem der Ausdruck ausgewertet wird, wird im + hinterlegten Wert gesetzt. + + + Property-set-Ausdruck + + $this->foo . ($this->modified ? ' (modified)' : ''); + set => strtolower($value); + } +} +?> +]]> + + + + Dieses Beispiel ist nicht ganz äquivalent zum vorherigen, da es nicht + zusätzlich $this->modified verändert. Wenn im Rumpf des + set-Hooks mehrere Anweisungen benötigt werden, ist die Variante mit + geschweiften Klammern zu verwenden. + + + Eine Eigenschaft kann je nach Situation null, einen oder beide Hooks + implementieren. Alle Kurzschreibweisen sind voneinander unabhängig. Das + heißt, die Verwendung eines Kurz-get mit einem langen set oder eines + Kurz-set mit einem expliziten Typ usw. ist alles gültig. + + + Bei einer hinterlegten Eigenschaft bedeutet das Weglassen eines + get- oder set-Hooks, dass das + standardmäßige Lese- bzw. Schreibverhalten verwendet wird. + + + + Hooks können bei der Verwendung der + Konstruktor-Promotion von Eigenschaften + definiert werden. In diesem Fall müssen die dem Konstruktor übergebenen + Werte jedoch mit dem der Eigenschaft zugeordneten Typ übereinstimmen, + unabhängig davon, was der set-Hook erlauben könnte. + + + Man betrachte das Folgende: + + +created = $value; + } + }, + ) { + } +} +]]> + + + Intern zerlegt die Engine dies in das Folgende: + + +created = $value; + } + } + + public function __construct( + DateTimeInterface $created, + ) { + $this->created = $created; + } +} +]]> + + + Jeder Versuch, die Eigenschaft außerhalb des Konstruktors zu setzen, lässt + entweder string- oder + DateTimeInterface-Werte zu, der Konstruktor + erlaubt jedoch nur DateTimeInterface. + Das liegt daran, dass der für die Eigenschaft definierte Typ + (DateTimeInterface) als Parametertyp + innerhalb der Konstruktorsignatur verwendet wird, unabhängig davon, was der + set-Hook erlaubt. + + + Wenn diese Art von Verhalten vom Konstruktor benötigt wird, kann die + Konstruktor-Promotion von Eigenschaften nicht verwendet werden. + + + + + Virtuelle Eigenschaften + + Virtuelle Eigenschaften sind Eigenschaften, die keinen hinterlegten Wert + besitzen. Eine Eigenschaft ist virtuell, wenn weder ihr + get- noch ihr set-Hook unter + Verwendung der exakten Syntax auf die Eigenschaft selbst verweist. Das + heißt, eine Eigenschaft mit dem Namen $foo, deren Hook + $this->foo enthält, wird hinterlegt sein. Das Folgende ist + jedoch keine hinterlegte Eigenschaft und wird einen Fehler verursachen: + + + Ungültige virtuelle Eigenschaft + +$temp; // Verweist nicht auf $this->foo, zählt also nicht. + } + } +} +?> +]]> + + + + Wird bei virtuellen Eigenschaften ein Hook weggelassen, so existiert die + betreffende Operation nicht, und der Versuch, sie zu verwenden, erzeugt + einen Fehler. Virtuelle Eigenschaften belegen keinen Speicherplatz in einem + Objekt. Virtuelle Eigenschaften eignen sich für "abgeleitete" + Eigenschaften, etwa solche, die die Kombination zweier anderer Eigenschaften + sind. + + + Virtuelle Eigenschaft + + $this->h * $this->w; + } + + public function __construct(public int $h, public int $w) {} +} + +$s = new Rectangle(4, 5); +print $s->area; // gibt 20 aus +$s->area = 30; // Fehler, da keine set-Operation definiert ist. +?> +]]> + + + + Es ist ebenfalls zulässig, sowohl einen get- als auch + einen set-Hook für eine virtuelle Eigenschaft zu + definieren. + + + + Geltungsbereich + + Alle Hooks operieren im Geltungsbereich des Objekts, das verändert wird. + Das bedeutet, dass sie Zugriff auf alle public-, private- oder + protected-Methoden des Objekts haben, ebenso wie auf alle public-, private- + oder protected-Eigenschaften, einschließlich Eigenschaften, die ihre eigenen + Property Hooks besitzen können. Der Zugriff auf eine andere Eigenschaft + innerhalb eines Hooks umgeht nicht die für diese Eigenschaft definierten + Hooks. + + + Die bemerkenswerteste Auswirkung davon ist, dass nicht-triviale Hooks bei + Bedarf eine beliebig komplexe Methode aufrufen können. + + + Aufruf einer Methode aus einem Hook + + $this->sanitizePhone($value); + } + + private function sanitizePhone(string $value): string { + $value = ltrim($value, '+'); + $value = ltrim($value, '1'); + + if (!preg_match('/\d\d\d\-\d\d\d\-\d\d\d\d/', $value)) { + throw new \InvalidArgumentException(); + } + return $value; + } +} +?> +]]> + + + + + Referenzen + + Da das Vorhandensein von Hooks den Lese- und Schreibprozess für + Eigenschaften abfängt, verursachen sie Probleme beim Erlangen einer Referenz + auf eine Eigenschaft oder bei indirekter Veränderung, etwa + $this->arrayProp['key'] = 'value';. Das liegt daran, dass jeder + Versuch, den Wert per Referenz zu verändern, einen set-Hook umgehen würde, + sofern einer definiert ist. + + + In dem seltenen Fall, dass das Erlangen einer Referenz auf eine Eigenschaft, + für die Hooks definiert sind, notwendig ist, kann dem + get-Hook & vorangestellt werden, + damit er per Referenz zurückgibt. Das Definieren von sowohl + get als auch &get für dieselbe + Eigenschaft ist ein Syntaxfehler. + + + Das Definieren von sowohl &get- als auch + set-Hooks für eine hinterlegte Eigenschaft ist nicht + zulässig. Wie oben angemerkt, würde das Schreiben in den per Referenz + zurückgegebenen Wert den set-Hook umgehen. Bei virtuellen + Eigenschaften gibt es keinen notwendigerweise zwischen den beiden Hooks + geteilten gemeinsamen Wert, daher ist das Definieren beider zulässig. + + + Das Schreiben in einen Index einer Array-Eigenschaft beinhaltet ebenfalls + eine implizite Referenz. Aus diesem Grund ist das Schreiben in eine + hinterlegte Array-Eigenschaft mit definierten Hooks genau dann zulässig, + wenn sie nur einen &get-Hook definiert. Bei einer + virtuellen Eigenschaft ist das Schreiben in das von get + oder &get zurückgegebene Array zulässig, ob dies + jedoch eine Auswirkung auf das Objekt hat, hängt von der Implementierung des + Hooks ab. + + + Das Überschreiben der gesamten Array-Eigenschaft ist in Ordnung und verhält + sich wie bei jeder anderen Eigenschaft. Nur das Arbeiten mit Elementen des + Arrays erfordert besondere Sorgfalt. + + + + Vererbung + + Finale Hooks + + Hooks können auch als final + deklariert werden, wodurch sie nicht überschrieben werden können. + + + Finale Hooks + + strtolower($value); + } +} + +class Manager extends User +{ + public string $username { + // Dies ist erlaubt + get => strtoupper($this->username); + + // Aber dies ist NICHT erlaubt, weil set in der Elternklasse final ist. + set => strtoupper($value); + } +} +?> +]]> + + + + Eine Eigenschaft kann ebenfalls als + final deklariert werden. Eine + finale Eigenschaft darf von einer Kindklasse in keiner Weise neu deklariert + werden, was das Ändern von Hooks oder das Erweitern ihres Zugriffs + ausschließt. + + + Hooks bei einer Eigenschaft als final zu deklarieren, die als final + deklariert ist, ist redundant und wird stillschweigend ignoriert. Dies ist + dasselbe Verhalten wie bei finalen Methoden. + + + Eine Kindklasse kann einzelne Hooks einer Eigenschaft definieren oder neu + definieren, indem sie die Eigenschaft und nur die Hooks, die sie + überschreiben möchte, neu definiert. Eine Kindklasse kann auch Hooks zu + einer Eigenschaft hinzufügen, die keine besaß. Dies ist im Wesentlichen + dasselbe, als ob die Hooks Methoden wären. + + + Vererbung von Hooks + +x = $value; + } + } +} +?> +]]> + + + + Jeder Hook überschreibt die Implementierungen der Elternklasse unabhängig + voneinander. Wenn eine Kindklasse Hooks hinzufügt, wird jeder für die + Eigenschaft gesetzte Standardwert entfernt und muss neu deklariert werden. + Dies ist konsistent damit, wie Vererbung bei Eigenschaften ohne Hooks + funktioniert. + + + + Zugriff auf Hooks der Elternklasse + + Ein Hook in einer Kindklasse kann unter Verwendung des Schlüsselworts + parent::$prop, gefolgt vom gewünschten Hook, auf die + Eigenschaft der Elternklasse zugreifen. Zum Beispiel + parent::$propName::get(). Dies kann gelesen werden als + "greife auf die in der Elternklasse definierte Eigenschaft + prop zu und führe dann ihre get-Operation aus" + (oder set-Operation, je nachdem). + + + Wenn der Zugriff nicht auf diese Weise erfolgt, wird der Hook der + Elternklasse ignoriert. Dieses Verhalten ist konsistent damit, wie alle + Methoden funktionieren. Es bietet außerdem eine Möglichkeit, auf den + Speicher der Elternklasse zuzugreifen, falls vorhanden. Wenn es keinen Hook + für die Eigenschaft der Elternklasse gibt, wird ihr standardmäßiges + get/set-Verhalten verwendet. Hooks dürfen auf keinen anderen Hook als ihren + eigenen Eltern-Hook ihrer eigenen Eigenschaft zugreifen. + + + Das obige Beispiel könnte wie folgt umgeschrieben werden, was es der Klasse + Point ermöglichen würde, in Zukunft problemlos ihren + eigenen set-Hook hinzuzufügen (im vorherigen Beispiel + würde ein zur Elternklasse hinzugefügter Hook in der Kindklasse ignoriert). + + + Zugriff auf Eltern-Hook (set) + + +]]> + + + + Ein Beispiel für das Überschreiben nur eines get-Hooks könnte sein: + + + Zugriff auf Eltern-Hook (get) + + $this->uppercase + ? strtoupper(parent::$val::get()) + : strtolower(parent::$val::get()); + } +} +?> +]]> + + + + + + Serialisierung + + PHP verfügt über eine Reihe unterschiedlicher Möglichkeiten, wie ein Objekt + serialisiert werden kann, entweder für die öffentliche Verwendung oder zu + Debugging-Zwecken. Das Verhalten von Hooks variiert je nach Anwendungsfall. + In einigen Fällen wird der rohe hinterlegte Wert einer Eigenschaft + verwendet, wobei jegliche Hooks umgangen werden. In anderen wird die + Eigenschaft "durch" den Hook gelesen oder geschrieben, genau wie + bei jeder anderen normalen Lese-/Schreibaktion. + + + var_dump: Verwendet den rohen Wert + serialize: Verwendet den rohen Wert + unserialize: Verwendet den rohen Wert + __serialize()/__unserialize(): Eigene Logik, verwendet den get/set-Hook + Array-Casting: Verwendet den rohen Wert + var_export: Verwendet den get-Hook + json_encode: Verwendet den get-Hook + JsonSerializable: Eigene Logik, verwendet den get-Hook + get_object_vars: Verwendet den get-Hook + get_mangled_object_vars: Verwendet den rohen Wert + + + +