Theme-Editor

PHP8 Feautures

Igäl

Mitglied
Registriert
17.10.26
Beiträge
13
Reaktionspunkte
3
Punkte
3
1/3
Themensteller
Liebe Gemeinde

Wie gesagt fliesst meine Freizeit wieder stark in mein Hobbyprojekt. Es ist eine abgespeckte Rule Engine für Dungeons & Dragons 2024. Abgespeckt deswegen, weil ich mit meiner Gruppe weiterhin am Tisch würfle und weiterhin am Tisch Schaden berechne und manuell abziehe. Also eher ein Charakterbogen+ und ein Management Tool für Zustände, Inventar, Gear etc.

Mein altes Framework basierte noch auf PHP 4 und 5 und wurde punktuell erweitert. Grossmehrheitlich ist es aber Spaghetticode im OOP-Pelz. Entsprechend ist das Refactoring mit allem was PHP 8+ zu bieten hat ein riesen Schritt und eigentlich eine neue Programmiersprache, die ich mir aneignen muss.

Aus diesem Grund bin ich interessiert an Austausch. Und auch, weil wir ja jetzt diese Forum wieder etwas mit Inhalt füllen wollen, richtig? Entsprechend möchte ich wissen, wie eure Erfahrungen mit gewissen Feautures sind und ob ihr sie in Gebrauch habt. Falls ja, warum, falls nein, warum nicht?

Unlängst bin ich auf zwei Themen gestossen, die meine Architektur vermutlich prägen werden, sofern sich deren Einsatz bewährt. Die Rede ist von asymmetric visibility (für Entities) und Wither-Methoden (für DTOs) .

Architektonisch habe ich mich dazu entschieden, dass meine Repositories jeweils Entitys oder Config-Container befüllen. In einem Domain Driven Design im Backend und einem Data Driven Design in der Datenbank kommen da ziemlich viele solcher Klassen zusammen. Entsprechend schreibt man sich mit getter- und setter-Methoden die Finger wund. Genau für diesen Fall habe ich die asymmetric visibility entdeckt.
PHP:
class someEntity {
    public function __construct(
        public private(set) int $someId,
        public private(set) string $someTitle,
        public private(set) array $someAttributes
    ) {}
}
Das eliminiert mir auf einen Schlag drei Getter-Methoden und ich muss nur noch die Setter schreiben.

In diesem Zusammenhang nimmt mich wunder, ob ihr das ähnlich macht oder ob ihr eure Variablen mit public mutable belässt oder gar auf private setzt und getter-Methoden einfügt. Auch auf das Warum bin ich neugierig, weil ich in PHP8 noch wenig Erfahrung habe und eventuell auch gewisse Fallstricke übersehe.

Das andere sind meine DTOs, an welche ich den Anspruch habe, dass sie die Datenintegrität 100%ig gewährleisten. Diese werden, wo vorhanden, aus den Config-Containern generiert. Das ist kein Problem, wenn ein DTO mit den finalen Daten instanziert werden kann, wird aber zum Problem, wenn es nachträgliche Änderungen im DTO gibt.
PHP:
class someDTO    {
    public function __construct(
        public readonly int $someId,
        public readonly string $someTitle,
        public readonly array $someAttributes
    ) {}
}
Man denke beispielsweise an eine Edit-Funktion für News, wo der Controller das Formular für die Anzeige rendert, aber noch keine Daten hat, um die value-Attribute der Form-Felder zu füttern. Meine Herangehensweise ist, dass ich das DTO für das Frontend einfach ersetze, indem ich eine Wither-Methode im DTO aufrufe, die quasi bewirkt, dass sich das DTO als Kopie neu aufsetzt, jedoch einzelne Parameter mit geänderten Werten instanziert.

Das erhält mir die Datenintegrität, jedoch finde ich es etwas unschön, dass ich in derselben Methode (bspw. einer Funktion in einem Controller) ein DTO erstelle, Daten modifiziere und dann gleich nochmals ein DTO erstelle, das die Daten des zuvor erstellten DTOs überschreibt. Aber ich sehe keine Alternative, wenn ich fully protected DTOs bereitstellen möchte.

Was sind eure Erfahrungen, respektive Lösungsansätze mit diesem Pattern?

Kennt ihr irgendwelche Gamechanger aus PHP8 (oder auch 6 und 7 🙂)? Aktuell bin ich dankbar für alles, was mir meinen alten Spaghetticode sinnvoll entflechten hilft.

So long,

dä Igäl.
 
Bin eher Webseiten-Bastler als Backend-Architekt, aber für reine DTOs würde ich readonly-Klassen nehmen, dann brauchst du da gar kein private(set). Und mit clone() samt Property-Array in PHP 8.5 werden Wither ziemlich kurz. private(set) finde ich vor allem bei den Entities spannend, wo sich intern doch noch was ändern soll.
 
Themensteller
Genau so mache ich das. DTOs sind mit readonly protected, Entity-Variablen und Variablen aus Configs (bearbeitbare Vorgänger meiner DTOs, die danach als DTO komplett fixiert werden) werden mit public private(set) instanziert.

