andy4412: Klasse aus Datenbank füllen, danach die Klasse verarbeiten

Hallo,

ich hatte es für eine gute Idee gehalten, beim Erzeugen einer Klasseninstanz diese per __construct automatisch mit einem Array zu füllen, dessen Daten ich aus einer Datenbank hole.

class Proofs
{
    public $proofs;
    public function __construct(){
    try {
            $dbh = new Db();
            $connection = $dbh->dbConnect();
            $sqlStatement = "SELECT id, title, price FROM proofs WHERE id > :id";
            $stmt = $connection->prepare( $sqlStatement );
            $stmt->execute([':id' => 100]);
            $this->proofs = $stmt->fetchAll(PDO::FETCH_ASSOC);
        }
        catch (Throwable $e) {
            echo $e->getMessage();
        }
    }
}

Dies hier gibt mir auf dem Monitor auch die Inhalte der Datenbankoperation aus ...

$proofs = new Proofs();
var_dump($proofs);

... aber ich habe das Problem, wie ich denn jetzt an die einzelnen Felder des Arrays, welches in der Klasse gespeichert ist, heranzukommen.

Und ist es denn eigentlich generell sinnvoll, die Klasse auf diesem Weg zu erzeugen?

Danke Andy

  1. Tach!

    ... aber ich habe das Problem, wie ich denn jetzt an die einzelnen Felder des Arrays, welches in der Klasse gespeichert ist, heranzukommen.

    Nun, indem du auf dieses Array zugreifst. Du hast die Variable $proofs, die auf das Objekt verweist, Das Objekt hat eine Eigenschaft proofs, in der du das Ergebnis des Abfrage abgelegt hast. Das Grundlagenwissen zur OOP sagt, dass man mit $proofs->proofs auf diese Eigenschaft, also das Array darin, zugreifen kann. Das erste Element wäre $proofs->proofs[0]. Das ist prinzipiell nicht anders als bei Nicht-OOP-Variablen.

    Und ist es denn eigentlich generell sinnvoll, die Klasse auf diesem Weg zu erzeugen?

    Das kann man ohne Kenntnis des Anwendungsfalles nicht beurteilen.

    dedlfix.

  2. Und ist es denn eigentlich generell sinnvoll, die Klasse auf diesem Weg zu erzeugen?

    Die Klasse nicht aber auf jeden Fall die Instanzen! Instanzen sind ja Objekte die Eigenschaften haben und diese können auch in DBs gespeichert sein. Idealerweise schaltest Du da jedoch einen austauschbaren Layer ein. D.h. daß dem Konstruktor mitgeteilt wird, aus welcher Datenquelle die Eigenschaften kommen (DB, Datei, Netwerk ..). MFG

    PS: Den Zugriff auf Eigenschaften regeln Methoden.

  3. Ah, dieser Schritt $proofs->proofs fehlte mir!

    Vielen Dank euch beiden!

    1. Ah, dieser Schritt $proofs->proofs fehlte mir!

      Ich empfehle Dir, anstelle von public properties einen Getter und einen Setter für den Zugriff auf solche einzusetzen. Damit bist Du auf etwaige Veränderungen besser vorbereitet und wenn sich an der Datenstruktur der Instanz mal was ändern sollte muss man nicht haufenweise CODE ändern sondern nur die entsprechende Methode. MFG

      1. Tach!

        Ah, dieser Schritt $proofs->proofs fehlte mir!

        Ich empfehle Dir, anstelle von public properties einen Getter und einen Setter für den Zugriff auf solche einzusetzen. Damit bist Du auf etwaige Veränderungen besser vorbereitet und wenn sich an der Datenstruktur der Instanz mal was ändern sollte muss man nicht haufenweise CODE ändern sondern nur die entsprechende Methode.

        Ich empfehle, darauf zu verzichten, weil das in den meisten Fällen nur unnötige Funktionsaufrufe sind und nur selten ein Bedarf entsteht, der eine Kapslung benötigt. Zudem hat PHP mit den Magic Methods __get() und __set() die Möglichkeit, das auch noch nachträglich nachzurüsten, ohne die Aufrufstellen zu ändern zu müssen.

        dedlfix.

        1. Magic Methods sind eine feine Sache in Perl funktioniert das ganz ähnlich. Es bedarf jedoch schon ein bischen Erfahrung für einen zweckmäßigen Einsatz. So wäre es z.B. möglich den Datenzugriff auszulagern, also vom Konstruktor weg in eine ausgelagerte Methode. So hat man recht einfach eine API zum Data Access Layer und alles zusammen austauschbar in einer Zeile.

          Man muss sich einfach mal damit befassen.

          1. Tach!

            Magic Methods sind eine feine Sache in Perl funktioniert das ganz ähnlich. Es bedarf jedoch schon ein bischen Erfahrung für einen zweckmäßigen Einsatz. So wäre es z.B. möglich den Datenzugriff auszulagern, also vom Konstruktor weg in eine ausgelagerte Methode.

            Ja, aber anders als du dir das vermutlich gerade vorstellst. Kein Zusammenfummeln von Teilen zu einem Ganzen während der Laufzeit, sondern das bereits erwähnte Repository als eigenständige Einheit neben den Daten-Objekten.

            Man muss sich einfach mal damit befassen.

            Das wäre durchaus sinnvoll, bevor man das Bewerten beginnt.

            dedlfix.

            1. Ich würde auch gerne mehr fundiertes Fachwissen hier vermitteln was auch mit einer gehörigen Portion Erfahrung verbunden ist. GGA

  4. Hallo andy4412,

    nein, es ist keine gute Idee, wenn ein Konstruktor das Objekt aus der Datenbank liest. Du kannst in einem Konstruktor nicht jede Semantik verpacken, die Du brauchst. Und es widerspricht der Aufgabentrennung. Die Abbildung des fachlichen Objektmodells auf das Datenbankschema gehört nicht ins Objektmodell, das ist eine separate Aufgabe und gehört in separate Klassen.

    Es ist häufig so, dass man Fachlogik aus den Modellklassen ganz heraushält und sie nur zur Datenhaltung einsetzt. Man spricht dann bei Java-Programmen von POJOs (Plain Old Java Objects), in C++ oder .net sind es POCOs (Plain Old C++/CLR Objects) und dementsprechend heißen sie in PHP POPOs :-D.

    Die Objekte, die sich um den Transport POPO <-> DB kümmern, heißen Repository. Repositories werden nach fachlichen Kritierien erstellt, d.h. du schreibst nicht für jede Table ein Repository, sondern für "Business Domains" - fachliche Komplexe.

    Ein Repository ist eine Klasse, von der es genau eine Instanz gibt, und es dient als Fabrik für die fachlichen Klassen - wie zum Beispiel dein Proof. Sofern ein Eintrag in der Proof-Table keine Beziehungen zu anderen Tables hat, ist das alles relativ simpel und mehr eine akademische Übung.

    „Ein Repository ist eine Klasse, von der es genau eine Instanz gibt“ - das heißt nicht, dass es nur ein Repository gibt. Es gibt normalerweise mehrere davon, aber jedes Repositoryobjekt hat eine eigene Klasse. In PHP könnte man auf den Repository-Klassen auch mit statischen Methoden arbeiten, ich würde aber davon abraten und die benötigten Repositories von einer zentralen Instanz (Repository-Factory / Service-Factory) bei Bedarf als Singleton-Objekt erzeugen lassen.

    Das ProofRepository verfügt über Methoden wie

    Get($id) - liefert ein Proof-Objekt zur id Save($proof) - speichern ein Proof-Objekt in der DB Delete($id) - löscht einen Proof Find($text) - sucht alle Proofs zum Text, da gibt's vermutlich mehrere Möglichkeiten.

    Die Get($id) Operation sollte das gelesene Proof-Objekt cachen, so dass ein zweiter Get zur gleichen id nicht mehr auf die DB zugreifen muss. Die Save-Operation kann das zu speichernde Objekt mit dem gecachten Objekt vergleichen und entscheiden, was gespeichert werden muss. Das ist bei deinem Proof wenig ergiebig, aber stell Dir vor, ein Proof ist ein Bündel von Teildokumenten. Die Save() Methode würde dann erkennen, ob Teile hinzugefügt oder entfernt wurden, und entsprechende SQL Befehle ausführen.

    Das Mapping eine DB-Row auf das Proof-Objekt wäre ebenfalls Aufgabe des Repositories. Das ist allerdings keine triviale Sache, deswegen gibt es fertige ORM Bibliotheken, die einen großen Teil der Repository-Aufgaben fertig mitbringen. Wenn Du es selbst machen willst, dann brauchst Du im Repository eine interne Factory-Methode, die aus einer DB-Row ein Proof-Objekt erzeugt und umgekehrt aus einem Proof-Objekt die nötigen Spaltenwerte für INSERT oder UPDATE ableiten kann.

    Rolf

    --
    sumpsi - posui - clusi
    1. hi

      nein, es ist keine gute Idee, wenn ein Konstruktor das Objekt aus der Datenbank liest.

      Wieso nicht? Begründung? Wo denn sonst sollen die Daten von Kunden (Name, Str, PLZ, Ort ..) gespeichert sein? Und warum sollte man OOP nicht sinnvoll dazu nutzen einen Kunden zur Instanz der Klasse Kunden zu machen? Warum nicht mit persistenten Eigenschaften die ein Konstruktor aus einer DB liest? Abgesehen davon daß es auch objektorioientierte DBs gibt. Und einer Datenhaltung in Dateien natürlich.

      MFG

      PS: Gerade wenn man transparente Layer anstrebt wird OOP interessant. So kann man über eine User-Instanz die Methode $user->write() aufrufen was vorgenommene Ändrungen persistent macht. Wo diese Persistierung stattfindet ist Sache der Klasse und nicht der Instanz.

      1. Hallo pl,

        die Begründung ist der Rest des Posts. Hauptargument: Separation Of Concerns.

        Rolf

        --
        sumpsi - posui - clusi
        1. Ein Repository ist eine Klasse, von der es genau eine Instanz gibt,

          Warum soll es denn nicht möglich sein, mehrere Instanzen gleichzeitig erstellen zu dürfen!? Stell Dir mal vor, mehrere Shopmitarbeiter wollen Artikel bearbeiten. Soll da etwa jeder Mitarbeiter auf den Anderen warten müssen?

          Oder Kunden wollen ihre Kundendaten ändern, wäre ja noch schlimmer!

          Und wenn Du schon von JAVA schreibst: Ja, Plain Old Java Objects gibt es auch in Perl. Das sind nur Objekte mit Eigenschaften und haben demzufolge auch keine Methoden weil sie keine Instanzen sind.

          So kann man sicher eine Funktion mit einem get($poo, 'name') aufrufen, wird sich jedoch füher oder später mal die Frage stellen in welchem Namespace die ganzen Funktionen zu organisieren wären.

          Und genau hier sehen wir nämlich welchen Vorteil OOP bringt: Aus Funktionen werden Methoden und damit einem ganz bestimmten Namespace, nämlich der der Klasse und nur dieser, zugeordnet. So wird aus

          $name = get($user, 'name'); # Plain Object
          $user->get('name');         # Objekt als Instanz
          

          und das Zumüllen der main-Class mit Funktionen vermieden von denen je nach Anwendung nur eine Handvoll benötigt werden. Mit AUTOLOAD ist es in Perl dann auh möglich, Methoden in dedizierte Dateien auszulagern so daß deren CODE nur bei Bedarf kompiliert wird. Kann das JAVA auch?

          1. Hallo pl,

            Warum soll es denn nicht möglich sein, mehrere Instanzen gleichzeitig erstellen zu dürfen!? Stell Dir mal vor, mehrere Shopmitarbeiter wollen Artikel bearbeiten. Soll da etwa jeder Mitarbeiter auf den Anderen warten müssen?

            Repositories im Web-Umfeld existieren nur für die Dauer eines Webrequests.

            Ob ein User auf irgendwas warten muss, ist eine Frage der verwendeten Server-Runtime. Nicht Apache, sondern die Schicht darüber. Das Programmiermodell von PHP ist so, dass ein Webrequest auf der grünen Wiese beginnt und seine Speicherinhalte nach Script-Ende verworfen werden. FastCGI behält zwar den Code im Speicher, die Daten werden aber verworfen. Kommen mehrere Webrequeste gleichzeitig, werden sie gequeued und nacheinander ausgeführt, es sei denn, der Server bildet einen Webgarden[1] und hat parallele PHP Instanzen. Jedenfalls sieht der eine Request die Daten des anderen nicht. In einem solchen Szenario ist es egal, ob ich das Repository als Singleton-Instanz fahre oder als statische Methoden.

            In ASP.NET (C#) ist es anders. ASP.NET ist ein HTTP Handler im IIS, und kann mittels async-Methoden mehrere Requests quasiparallel fahren (d.h. die I/O-Pausen von Request 1 nutzen um Code von Request 2 auszuführen). Würde ich dort Repositories als Klassen mit statischen Methoden bauen, würden sie sich gegenseitig stören. Und selbst, wenn ich nicht async programmieren würde, dann würde bei statischem Code der Request n+1 noch die Daten des Requests n in den Repositories vorfinden (was gelegentlich gewollt sein kann, aber Caching baut man normalerweise anders). Ich muss in ASP.NET die Repositories pro Request anlegen und als Objekte im RequestContext vorhalten (bzw. als Parameter durch die Gegend reichen).

            Also - TL;DR - ja, je nach Server warten die Mitarbeiter. Allerdings nicht von BeginSession bis End-Session, sondern von Webrequest zu Webrequest. Wenn der eine "Suchen" klickt und die Suche 10s läuft, beginnt die Suche des zweiten erst, wenn der erste sein Ergebnis hat. Es sei denn, wie gesagt, der Server fährt einen Webgarden und betreibt mehrere PHP Prozesse parallel. Oder das mod_php kooperiert mit dem Apache, nimmt mehrere Requests parallel an und führt sie in eigenen Threads isoliert voneinander aus (was dann de facto auch ein Webgarden ist).

            Das Programmiermodell des Repositories ist jedenfalls so, dass ich für einen Webrequest genau eine Instanz eines Repositories habe.

            Rolf

            --
            sumpsi - posui - clusi

            1. Der kleine Bruder der Webfarm, in der mehrere Server parallel laufen ↩︎

            1. Repositories im Web-Umfeld existieren nur für die Dauer eines Webrequests.

              Der Begriff war mir in diesem Zusammenhang neu.

      2. Tach!

        Wieso nicht? Begründung? Wo denn sonst sollen die Daten von Kunden (Name, Str, PLZ, Ort ..) gespeichert sein? Und warum sollte man OOP nicht sinnvoll dazu nutzen einen Kunden zur Instanz der Klasse Kunden zu machen?

        Das wurde ja nicht abgesprochen. Nur wie die Kundenklasse im Falle des Repository-mit-POxO-Modells und im Falle von Active Record aussieht, das ist der wesentliche Unterschied.

        Warum nicht mit persistenten Eigenschaften die ein Konstruktor aus einer DB liest?

        Dinge materialisieren sich nicht von selbst. Man kann natürlich nach dem Active-Record-Prinzip arbeiten und hat dann schwergewichtige Objekte, die neben ihren eigentlichen Daten auch noch das Datenbankhandling mit sich rumschleppen. Im Gegensatz dazu gibt es die getrennte Vorgehensweise. Datenobjekte haben nicht mehr als ihre eigentlichen Eigenschaften. Zusätzlich kann man ihnen auch noch weitere Eigenschaften (beispielsweise berechnete) und auch Methoden geben, aber die sollten sich auf das Datenobjekt selbst beziehen und nicht Aufgaben der Umgebung erfüllen. Kleinere Einheiten lassen sich leichter testen und verwalten als eierlegende Wollmilchsäue.

        dedlfix.

      3. Hallo,

        es ist keine gute Idee, wenn ein Konstruktor das Objekt aus der Datenbank liest.

        Wieso nicht? Begründung?

        Neben dem genannten S aus SOLID sorgt es vor allem dafür, dass der Code ziemlich schwer testbar ist. Bleiben wir beim Beispiel oben: a) es gibt eine implizite Abhängigkeit der Klasse proofs zur Klasse Db, die von außen (Signatur des Konstruktors) nicht erkennbar ist. b) es ist nicht möglich, diese Abhängigkeit in irgendeiner Form auszutauschen. In Produktiv-Code bedeutet dies, dass man verhindert, dass die Klasse mit einer anderen DB benutzt wird. Im Test-Code bedeutet es, dass man mindestens eine echte DB-Instanz initialisieren muss um die Klasse zu testen.

        Ich empfehle dir hierzu diesen Artikel zu Clean Code und wie man testbaren Code schreibt.

        Viele Grüße Matti

        1. Es gibt immer Abhängigkeiten. Und es gibt in OOP 2 Wege zur Auflösung:

          1. Vererbung
          2. Dependency Injection

          Wenn Instanzen der Klasse Proof Methoden der Klasse DB nutzen wollen, wird man entweder diese Methoden von der Klasse DB erben (1) oder sich durch DI (2) zueigen machen. Wann diese Aggregierung erfolgt und auf welche Art und Weise ist eine andere Frage die nur das von Dir angesprochene Verhalten beim Testen bestimmt. Hatten wir hier aber auch schon. GGA

          1. Hallo pl,

            Vererbung und DI sind zwei verschiedene Dinge.

            1. Vererbung: Statisch, zur Laufzeit nicht änderbar. In den meisten Sprachen. Manchmal kann man Laufzeit zur Vererbung herstellen (PHP dynamic objects, JavaScript prototype chain). Semantik: "A ist ein B".

            2. DI: Dynamisch, während der Laufzeit ausgeführt. Semantik: "A benutzt ein B".

            Rolf

            --
            sumpsi - posui - clusi
            1. Vererbung und DI sind zwei verschiedene Dinge.

              Genau! Sie beschreiben zwei verschiedene Möglichkeiten zur effizienten Auflösung von Abhängigkeiten. Sie sind übrigens die Einzig effizienten Möglichkeiten die auf einen sauberen Programmierstil hinauslaufen. Aber auch nur wenn man sie verstanden hat.

              1. Tach!

                Vererbung und DI sind zwei verschiedene Dinge.

                Genau! Sie beschreiben zwei verschiedene Möglichkeiten zur effizienten Auflösung von Abhängigkeiten.

                Vererbung ist ein Mittel, eine starre Abhängigkeit zu schaffen. Das ist nicht in jedem Fall effizient, wenn man immer wieder diese Verbindungen berücksichtigen muss. Eine ganze Menge Leute sehen das jedenfalls als problematisch an, weil sich damit Schwierigkeiten für beispielsweise TDD (Test Driven Design) ergeben, was sie jedoch als Vorteil für qualitativ hochwertige Software ansehen.

                Sie sind übrigens die Einzig effizienten Möglichkeiten die auf einen sauberen Programmierstil hinauslaufen.

                Wenn man sich mit der einen Vorgehensweise Probleme ins Boot holt, kann man durchaus unterschiedlicher Meinung sein, was man als sauber ansieht.

                dedlfix.

                1. Hallo dedlfix,

                  um das für das Thema ORM mal zu adressieren: Aus meiner Sicht kann es sinnvoll sein, eine Basisklasse "DatamodelObject" oder so zu vergeben, die das Interface für ORM bereitstellt und die mittels Reflection oder abstrakten Methoden die nötigen Informationen für das Mapping bereitstellt, aber die Kopplung an eine konkrete Repositoryimplementierung würde ich per DI durchführen.

                  Als Alternative zum DatamodelObject gibt es auch das Decorator-Pattern, d.h man erzeugt (dynamisch) eine Klasse, die alle Methoden/Properties des Datenmodell-Objekts kopiert, plus die nötigen ORM Methoden mitbringt. Ein Objekt dieser Klasse wird dem Datenmodell-Objekt vorgeschaltet und delegiert alle kopierten Methoden und Properties dorthin. Bei der Gelegenheit kann der Aufruf eines Setters auch automatisch ein "Changed" Bit setzen. Das setzt natürlich eine Sprache voraus, in der man Property-Getter/-Setter bauen kann. Alternativ kann man auch eine Klasse erzeugen, die von der Datenmodell-Klasse erbt; aber ohne programmierbare Getter/Setter ist das auch langweilig.

                  Ich habe sowas vor 15 Jahren mal in C# gebaut, da haben wir die DatamodelObject Basisklasse verwendet und in jeder konkreten Klasse des Datenmodells ein Setter-Muster benutzt, um Property-Changes zu signalisieren. War einiges an Boilerplate, aber dynamische Generierung von Klassen war mir damals noch fremd. Vor 25 Jahren hatte mein Unternehmen sowas als Standardsoftware auf der damals verwendeten Smalltalk-Plattform, da designte man die Fachklassen und ließ die Implementierung mit allem Boilerplate generieren. Dependency Injektion des ORM System war da aber nicht vorgesehen und Unittests unmöglich...

                  Insofern: Ich habe schon einiges an Schmerzen mit ORM erlebt…

                  Rolf

                  --
                  sumpsi - posui - clusi
                  1. Vererbung ist nicht immer sinnvoll. Und Mehrfachvererbung ist es auch nicht. Man muss eine Klasse schon sehr genau kennen wenn man vorhat das Erbe derer anzutreten. So ist oftmals DI der zweckmäßigere Weg.

                    Es gibt jedoch zumindest in Perl Möglichkeiten, Abhängigkeiten virtuell zu manifestieren um Programmcode lesbarer zu machen. Beispiel:

                    package xCGI::File;
                    ..
                    package xCGI;
                    ..
                    

                    Als eine weitere Möglichkeit einer sauberen und reproduzierbaren Programmierung. Die Abhängigkeit der Klasse xCGI::File von Klasse xCGI ist rein virtuell. Tatsächlich findet hier gar keine Vererbung statt.

                    Diese Virtualisierung ist auch wesentlich für CPAN. Viele Autoren halten sich daran und das aus gutem Grunde. MFG

                    PS: Neue CPAN Autoren werden auf diese Möglichkeit auch hingewiesen. Also daß bereits am Namen der Package zu sehen ist in welche KlassenHierarchie die gehört. So gib es bspw. die libnet und libwww. In Letzerer spielen sehr viele Klassen zusammen und so kommen sowohl DI als auch Vererbung gleichermaßen zur Anwendung.

    2. Tach!

      Die Get($id) Operation sollte das gelesene Proof-Objekt cachen, so dass ein zweiter Get zur gleichen id nicht mehr auf die DB zugreifen muss.

      Naja, das hängt auch stark vom Anwendungsfall ab. Cachen ist außerdem eine zweite Tätigkeit und nicht Kernaufgabe des Repositorys. Vielleicht hätte man doch lieber die Datenbank als Single Source of Truth haben und möchte sich nicht mit Cache Invalidation herumschlagen und Daten cachen, die bereits jemand anderes verändert hat.

      Die Save-Operation kann das zu speichernde Objekt mit dem gecachten Objekt vergleichen und entscheiden, was gespeichert werden muss.

      Oder sie kann mit der im DBMS gespeicherten Version vergleichen und Bearbeitungskonflikte aufzeigen.

      dedlfix.

    3. Tach!

      Das Mapping eine DB-Row auf das Proof-Objekt wäre ebenfalls Aufgabe des Repositories.

      Übrigens, wenn man Fremdwörter in einer anderen Sprache verwendet, so ist es eigentlich üblich sie nach den Rechtschreib- und Grammatikregeln der Zielsprache zu behandeln. Es ist zwar schön, wenn man für Englisch, als eine weit verbreitete und wichtige Sprache, weiß, wie die dortigen Regeln lauten, aber die sind für deutsche Texte nicht weiter relevant. Ebensowenig wie die Ursprungssprachen von vielen anderen bereits eingebürgerten Wörtern und deren Beugungsregeln ihrer Ursprungssprache. Ausnahmen gibt es selbstverständlich auch, zum Beispiel bei einigen latinischen Wörtern, deren Regeln wir teilweise adaptiert haben.

      Englische Wörter, die auf y enden und in der Mehrzahl ihre Endung auf ies ändern, werden im Deutschen durch ein einfaches Anhängen eines s gebildet. Baby - Babys, nicht Babies. Hobby - Hobbys, nicht Hobbies. Und wenn man es in korrektem Englisch schreiben wollte, müsste man auch auf die Großschreibung verzichten. Dabei würde sich ganz deutlich der Konflikt zeigen, in den man sich unnötigerweise begibt. Die Regeln der Zielsprache sollte man auch bei Fachbegriffen verwenden, die kein Teil der allgemeinen Sprache sind.

      Was jedoch weder Deutsch noch Englisch ist: die Genitiv-Singular-Form eines Repositorys auf ies enden zu lassen. Aha, englisches Genitiv, da war doch was, das dem deutschen Deppenapostroph entspricht. Nun, "Aufgabe des Repository's" wäre auch nur ein Mischmasch. Bei "task of the repository" findet keine Beugung statt, ein s wird aber für das Deutsche benötigt, damit es nicht falsch klingt. "the repository's task" wäre im Englischen richtig, aber das muss man für die deutsche Sprache nicht wissen, und man muss den Teil des obigen Satzes auch nicht entsprechend umstellen, damit man ein Genitiv-s verwenden darf. Einfach ein zusatzloses s für Genitiv und Mehrzahl anhängen, wie man das für eine Menge anderer Wörter ohne Beachtung der Herkunft auch macht.

      dedlfix.

      1. Tach!

        Das Mapping eine DB-Row auf das Proof-Objekt wäre ebenfalls Aufgabe des Repositories.

        Übrigens, wenn man Fremdwörter in einer anderen Sprache verwendet, so ist es eigentlich üblich sie nach den Rechtschreib- und Grammatikregeln der Zielsprache zu behandeln.

        Beispiel: Der Sonne, die Mond. Hauptsache latainisch! GGA

        --
        Eis ist die Mehrzahl von Ei.
        1. Hallo,

          Eis ist die Mehrzahl von Ei.

          Also mindestens ja wohl Zweis!

          Gruß
          Kalk

          1. Hallo,

            Eis ist die Mehrzahl von Ei.

            Also mindestens ja wohl Zweis!

            Ei ist ein schlechts Beispiel ich geb zu. Nehmen wir Schal und bilden die Mehrheit. Kann nur Schals sein weil Schäler schon was anderes ist. GG

            1. Hallo,

              Ei ist ein schlechts Beispiel ich geb zu. Nehmen wir Schal und bilden die Mehrheit. Kann nur Schals sein weil Schäler schon was anderes ist. GG

              Meinten Sie: Schale?

              Gruß
              Kalk

              1. Hallo,

                Ei ist ein schlechts Beispiel ich geb zu. Nehmen wir Schal und bilden die Mehrheit. Kann nur Schals sein weil Schäler schon was anderes ist. GG

                Meinten Sie: Schale?

                Ach wie abgestanden ist das denn 😉

    4. Ich wollte schon das gleiche erzählen, aber du bist mir zuvor gekommen. Stattdessen nütze ich den Thread, um ein wenig über Design-Entscheidungen zu plaudern und hoffe, dass man mich dafür kritisiert.

      Es ist häufig so, dass man Fachlogik aus den Modellklassen ganz heraushält und sie nur zur Datenhaltung einsetzt. Man spricht dann bei Java-Programmen von POJOs (Plain Old Java Objects), in C++ oder .net sind es POCOs (Plain Old C++/CLR Objects) und dementsprechend heißen sie in PHP POPOs :-D.

      Die Modellklasse für einen Proof könnte in PHP z.B. so aussehen:

      <?php
      
      namespace Demo\Domain;
      
      final class Proof
      {
          private int $id;
      
          private string $title;
      
          private int $price;
      
          public function __construct(int $id, string $title, int $price)
          {
              $this->id = $id;
              $this->title = $title;
              $this->price = $price;
          }
      
          public function getId() : int
          {
              return $this->id;
          }
      
          public function getTitle() : string
          {
              return $this->title;
          }
      
          public function getPrice() : int
          {
              return $this->price;
          }
      }
      

      Das ist natürlich ne Menge Schreibarbeit für eine reine Datenklasse, die keine Geschäftslogik enthält. Man hätte sich die getter-Methoden sparen können und dafür die Attribute public machen können. Das ist eine freie Design-Entscheidung, ich persönlich bevorzuge unveränderliche Objekte, und habe mich daher für dieses Design entschieden und die Tipparbeit in Kauf genommen. Einige PHP-Frameworks bieten auch die Möglichkeit an, solche Datenklassen, automatisch erzeugen zu lassen.

      Eine andere Design-Entscheidung von mir ist die Klasse mit final auszuzeichnen. Das ist in diesem Fall auch rein meinen persönliche Vorlieben geschuldet: ich bevorzuge Komposition gegenüber Vererbung, deshalb öffne ich meine Klassen nur für Vererbung, wenn ich sie wirklich mal brauche.

      Die Objekte, die sich um den Transport POPO <-> DB kümmern, heißen Repository. Repositories werden nach fachlichen Kritierien erstellt, d.h. du schreibst nicht für jede Table ein Repository, sondern für "Business Domains" - fachliche Komplexe.

      Ein passendes Proof-Repostiory zum obigen Domain-Model könnte etwa so aussehen:

      <?php
      
      namespace Demo\Persistence;
      
      use \PDO;
      use \Demo\Domain\Proof;
      
      final class ProofRepository
      {
          private PDO $connection;
      
          public function __construct(PDO $connection)
          {
              $this->connection = $connection;
          }
      
          public function proofsWithIdGreaterThan(int $id) : array
          {
              $statement = $this
                  ->connection
                  ->prepare("SELECT id, title, price FROM proofs WHERE id > :id");
              $statement->bindValue(':id', $id, PDO::PARAM_INT);
              $statement->execute();
              return $stament->fetchAll(
                  PDO::FETCH_FUNC,
                  function (string $id, string $title, string $price) {
                      return new Proof((int)$id, $title, (int)$price);
                  }
              );
          }
      }
      

      Hier habe ich dieselbe Entscheidung über Vererbung wie oben getroffen. Außerdem habe ich mich anders als Rolf entschieden, und die (im Moment einzige) Methode des Repositorys nicht statisch deklariert. Nach Rolfs Vorschlag müsste die Datenbank-Verbindung jeder statischen Methode als zusätzlicher Parameter mitgegeben werden, das fände ich müßig beim Aufrufen. Oder man hätte die Datenbank-Verbindung innerhalb der Methoden selbst herstellen können, das wäre aber wieder eine Verletzung von SOLID. In anderen Programmiersprachen, die partielle Funktions- bzw. Methoden-Aufrufe erzeugen, hätte ich mich auch für das statische Design entschieden.

      Der aufrufende Code müsste dann übrigens etwa so aussehen:

      use \Demo\Domain\Proof;
      use \Demo\Persistence\ProofRepository;
      
      $connection = new Db();
      $repository = new ProofRepository($connection);
      $proofs = $repository->proofsWithIdGreaterThan(100);
      
      1. Hallo 1unitedpower,

        Außerdem habe ich mich anders als Rolf entschieden

        Nein, hast Du nicht. Statische Repositories hatte ich doch als die schlechtere Lösung bezeichnet, oder?

        Die DB Connection von der Repository-Factory in das Repository zu übergeben ist aus meiner Sicht die genau richtige Lösung.

        In einem Projekt von mir, wo ich mit Repositories arbeite, ist es sogar ein SQL Adapter, der das SQL automatisch generiert und den ich genutzt hatte, um den Schritt von mysql nach PDO zu machen.

        Rolf

        --
        sumpsi - posui - clusi
        1. Statische Repositories hatte ich doch als die schlechtere Lösung bezeichnet, oder?

          Stimmt, hast du. Ich hab mich in einer anderen Frage anders entschieden: Du hast ein Singelton-Interface vorgeschlagen. Singeltons und statische Methoden lösen ähnliche Probleme auf unterschiedliche Weise: In beiden Fällen möchte man vermeiden, dass Methoden auf unterschiedlichen Instanzen der Klasse aufgerufen werden. Mit einem Singelton-Interface stellt man sicher, dass zur Laufzeit nur eine einige Instanz der Klasse jemals erzeugt wird. Mit einer statischen Methode legt man zur Entwicklungszeit fest, dass eine Methode keinen Zugriff auf den Laufzeit-Kontext $this hat. Im Allgemeinen bevorzuge ich statische Verfahren, die Fehler frühzeitig aufdecken. Im konkreten Fall, halte ich es aber für möglich, dass ein Repository mit unterschiedlichen Datenbank-Verbindungen instanziiert wird, bspw. um Datenbestände zu synchronisieren, also habe ich mich gegen Singelton und statische Methoden entschieden.

          Die DB Connection von der Repository-Factory in das Repository zu übergeben ist aus meiner Sicht die genau richtige Lösung.

          Bzw. im 0815-Konstruktor. Mein Verhältnis zu Factorys (und anderen Erzeugungsmustern) ist ambivalent. Wenn ich eine Klasse habe, die so komplex ist, dass ich eine zweite Klasse brauche, nur um Instanzen der ersten Klasse zu erzeugen, dann ist das ein Indiz dafür, dass die erste Klasse zu viele Dinge erledigt. Mit einer Factory oder einem Builder lässt sich die Komplexität verwalten, aber nicht vermindern. Manchmal ist das aber auch "gut genug" oder sogar die beste Lösung.

          1. Hallo 1unitedpower,

            die Factory kümmert sich darum, dass die Repositories Singletons bleiben, und sorgt für die Injektion der SQL Connection.

            Die Alternative wäre eine DI Library, die so parametrierbar ist, dass sie für bestimmte Interfaces den Singleton automatisch bildet und automatisch ein anderes Singleton-Objekt (die Connection) in den Konstruktor injiziert. In der .net Welt wäre das z.B. Unity aus der Patterns&Practices Library von Microsoft.

            Rolf

            --
            sumpsi - posui - clusi
            1. die Factory kümmert sich darum, dass die Repositories Singletons bleiben, und sorgt für die Injektion der SQL Connection.

              Nur, um sicher zu gehen, du redest von Factory-Methods, oder? Ansonsten wüsste ich nicht, wie du in PHP sicherstellen willst, dass eine Klasse nicht mehrfach erzeugt wird.

              Die Alternative wäre eine DI Library, die so parametrierbar ist, dass sie für bestimmte Interfaces den Singleton automatisch bildet und automatisch ein anderes Singleton-Objekt (die Connection) in den Konstruktor injiziert.

              Ich verstehe noch nicht wieso du aus Repostiorys oder Datenbank-Verbindungen Singletons machen möchtest? Manche Dinge sind einfach schlechte Singletons, Götter zum Beispiel.

              1. Hallo 1unitedpower,

                die Factory kümmert sich darum, dass die Repositories Singletons bleiben, und sorgt für die Injektion der SQL Connection.

                Nur, um sicher zu gehen, du redest von Factory-Methods, oder? Ansonsten wüsste ich nicht, wie du in PHP sicherstellen willst, dass eine Klasse nicht mehrfach erzeugt wird.

                Ja, sicher. Im klassischen GoF Singleton-Pattern ist die Factory-Methode statisch in der Klasse enthalten, deren Objekt Singleton sein soll. Dann lässt sich technisch verhindern, dass ein Objekt anders als mit dieser Factory-Methode erzeugt wird.

                Wenn man die Anwendung so baut, dass die DB-Verbindung nicht in einer globalen Variablen steht, sondern nur dem Repository-Factoryobjekt bekannt ist, kann man mittels dieses Exklusiv-Wissens ebenfalls sicherstellen, dass es keine unbefugte Instanziierung gibt.

                Ich verstehe noch nicht wieso du aus Repositorys oder Datenbank-Verbindungen Singletons machen möchtest?

                Normalerweise braucht man nur eine Connection zu einer Datenbank. Und die kann man entweder nach dem Prinzip Heiße Kartoffel handhaben (d.h. pro SQL Befehl in die Hand nehmen und dann schnell wieder wegwerfen) oder einfach beim ersten SQL öffnen und bis zum Ende des Requests offenhalten. In meiner Anwendung hatte ich einen DB-Adapter, der die verwendete DB kapseln sollte, und den als Singleton genutzt.

                Ein Repository sollte ein Singleton (pro Repositoryklasse) sein, weil es wissen muss, was es während des laufenden Requests schon gemacht hat (welche Objekte sind unterwegs, welche sind verändert worden, etc). Wenn es 2 oder mehr gibt, kommen sie sich ins Gehege.

                Rolf

                --
                sumpsi - posui - clusi
                1. Ich verstehe noch nicht wieso du aus Repositorys oder Datenbank-Verbindungen Singletons machen möchtest?

                  Normalerweise braucht man nur eine Connection zu einer Datenbank.

                  Eine Verbindung je Datenbank. Wie ich schon schrieb: bei Synchronisierungen von Datenbeständen aus verschiedenen Datenbanken benötigt man mindestens zwei Verbindungen und am besten auch zwei Repositories.

                  Ein Repository sollte ein Singleton (pro Repositoryklasse) sein, weil es wissen muss, was es während des laufenden Requests schon gemacht hat (welche Objekte sind unterwegs, welche sind verändert worden, etc). Wenn es 2 oder mehr gibt, kommen sie sich ins Gehege.

                  Wenn ich dich richtig verstehe, dann befürchtest du also Race-Conditions. Wenn das das Problem ist, dann verschafft das Singelton-Pattern aber auch keinen Abhilfe. Ein Singelton ist nicht unverisal einzigartig, sondern nur pro Prozess. Gerade bei PHP-Webanewndungen ist es aber üblich, dass mehrere Prozesse gleichzeitig laufen. Die Race-Condition bestünde also immernoch. Dritte Programme, die auch mit der Datenbank kommunzieren mal außenvor gelassen.

                  In vielen Fällen hilft es schon, wenn man die betroffenen SQL-Statements in einer Transaktion abwickelt. In komplizieren Fällen, muss man auf Anwednungsebene die konkurrierenden Zugriffe auf kritische Ressourcen managen.

                  1. Hallo 1unitedpower,

                    nein, ich dachte nicht an Race-Conditions zwischen zwei Requests - dagegen helfen nur Transaktionen oder Locks (optimistisch, pessimistisch, wie auch immer implementiert).

                    Ich dachte daran, dass ein Repository ein Wissen darüber hat, welche Objekte es geladen hat, welche Rows also sozusagen zu Objekten wurden, und damit das Objektmodell sauber verwalten kann. Wenn bspw. in der DB zwei Personen die gleiche Adresse referenzieren, sollten die Personenobjekte auch das gleiche Adressobjekt referenzieren. Und wenn dieses Wissens auf 2 Repositories in einem Request verteilt ist, gibt es unnötig Ärger.

                    Das Problem, dass ein anderer Request eventuell parallel das gleiche tut, ist natürlich immer noch zu lösen.

                    Rolf

                    --
                    sumpsi - posui - clusi
                    1. Tach!

                      Ich dachte daran, dass ein Repository ein Wissen darüber hat, welche Objekte es geladen hat, welche Rows also sozusagen zu Objekten wurden, und damit das Objektmodell sauber verwalten kann. Wenn bspw. in der DB zwei Personen die gleiche Adresse referenzieren, sollten die Personenobjekte auch das gleiche Adressobjekt referenzieren. Und wenn dieses Wissens auf 2 Repositories in einem Request verteilt ist, gibt es unnötig Ärger.

                      Das ist aber nicht abhängig vom Request sondern von der zu erfüllenden Aufgabe. Davon könnte es durchaus auch mehrere unabhängige pro Request und an derselben Tabelle geben.

                      dedlfix.

      2. Tach!

        Ich wollte schon das gleiche erzählen, aber du bist mir zuvor gekommen. Stattdessen nütze ich den Thread, um ein wenig über Design-Entscheidungen zu plaudern und hoffe, dass man mich dafür wehement kritisiert.

        Für das falsch geschriebene vehement könnte ich dich kritisieren, aber für die fachliche Diskussion brauche ich dieses Adjektiv nicht.

        Die Modellklasse für einen Proof könnte in PHP z.B. so aussehen:

        <?php
        
        namespace Demo\Domain;
        
        final class Proof
        {
            private int $id;
        
            private string $title;
        
            private int $price;
        
            public function __construct(int $id, string $title, int $price)
            {
                $this->id = $id;
                $this->title = $title;
                $this->price = $price;
            }
        
            public function getId() : int
            {
                return $this->id;
            }
        
            public function getTitle() : string
            {
                return $this->title;
            }
        
            public function getPrice() : int
            {
                return $this->price;
            }
        }
        

        Die Propertys mit Typ auszuzeichnen ergab (in PHP 7.2) einen Fehler. Das hab ich auch im Handbuch so nicht gefunden.

        Das ist natürlich ne Menge Schreibarbeit für eine reine Datenklasse, die keine Geschäftslogik enthält. Man hätte sich die getter-Methoden sparen können und dafür die Attribute public machen können. Das ist eine freie Design-Entscheidung, ich persönlich bevorzuge unveränderliche Objekte, und habe mich daher für dieses Design entschieden und die Tipparbeit in Kauf genommen.

        Hier mal meine Variante:

        class Proof
        {
            private $id;
        
            private $title;
        
            private $price;
        
            public function __construct(int $id, string $title, int $price)
            {
                $this->id = $id;
                $this->title = $title;
                $this->price = $price;
            }
        
            public function __get($name)
            {
                return $this->$name;
            }
        }
        

        Weniger Tipparbeit und man muss nicht unterscheiden, ob man $x->getFoo() oder $x->foo schreiben muss. Man kann noch mehr Fehlerbehandlung in __get() einbauen, aber die hauseigenen PHP-Meldungen reichen im Prinzip aus, um unzulässige Zugriffe zu erkennen. Ein __set(), das eine Exception wirft, wäre vielleicht noch sinnvoll, denn ohne dies kann man beliebige Eigenschaften hinzufügen, die andere Namen haben als den privaten Eigenschaften.

        Mit dem Magic Getter geht leider der Type Hint für den Variablentyp verloren, aber ich glaube mich zu erinnern, dass es da PHPDoc-Syntax gibt, um solche versteckten Eigenschaften der IDE bekanntzugeben.

        Eine andere Design-Entscheidung von mir ist die Klasse mit final auszuzeichnen. Das ist in diesem Fall auch rein meinen persönliche Vorlieben geschuldet: ich bevorzuge Komposition gegenüber Vererbung, deshalb öffne ich meine Klassen nur für Vererbung, wenn ich sie wirklich mal brauche.

        Selbst wenn ich sie als final ansehe, würde ich dieses Schlüsselwort nicht setzen wollen. Was stört es mich denn, wenn ein Verwender davon erbt, wenn das unbedingt gewünscht ist? Aus welchem Grunde muss ich das aktiv verhindern? Ich selbst werde wohl nicht auf die Idee kommen, eine Vererbung zu verwenden, geschweige denn es unbeabsichtigt zu notieren, wenn das meine bevorzugte Variante wäre.

        dedlfix.

        1. Für das falsch geschriebene vehement könnte ich dich kritisieren, aber für die fachliche Diskussion brauche ich dieses Adjektiv nicht.

          Danke für den Hinweis, ist korrigiert.

          Die Propertys mit Typ auszuzeichnen ergab (in PHP 7.2) einen Fehler. Das hab ich auch im Handbuch so nicht gefunden.

          Du hast recht, das ist erst für 7.4 vorgesehen. Mein Code-Editor ist mir da schon in der Zeit voraus.

          Hier mal meine Variante:
          […]
          Weniger Tipparbeit und man muss nicht unterscheiden, ob man $x->getFoo() oder $x->foo schreiben muss. Man kann noch mehr Fehlerbehandlung in __get() einbauen, aber die hauseigenen PHP-Meldungen reichen im Prinzip aus, um unzulässige Zugriffe zu erkennen. Ein __set(), das eine Exception wirft, wäre vielleicht noch sinnvoll, denn ohne dies kann man beliebige Eigenschaften hinzufügen, die andere Namen haben als den privaten Eigenschaften.

          Mit dem Magic Getter geht leider der Type Hint für den Variablentyp verloren […]

          Und damit auch ein paar Intellisense-Funktionen und Autovervollständigung. Deshalb hab ich mich für die ausgeschriebene Variante entschieden. Früher habe ich aber auch regelmäßig die magic-Methods benutzt, um mir die Tipparbeit zu sparen. Inzwischen nehme ich bei solchen No-Brainern die Tipparbeit aber häufiger in Kauf, auch wenn das nur marginale Vorteile mit sich bringt.

          aber ich glaube mich zu erinnern, dass es da PHPDoc-Syntax gibt, um solche versteckten Eigenschaften der IDE bekanntzugeben.

          Ah, das kannte ich noch gar nicht. Habs aber für alle Interessierten in den Docs gefunden: @property. Falls sich für mich nochmal eine Gelegenheit ergibt, werde ich das mal ausprobieren.

          Eine andere Design-Entscheidung von mir ist die Klasse mit final auszuzeichnen. Das ist in diesem Fall auch rein meinen persönliche Vorlieben geschuldet: ich bevorzuge Komposition gegenüber Vererbung, deshalb öffne ich meine Klassen nur für Vererbung, wenn ich sie wirklich mal brauche.

          Selbst wenn ich sie als final ansehe, würde ich dieses Schlüsselwort nicht setzen wollen. Was stört es mich denn, wenn ein Verwender davon erbt, wenn das unbedingt gewünscht ist? Aus welchem Grunde muss ich das aktiv verhindern?

          Zum einen schütze ich die Nutzer und Nutzerinnen der Klasse vor einer Verwendung, die ich als Autor nicht beabsichtigt habe und die auf seiner bzw. ihrer Seite früher oder später für Frustration sorgen wird: Methodenüberladung kann dafür sorgen, dass Implementierungs-Details der Elternklasse die Kindklasse auf unvorhersehbare Weise beeinflussen. Gehen wir zum Beispiel von einer Klasse A mit zwei öffentlichen Methoden aus:

          class A
          {
              public function foo()
              {
                  // Some Code
              }
          
              public function bar()
              {
                  // Some Other Code
              }
          }
          

          Ein kleiner Test zwischendurch:

          $a = new A()
          $a->foo(); // 42
          

          Klasse B erbt von A und überschreibt die Methode bar:

          class B extends A
          {
              public function bar()
              {
                  throw new Exception("Some men just want to watch the world burn.");
              }
          }
          

          Frage, zu welchem Wert evaluiert die zweite Zeile in folgendem Code?

          $b = new B();
          $b->foo(); // ???
          

          Ich rate ein paar Augeblicke darüber nachzudenken bevor man weiterliest. Ein kleiner Tipp: Die Antwort steckt nicht allein im Code.






          Egal, wie die Antwortet lautet, sie gehört vermutlich zu den richtigen Lösungsmöglichkeiten. Es kommt nämlich darauf an, wie foo in Klasse A implementiert ist, wenn foo direkt 42 zurückgibt, ist die Antwort auch dieses mal 42. Wenn A aber intern $this->bar() aufruft, dann führt das zu einer Exception.

          Der Knackpunkt ist, dass man als Autor der Klasse B nicht wissen kann, wie sie sich verhalten wird, wenn nur eine einzige Methode überschrieben wird, außer man sieht sich die Implementierung der Elternklasse an.

          Zum aderen sorge ich mit final auch dafür, dass die Schnittstelle homogen genutzt wird, ich gebe dem Verwender eine klare Intention zu ihrem Gebrauch mit. Das macht es einfacher für verschiedene Nutzer Code miteinander zu teilen. Ich habe vor Jahren mal irgendwas mit FPDF machen müssen – eine PDF-Library für PHP – die damals noch aus einer Gottklasse bestand. Ich brauchte zwei Zusatzfunktionen, die nicht in FDPF enthalten waren, aber es gab entsprechende Erweiterungsmodule dafür. Das Problem war, dass beide Module jeweils von FPDF geerbt haben und obwohl die Features völlig orthogonal zueinander waren, konnte ich sie nicht zusammen benutzen, einfach weil die AutorInnen sich damals für Vererbung als Mittel der Wahl entschieden haben. Damals habe ich das Problem gelöst, indem ich mir den Code der Erweiterungen kopiert und so modifiziert habe, dass sie kompositionell zusammenarbeiteten, das war ein Aha-Moment für mich.

          1. Tach!

            Selbst wenn ich sie als final ansehe, würde ich dieses Schlüsselwort nicht setzen wollen. Was stört es mich denn, wenn ein Verwender davon erbt, wenn das unbedingt gewünscht ist? Aus welchem Grunde muss ich das aktiv verhindern?

            Zum einen schütze ich die Nutzer und Nutzerinnen der Klasse vor einer Verwendung, die ich als Autor nicht beabsichtigt habe und die auf seiner bzw. ihrer Seite früher oder später für Frustration sorgen wird: Methodenüberladung kann dafür sorgen, dass Implementierungs-Details der Elternklasse die Kindklasse auf unvorhersehbare Weise beeinflussen.

            Ich wähle für die Antwort mal eine paar sehr herbe Worte, formuliert als Angriff, Übertreibung als Stilmittel. Lass sie nicht bis zum Gemüt vordringen, das könnte sich darüber aufregen, was ich aber nicht beabsichtige. Los geht's: Wofür hältst du dich eigentlich, dass du meinst den Verwender vor etwas beschützen zu müssen? Meinst du die Verwender sind nicht gut genug im Programmieren und Code-Verstehen, die potentiellen Auswirkungen zu erkennen? Warum willst du sie davon abhalten, dass sie sich ins Knie schießen, wenn sie es denn unbedingt wollen? Du hast den Code geschrieben, du hast ihn freigegeben, das war's nun. Er ist nicht mehr dein Kind, wenn ein Clone davon anderswo läuft. Lass es laufen.</ende>

            Gehen wir zum Beispiel von einer Klasse A mit zwei öffentlichen Methoden aus:

            [...]

            Klasse B erbt von A und überschreibt die Methode bar:

            [...]

            Frage, zu welchem Wert evaluiert die zweite Zeile in folgendem Code?

            [...]

            Wenn A aber intern $this->bar() aufruft, dann führt das zu einer Exception.

            Wenn es denn so wichtig ist, dass foo() unbeeinflusst vom Zustand von bar() korrekt arbeitet, dann kann man das eigentliche Geschehen auch in eine private Methode auszulagern versuchen, die dann von foo() und bar() aufgerufen wird. Wenn jemand das öffentliche bar() überschreibt, kann er das tun, ohne die Abhängigkeit zu beeinflussen.

            Aber auch das ist nicht in jedem Falle hübsch. Ein paar mal schon hatte ich mir bei Verwendung des .NET-Frameworks gewünscht, eine Methode für eigene Zwecke zu verwenden, weil ich ein etwas anderes Verhalten brauchte, als die vorhandenen Methoden hergaben. Aber ich kam da nicht ran, weil das eigentliche Verhalten in einer private InternalFoo() Methode steckte, und alle verwendbaren Wege dahin irgendwas Zusätzliches machten, das ich nicht brauchte. Ich hätte die ganze Klasse ersetzen und umbauen müssen, um da ranzukommen. Aber dann hätte der Rest des Frameworks mir immer noch Objekte der alten Implementation ohne meine Ergänzungen übergeben. Natürlich kann es an meiner Unerfahrenheit gelegen haben, dass ich dieses Vorgehen als Lösung angesehen hatte. Mittlerweile habe ich den Eindruck, dass bei neuerem Code SOLIDer gearbeitet wird, und Funktionalität ausgelagert und einzeln verwendbar gemacht wird. Das steckt zwar mitunter in Namespaces mit Internal im Namen, aber ist meist doch für Außenstehende verwendbar.

            Der Knackpunkt ist, dass man als Autor der Klasse B nicht wissen kann, wie sie sich verhalten wird, wenn nur eine einzige Methode überschrieben wird, außer man sieht sich die Implementierung der Elternklasse an.

            Ja, das sollte man tun, wenn man etwas verwenden möchte. Wenn es die Dokumentation nicht hergibt, notfalls mit dem Blick in die Quellen oder mit dem Decompiler. PHP selbst ist da jedoch nicht ganz so zugänglich. .NET-Code lässt sich einfacher in lesbaren C#-Code rückübersetzen. Außerdem ist da vieles sowieso im Original C# gewesen und nicht artfremdes C wie bei PHP. Aber dein Code ist ja PHP, und wenn du ihn nicht verschleiert und verschlüsselt auslieferst, hat man da ja die Chance, ihn sich anzusehen.

            Zum aderen sorge ich mit final auch dafür, dass die Schnittstelle homogen genutzt wird, ich gebe dem Verwender eine klare Intention zu ihrem Gebrauch mit.

            Jein. Das ist lediglich ein einzelnes Wort. Es muss nicht immer Intention hinter einem bestimmten Gebrauch stecken. Auch "haben wir schon immer so gemacht" kann der Grund sein. Man kann da nicht immer zweifelsfrei erkennen, was dahintersteckt. Ein erklärender Text in der Dokumentation, warum das da so wichtig ist, und vielleicht auch, was die Folgen sein könnten, ist da meist viel verständlicher. Gerade mit weniger Erfahrung sieht man nur die Tatsache, dass es final oder sealed ist, weiß aber vielleicht noch so nicht recht, warum man sowas macht.

            Damals habe ich das Problem gelöst, indem ich mir den Code der Erweiterungen kopiert und so modifiziert habe, dass sie kompositionell zusammenarbeiteten, das war ein Aha-Moment für mich.

            Das ist schön für dich, aber für dich war es ein Erkenntnisprozess. Einfach nur ein final dastehen zu haben, setzt diesen Prozess nicht in Gang. Es wirkt eher wie ein Verbotsschild, dessen Grund man sich vielleicht erschließen kann, vielleicht aber auch nicht.

            Abgesehen davon, dass ich für mich noch keinen Anwendungsfall gefunden habe, wo ich eine solche Unveränderbarkeit als notwendig erachtet hätte, will ich dem nicht komplett die Nützlichkeit absprechen. Aber ich schreibe ja meist auch Endanwender-Code und so gut wie nie für die Öffentlichkeit bestimmte Librarys.

            dedlfix.

            1. Hallo dedlfix,

              außer man sieht sich die Implementierung der Elternklasse an.

              Ja, das sollte man tun, wenn man etwas verwenden möchte.

              Da möchte ich jetzt widersprechen. Wenn die Elternklasse von fremden Autoren stammt, dann sollte ich mir die Implementierung definitiv NICHT anschauen (müssen). Nur die Schnittstellendefinition und die Dokumentation. Natürlich mag diese unvollständig sein oder unverständlich sein - viele Programmierer sind keine guten Dokumentierer. Der Grund ist: nur von Features, die dokumentiert sind, kann ich erwarten, dass sie bei Updates stabil bleiben. Oder dass eine Änderung zu einer "Breaking Change" Release Note führt.

              Wenn ich Klassen verwende, die ausdrücklich in einem internal-Namespace stehen (oder wo die Doku sagt: Intended for internal use only), dann kann ein Versionswechsel der Library dazu führen, dass diese Klassen auf einmal ganz anders ticken oder weg sind.

              Wenn die Dokumentation zu karg ist, kann ein Blick in den Sourcecode helfen, mehr zu verstehen. Da stimme ich zu. Aber undokumentierte oder interne Features von Fremd-Code sollte man nicht verwenden. Ibi sunt dracones...

              Rolf

              --
              sumpsi - posui - clusi
            2. Ich wähle für die Antwort mal eine paar sehr herbe Worte, formuliert als Angriff, Übertreibung als Stilmittel. Lass sie nicht bis zum Gemüt vordringen, das könnte sich darüber aufregen, was ich aber nicht beabsichtige. Los geht's:

              *g* darauf lasse ich mich ein.

              Wofür hältst du dich eigentlich, dass du meinst den Verwender vor etwas beschützen zu müssen? Meinst du die Verwender sind nicht gut genug im Programmieren und Code-Verstehen, die potentiellen Auswirkungen zu erkennen?

              Für einen verantwortungsbewussten Menschen. Wenn ich mit dem Wagen liegen bleibe, stelle ich auch ein Warndreieck auf. Wenn ich ein Auto baue, baue ich Sicherheitsgurte und Airbags ein.

              Warum willst du sie davon abhalten, dass sie sich ins Knie schießen, wenn sie es denn unbedingt wollen?

              Weil ich nicht glaube, dass sich jemand wirklich ins Knie schießen will. Ich bin nicht der Typ, der jemandem zum Fischen eine Schusswaffe in die Hand drückt, ich drücke ihm eine Angel in die Hand.

              Gehen wir zum Beispiel von einer Klasse A mit zwei öffentlichen Methoden aus:

              [...]

              Klasse B erbt von A und überschreibt die Methode bar:

              [...]

              Frage, zu welchem Wert evaluiert die zweite Zeile in folgendem Code?

              [...]

              Wenn A aber intern $this->bar() aufruft, dann führt das zu einer Exception.

              Wenn es denn so wichtig ist, dass foo() unbeeinflusst vom Zustand von bar() korrekt arbeitet, dann kann man das eigentliche Geschehen auch in eine private Methode auszulagern versuchen, die dann von foo() und bar() aufgerufen wird. Wenn jemand das öffentliche bar() überschreibt, kann er das tun, ohne die Abhängigkeit zu beeinflussen.

              Könnte man, das lässt sich aber nicht statisch überprüfen. Die Klasse A muss mit mehr Aufwand implementiert werden, damit beim Ableiten keine Nebenwirkungen auftreten. Und selbst wenn das gelingt, dann muss man bei der Implementierung von B immer noch in die Implementierung von Klasse A gucken, weil die Eigenschaften von Klasse A sich nicht statisch verifizieren lassen. final dagegen hat eine statische Semantik.

              Der Knackpunkt ist, dass man als Autor der Klasse B nicht wissen kann, wie sie sich verhalten wird, wenn nur eine einzige Methode überschrieben wird, außer man sieht sich die Implementierung der Elternklasse an.

              Ja, das sollte man tun, wenn man etwas verwenden möchte.

              Nö, sollte man nicht müssen. Program to an interface, not an implementation.

              Wenn es die Dokumentation nicht hergibt, notfalls mit dem Blick in die Quellen oder mit dem Decompiler. PHP selbst ist da jedoch nicht ganz so zugänglich. .NET-Code lässt sich einfacher in lesbaren C#-Code rückübersetzen. Außerdem ist da vieles sowieso im Original C# gewesen und nicht artfremdes C wie bei PHP. Aber dein Code ist ja PHP, und wenn du ihn nicht verschleiert und verschlüsselt auslieferst, hat man da ja die Chance, ihn sich anzusehen.

              Ja, die Möglichkeit besteht. Ich will es aber nicht zu einer Voraussetzung machen, in die Implementierung zu gucken, um die Schnittstelle zu nutzen.

              Zum aderen sorge ich mit final auch dafür, dass die Schnittstelle homogen genutzt wird, ich gebe dem Verwender eine klare Intention zu ihrem Gebrauch mit.

              Jein. Das ist lediglich ein einzelnes Wort. Es muss nicht immer Intention hinter einem bestimmten Gebrauch stecken. Auch "haben wir schon immer so gemacht" kann der Grund sein. Man kann da nicht immer zweifelsfrei erkennen, was dahintersteckt. Ein erklärender Text in der Dokumentation, warum das da so wichtig ist, und vielleicht auch, was die Folgen sein könnten, ist da meist viel verständlicher. Gerade mit weniger Erfahrung sieht man nur die Tatsache, dass es final oder sealed ist, weiß aber vielleicht noch so nicht recht, warum man sowas macht.

              ACK.

              Damals habe ich das Problem gelöst, indem ich mir den Code der Erweiterungen kopiert und so modifiziert habe, dass sie kompositionell zusammenarbeiteten, das war ein Aha-Moment für mich.

              Das ist schön für dich, aber für dich war es ein Erkenntnisprozess. Einfach nur ein final dastehen zu haben, setzt diesen Prozess nicht in Gang. Es wirkt eher wie ein Verbotsschild, dessen Grund man sich vielleicht erschließen kann, vielleicht aber auch nicht.

              Das ist auch richtig. Aber ich verhindere, dass NutzerInnen meines Code in diese Falle tappen.

              Abgesehen davon, dass ich für mich noch keinen Anwendungsfall gefunden habe, wo ich eine solche Unveränderbarkeit als notwendig erachtet hätte, will ich dem nicht komplett die Nützlichkeit absprechen.

              Ich sehe das umgekehrt: Wenn ich eine Klasse für Vererbung öffne, dann füge ich der Schnittstelle eine gehörige Portion Komplexität hinzu, sie gewinnt aber nicht an Ausdrucksstärke. Nur durch das Zulassen von Vererbung werden ja nicht auf magische Weise neue Anwendungsfälle abgedeckt. Wenn ich also eine Klasse schon öffne, und die gesteigerte Komplexität in Kauf nehme, dann sollte ich auch einen konkreten Anwendungsfall damit abdecken wollen. Anderenfalls, YAGNI.

              1. Tach!

                Wofür hältst du dich eigentlich, dass du meinst den Verwender vor etwas beschützen zu müssen? Meinst du die Verwender sind nicht gut genug im Programmieren und Code-Verstehen, die potentiellen Auswirkungen zu erkennen?

                Für einen verantwortungsbewussten Menschen. Wenn ich mit dem Wagen liegen bleibe, stelle ich auch ein Warndreieck auf. Wenn ich ein Auto baue, baue ich Sicherheitsgurte und Airbags ein.

                Beim Auto gab es sehr viele Erfahrungswerte, die zu diesen Sicherheitsvorschriften geführt haben. Wenn ich allein was programmiere, gibt es nur meine Erfahrung. Das Warndreieck schützt auch mein abgestelltes Auto, was man aber beim Programmieren nicht braucht, wenn jemand eine Kopie meines Codes bei sich laufen lässt. Davon bin ich nicht betroffen. Lass mal den Auto-Vergleich weg, der hilft nicht, weil das ein ganz anderes Metier ist.

                Warum willst du sie davon abhalten, dass sie sich ins Knie schießen, wenn sie es denn unbedingt wollen?

                Weil ich nicht glaube, dass sich jemand wirklich ins Knie schießen will. Ich bin nicht der Typ, der jemandem zum Fischen eine Schusswaffe in die Hand drückt, ich drücke ihm eine Angel in die Hand.

                Na und, wenn er denn unbedingt Dynamitfischen will? Die Frage ist, ob du den Verwender unerfahren einschätzt und ihn deshalb schützen möchtest. Wenn du ihn ebenbürtig ansiehst, müsste doch ein Hinweis statt einer Absperrung reichen.

                Ich sehe das umgekehrt: Wenn ich eine Klasse für Vererbung öffne, dann füge ich der Schnittstelle eine gehörige Portion Komplexität hinzu, sie gewinnt aber nicht an Ausdrucksstärke.

                Etwas, das nicht vorhanden ist, fügt Komplexität hinzu, nur weil ich eine Verhinderung weglasse? Ob jemand da was komplexes draus macht oder nicht, ist doch sein Problem.

                Nur durch das Zulassen von Vererbung werden ja nicht auf magische Weise neue Anwendungsfälle abgedeckt.

                Das nicht, aber wenn es welche gibt, die du dir nur nicht vorstellen kannst, hast du sie zumindest mal aktiv verhindert.

                Wenn ich also eine Klasse schon öffne, und die gesteigerte Komplexität in Kauf nehme, dann sollte ich auch einen konkreten Anwendungsfall damit abdecken wollen. Anderenfalls, YAGNI.

                YAGNI heißt für mich, dass man etwas nicht einzubauen braucht, für das man noch keine Verwendung absehen kann. Ich sehe darin nicht, dass man derzeit unbekannte Anwendungsfälle aktiv verhindern muss.

                Vor langer Zeit hab ich mal eine Änderung für ein PHP-Framework vorgeschlagen, weil ich da für mich einen Anwendungsfall gesehen habe, der sich mit den vorgegebenen Komponenten nicht realieren ließ. Begründet wurde die Ablehnung mit der 80/20-Regel. Für 80% der vermuteten Anwendungsfälle war Funktionalität da, die anderen 20% hat man verständlicherweise nicht einbauen wollen. Das wäre für die meisten nur Ballast. Aber muss man denn dann die Komponente so bauen, dass die unberücksichtigten 20% sich gar nicht mehr realisieren lassen? Ich wollte ja keine Funktionalität hinzugefügt haben, sondern lediglich in der Lage sein, etwas für einen Spezialfall erweitern zu können. Es musste dazu lediglich irgendeine Kleinigkeit geändert werden, sowas wie private gegen protected zu tauschen.

                dedlfix.

                1. Beim Auto gab es sehr viele Erfahrungswerte, die zu diesen Sicherheitsvorschriften geführt haben. Wenn ich allein was programmiere, gibt es nur meine Erfahrung.

                  Mit Vererbung ist es doch das selbe, auch da gibt es unzählige schlechte Erfahrungen, nicht nur meine persönlichen. Viele Software-Architekturen helfen sich mit Daumenregeln, die das schlimmste vermeiden sollen. Die lauten da etwa so: Vererbungshierarchien sollten niemals tiefer als zwei Ebenen reichen. Es sollte nur von abstraken Klassen geerbt werden. Vererbung sollte nicht über Modul-Grenzen hinaus stattfinden… Andere Programmiersprachen verzichten sogar ganz auf Vererbung.

                  Wenn du ihn ebenbürtig ansiehst, müsste doch ein Hinweis statt einer Absperrung reichen.

                  Nein, gerade weil meine NutzerInnen wertschätze, möchte ich nicht ihre Zeit und andere ökonomische Ressourcen verschwenden. Genau das mache ich aber, wenn ich von ihnen unnötigerweise verlange sich zu einer Design-Entscheidung zu positionieren, die ich schon während der Schnittstellen-Festlegung hätte treffen können. Aus dem selben Grund benutze ich auch bevorzugt statisch typsisierte Programmiersprachen. Natürlich enge ich damit die Lösungsmöglichkeiten ein, aber vor allem schließe ich kränkelnde Lösungsmöglichkeiten aus. Lösungsmöglichkeiten einschränken heißt ja im übrigen nicht automatisch, dass damit auch bestimmte Problemstellung nicht mehr lösbar wären. Der Weg zum Ziel ist lediglich besser ausgeleuchtet und Irrwege teilweise versperrt.

                  Ich sehe das umgekehrt: Wenn ich eine Klasse für Vererbung öffne, dann füge ich der Schnittstelle eine gehörige Portion Komplexität hinzu, sie gewinnt aber nicht an Ausdrucksstärke.

                  Etwas, das nicht vorhanden ist, fügt Komplexität hinzu, nur weil ich eine Verhinderung weglasse?

                  Schöner doppelter Negativ 😂 Aber ja, in meinen Augen tut es das. Maximale Flexibilität und minimale Komplexität sind Ziele, die sich nicht immer miteinander vereinbaren lassen.

                  Nur durch das Zulassen von Vererbung werden ja nicht auf magische Weise neue Anwendungsfälle abgedeckt.

                  Das nicht, aber wenn es welche gibt, die du dir nur nicht vorstellen kannst, hast du sie zumindest mal aktiv verhindert.

                  Zwei Dinge hierzu: Erstens, halte ich es für gutes Design, wenn Schnittstellen möglichst orthogonale Features anbieten und nicht mehrere Lösungswege für ein und das selbe Problem. Mehrere Lösungswege können sinnvoll sein, wenn sie verschiedene Trade-Offs ermöglichen. Zwei Lösungsmöglichkeiten mit den selben Trade-Offs, aber einer davon mit erheblichen Nachteilen, ist kein gutes Design.

                  Zweitens: Wie oben schon gesagt, bedeutet Lösungswege einzuschränken nicht zwingenderweise auch Probleminstanzen auszugrenzen. Im Falle von Subclass-Polymorphismus kann man immer auch Ad-Hoc-Polymorphimus als Supplement einsetzen, also Interfaces anstelle von Vererbungshierarchien.

                  Wenn ich also eine Klasse schon öffne, und die gesteigerte Komplexität in Kauf nehme, dann sollte ich auch einen konkreten Anwendungsfall damit abdecken wollen. Anderenfalls, YAGNI.

                  YAGNI heißt für mich, dass man etwas nicht einzubauen braucht, für das man noch keine Verwendung absehen kann. Ich sehe darin nicht, dass man derzeit unbekannte Anwendungsfälle aktiv verhindern muss.

                  Dann mal ein Gedankenspiel, angenommen PHPs Klassen wären standardmäßig immer final und um Vererbung zu ermöglichen müsste man die Klasse als inheritable deklarieren. Würdest du das dann immer tun, auch wenn du keine Verwendung dafür absehen kannst?

                  1. Tach!

                    Beim Auto gab es sehr viele Erfahrungswerte, die zu diesen Sicherheitsvorschriften geführt haben. Wenn ich allein was programmiere, gibt es nur meine Erfahrung.

                    Mit Vererbung ist es doch das selbe, auch da gibt es unzählige schlechte Erfahrungen, nicht nur meine persönlichen. Viele Software-Architekturen helfen sich mit Daumenregeln, die das schlimmste vermeiden sollen. Die lauten da etwa so: Vererbungshierarchien sollten niemals tiefer als zwei Ebenen reichen. Es sollte nur von abstraken Klassen geerbt werden. Vererbung sollte nicht über Modul-Grenzen hinaus stattfinden… Andere Programmiersprachen verzichten sogar ganz auf Vererbung.

                    Das ist soweit Erfahrung aus Vergleichssituationen, die Empfehlungen darstellen. Für das konkrete Produkt dass es zu erstellen gibt, gibt es aber noch keine Erfahrung, wie das konkret verwendet werden wird. Du machst das aus Best-Practice-Gründen, und nicht weil du mit deinem Programm und dessen Verwendungswünschen bereits Erfahrung gesammelt hast.

                    Zweitens: Wie oben schon gesagt, bedeutet Lösungswege einzuschränken nicht zwingenderweise auch Probleminstanzen auszugrenzen. Im Falle von Subclass-Polymorphismus kann man immer auch Ad-Hoc-Polymorphimus als Supplement einsetzen, also Interfaces anstelle von Vererbungshierarchien.

                    Dann muss mal alles selbst schreiben, was in einer anderen Klasse bereits vorhanden ist und nur etwas angepasst werden müsste. Interfaces helfen bei solche einem Fall nicht viel.

                    Dann mal ein Gedankenspiel, angenommen PHPs Klassen wären standardmäßig immer final und um Vererbung zu ermöglichen müsste man die Klasse als inheritable deklarieren. Würdest du das dann immer tun, auch wenn du keine Verwendung dafür absehen kannst?

                    Du kannst das Argument auch mit einem bereits vorhandenen Szenario bringen. Um in C# Methoden überschreiben zu können, müssen diese als virtual gekennzeichnet sein, sonst gehts nicht. Und nein, ich setze da auch nicht immer vorbeugend virtuals davor. Aber in meinem Fall stört das nicht weiter, weil ich das bei Bedarf anpassen kann. Wie ich das allerdings handhaben würde, wenn ich eine Bibliothek zur freien Verwendung schriebe, kann ich nicht vorhersagen.

                    C# hat auch das wunderbare Feature der Extension Methods, da stört es mich auch nicht, wenn du eine Klasse abschließt, ich kann trotzdem Methoden definieren, die sich so anfühlen, als ob sie zur Klasse selbst gehörten.

                    dedlfix.

              2. Tach!

                Für einen verantwortungsbewussten Menschen. Wenn ich mit dem Wagen liegen bleibe, stelle ich auch ein Warndreieck auf. Wenn ich ein Auto baue, baue ich Sicherheitsgurte und Airbags ein.

                Mittlerweile ist mir eingefallen, warum dieser Vergleich unfair ist. Sicherheitsgurte und Airbags sind Sicherheitsfeatures und (vor allem deswegen) auch gesetzlich vorgeschrieben. Für final trifft das nicht zu. Das ist lediglich eine Designentscheidung. Wenn man es mit dem Auto vergleichen wollte, müsste man da wohl sowas wie den Getränkehalter oder den Zigarettenanzünder nehmen und als Fall irgendwas nehmen, bei dem andere Dinge als Getränke abgelegt werden sollen und der Strom zum damals nicht vorgesehenen Betreiben von anderen Geräten. Mit final würden solche Anwendungsfälle verhindert werden. Und da magst du noch so sehr darauf bestehen, dass für Taschentücher andere Ablagen existieren, wenn ich das als Anwender in den Getränkehalter stecken möchte, warum soll mir das verwehrt werden? Selbst wenn das abzulegende Ding nicht reinpasst, umfällt und ausläuft, ist das mein Problem, vor dem du mich nicht zwingend schützen musst. Auch wenn der Laptop die Autobatterie leerzieht, was beim bestimmungsgemäßen Gebrauch der Dose als Zigarettenanzünder sehr unwahrscheinlich gewesen wäre, ist das auch nett gemeint, andere Anwendungen zu verhindern. Mittlerweile sind sie ja zu Bordspannungssteckdosen umgewidmet, früher waren sie oftmals ganz klar zweckentsprechend designet (z.B. verdeckt im Aschenbecher integriert) und für andere Anwendungsfälle umständlich nutzbar. Aber sie haben andere Anwendungen gefunden, die nicht unlegitim sind.

                dedlfix.

    5. Also um nochmal auf die Factory zurückzukommen: Das wäre auch eine Möglichkeit Objekte zu aggregieren. Heißt daß der Konstruktor von Class Proof zunächst gar keine Daten aus DB liest.

      Erst wenn die Proofinstanz $pro erstellt wird, ruft $pro eine Methode data($id) und in dieser Methode wird dann gebrüft ob es in $this eine Instanz der Klasse DB gibt die für die Datenbeschaffung zuständig ist. Wenn nicht wird sie erstellt.

      Auch eine Möglichkeit, Schnittstellen sauber zu definieren. Hab geshen ihr seid mittlerweile bei .net und c# angekommen? Da kann ich nicht mitreden aber die Vorgehensweise ist ja dieselbe. GG

      PS: Perl tie() wäre hier auch noch interessant. Da werden die Daten erst in die Instanz geholt wenn darauf zugegriffen werden soll.

      1. Tach!

        Also um nochmal auf die Factory zurückzukommen: Das wäre auch eine Möglichkeit Objekte zu aggregieren. Heißt daß der Konstruktor von Class Proof zunächst gar keine Daten aus DB liest.

        Das was du letztlich erreichen möchtest, ist das, was wir mit dem Repository erreichen wollen. Es holt einen angefragten Datensatz aus dem DBMS und erstellt eine POxO-Klasse, die es mit den Daten befüllt und gibt diese zurück. Wenn du so willst, ist das eine Art Factory.

        Erst wenn die Proofinstanz $pro erstellt wird, ruft $pro eine Methode data($id) und in dieser Methode wird dann gebrüft ob es in $this eine Instanz der Klasse DB gibt die für die Datenbeschaffung zuständig ist. Wenn nicht wird sie erstellt.

        Das entspräche nicht der DI-Philosophie, dass sich jemand einen Service selbst instantiiert, anstatt reingereicht zu bekommen. Und es entspräche auch nicht dem POxO-Prinzip, dass die Datenklasse mit artfremden Dingen wie Persistierung angereichert wird.

        Wir versuchen die Dinge nach Aufgabenbereich zu trennen. Danach hat die fachliche Datenklasse nur ihre fachliche Aufgabe zu erfüllen. Nichtfachliche Nebentätigkeiten wie Persistierung werden von anderen Komponenten des Systems ausgeführt.

        PS: Perl tie() wäre hier auch noch interessant.

        Das finde ich nicht. Ich hab zwar nicht komplett verstanden, was das tie() macht (ist mir auch egal), aber zur Laufzeit Dinge zusammenzubasteln, ist nicht der Weg, den man in PHP (oder .NET) geht.

        dedlfix.

        1. Ich hab zwar nicht komplett verstanden, was das tie() macht (ist mir auch egal),

          tie() bindet eine Variable oder einen abstrakten Datentyp an eine Klasse. Beispiel:

          tie %user, "User", id => 123;
          $user{name} = "Name"; 
          untie %user;
          

          Eine moderne Art der Datenabstraktion, Layer zu trennen und saubere Schnittstellen zu definieren!