Clone() schaue ich mir gerne an. Aktuell arbeiten meine Wither noch mit Elementkopie und einem Array als Übergabeparameter für den gezielten Austausch von Werten.

PHP:
class someDTO    {
    public function __construct(
        public readonly int $someId,
        public readonly string $someTitle,
        public readonly array $someAttributes
    ) {}
   
    public function with(array $new): self {
        return new self(
            someId: $new['newId'] ?? $this->someId,
            someTitle: $new['newTitle'] ?? $this->someTitle,
            someAttributes: $new['newAttributes'] ?? $this->someAttributes
        );
    }
}
 
Zuletzt bearbeitet:
Kleiner Fehler im Beispiel: der Parameter heisst $news, benutzt wird aber $new. Da das ?? den Fehler schluckt, bleiben dann einfach still die alten Werte drin, das sucht man ewig.

Mit dem Array als Übergabe hätte ich generell etwas Bauchweh, weil man sich bei den Keys leicht vertippt und das nirgends auffällt. Ich hab mir für die paar Felder, die sich wirklich ändern, lieber einzelne Methoden gebaut, also withTitle(string $title) usw. Ist mehr Schreibarbeit, dafür prüft PHP den Typ und die IDE kennt die Namen.

Mit 8.5 wird das in der Methode dann ein Einzeiler:

return clone($this, ['someTitle' => $title]);

Das klappt auch mit readonly, solange es innerhalb der Klasse passiert. Läuft dein Projekt schon auf 8.5 oder noch auf 8.4?
 
Themensteller
Danke für den Hinweis. War lediglich freihändiger Code für die Veranschaulichung im Thread. Habe das aber noch angepasst.

Ja, habe unlängst umgesattelt, weil ich unbedingt mit der asymmetric visibility und property hooks arbeiten wollte und dann halt die aktuellste Stable genommen. Wie gesagt werde ich clone() sicherlich noch genauer anschauen. Aber hatte bislang noch keine Zeit, weil Hobbyprogrammierer und... naja... das Leben... du weisst schon 🙂

Wither pro Feld fand ich einfach wieder zu viel Boilerplate. Ich denke, dass Fehler in der Schreibweise auffallen, wenn die Funktion gut getestet wird. Da ich noch recht am Anfang des Projektes stehe, hatte ich als Anwendungsbereich bis jetzt eigentlich nur das Befüllen von bestehenden FormElement-DTOs mit Einträgen ab DB (da fällt es auf, wenn die Einträge nicht da sind), das ergänzen von Error-Messages pro Feld bei Regelverletzung (fällt im Testing auch auf) und das hinzufügen von Entities in einem List-DTO (fällt auch auf, wenn keine Listeneinträge erscheinen).

Deshalb scheint wohl clone() schon das Mittel der Wahl zu sein für die Zukunft.
 
Genau so mache ich das. DTOs sind mit readonly protected, Entity-Variablen und Variablen aus Configs (bearbeitbare Vorgänger meiner DTOs, die danach als DTO komplett fixiert werden) werden mit public private(set) instanziert.

Clone() schaue ich mir gerne an. Aktuell arbeiten meine Wither noch mit Elementkopie und einem Array als Übergabeparameter für den gezielten Austausch von Werten.
Dein Beispiel lässt sich auch ohne das clone von 8.5 schöner schreiben. 😀 kai_web hats eh schon geschrieben, das array hat viele Probleme: vertippser, typsicherheit..

`readonly` gleich auf die Klasse, wenn alles readonly ist. Gleich auch ein final dazu. Wegnehmen ist leicht, falls du wirklich mal musst, aber bei welchem DTO brauchst du Vererbung?
Das attributes-Array mittels phpdoc näher bezeichnen hilft auch, v.a. wenn man dann die IDE den Code prüfen lässt bzw PHPStan nutzt.
Die with-Methode kann auch einfach benannte Parameter nutzen, dann hast du die Typen festgelegt, und der Aufruf mittels Array klappt ja auch noch.

Sobald ein Parameter allerdings null sein dürfte, klappt das nicht mehr gut, weil du ihn mit dieser with-Methode nicht überschreiben kannst. Bei deiner Array-Variante könntest du auf Existenz prüfen 😀.

PHP:
final readonly class SomeDTO
{
    /**
     * @param array<string, string|int|bool> $attributes
     */
    public function __construct(
        public int $id,
        public string $title,
        public array $attributes
    )
    {
    }

    /**
     * @param array<string, string|int|bool> $attributes
     */
    public function with(?int $id = null, ?string $title = null, ?array $attributes = null): self
    {
        return new self(
            id: $id ?? $this->id,
            title: $title ?? $this->title,
            attributes: $attributes ?? $this->attributes
        );
    }
}

$some = new SomeDTO(1, 'Title', ['weasel' => true]);

$other = $some->with(attributes: ['wiesel' => false ]);

// oder wenn die Daten in einem Array sind, aus Gründen:
$data = ['title' => 'Blubb', 'attributes' => [ 'weasel' => false, 'critter' => true ]];
$yetAnother = $some->with(...$data);
 
Zurück
Oben