MB: Wie Properties aus externen Klassen in Trait bekannt machen?

moin,

ich hab ein kleines Problemchen: ich habe zum training ein CRUD SQL-Klassen angefangen und mit Traits gearbeitet. Zur laufzeit passieren keine Fehler. Ein Code-Block in einer Klasse kann sich ja über Traits, mit use Referenz zur übergeordneten Klasse, auslagern lassen. Ich hab jedoch keine möglichkeit, in diesem Trait, Properties, die ja von einer externen Klasse stammen, irgend wie bekannt zu machen und eine Refferenz zur externen Klasse zu definieren. Klappt zwar auch so aber in meinem weiteren Projekt verlauf ist es sinnvoll irgend wie eine Kopplung hervorzubringen.

Natürlich könnte ich das alles zusammengefasst in eine Klasse schreiben, jedoch verliere ich den Überblick und das ist kein gute Stil und mir ist bewusst, das die eigentliche Funktionsweise von Traits ein wenig von meinem Code abweicht aber nur so schien es mir strukturierter.

Ein Code ausschnitt von meinen Klassen:

class SQLSyntax {

  /**
   * @const int
   */
  const WHERE = // Wert
}
class SQL {
  
  /* Traits */
  use SQLClause;
  use SQLPredicat;

  /**
   * @var string
   */
  private $statement;

  /**
   * @var array
   */
  private $params;

  /**
   * @var array
   */
  private $stack;

  public function execute() : void {
    if( $this->hasOrder() ) {
      // execute
    } else {
      throw new SQLException;
    }
  }

  private function hasOrder() : bool {
    if( /* syntax conditions */ ) {
      return false;
    } else {
      return true;
    }
  }
}
trait SQLPredicat {
  
  // other predicat functions

  public function like( string $col, $pattern ) : string {
    $this->params[] = $pattern;
    return // LIKE Prädikat rückgabe
  }
  
  // other predicat functions

}
trait SQLClause {

  // other clause functions

  public function where( array $conditions ) : SQL {
    $this->stack[] = SQLSyntax::WHERE;
    $this->statement .= "WHERE ";
    // foreach and other stuff
       $this->params[] = $value;
    return $this;
  }
  
  // other clause functions

}

lgmb

  1. Hallo MB,

    Traits sind dazu da, in beliebige Klassen eingemixt zu werden. Darum sollen sie über ihren Nutzer nichts wissen.

    Dieses Wissen kannst Du konkretisieren, wenn der Trait eine abstrakte Methode definiert.

    trait Logger {
       abstract function GetPrefix();
       function Log($message) {
          echo GetPrefix() . $message;
       }
    }
    

    Eine Klasse, die den Logger-Trait einbindet, muss eine Methode GetPrefix implementieren und kann damit z.B. ihren Namen als Prefix für Lognachrichten liefern (ja, ich weiß, mit get_class ginge das auch).

    Was technisch geht, aber aus Sicht von Software Engineering keine gute Idee ist, ist das Verwenden von $this. Der Trait gilt als Teil der Klasse und kann deshalb auf allem herumturnen, was in $this zu finden ist, private oder nicht, egal ob aus dem Trait stammend oder der Hostklasse. Das würde ich aber als OOS[1] bezeichnen. Traits sind dazu gedacht, entkoppelt und überall nutzbar zu sein. Dass die Host-Klasse ein paar abstrakte Methoden bereitstellen muss, das geht noch, das ist auch in der Sprache so vorgesehen. Aber ein Trait sollte keine unterschwelligen Annahmen treffen, dass die Hostklasse bestimmte Eigenschaften oder Methoden besitzt.

    Versuche mal, ob Du mit abstract functions etwas erreichen kannst. Andernfalls könnte es sein, dass Traits nicht das richtige Werkzeug sind. Statt dessen könntest Du Worker-Objekte einsetzen, die von der Hostklasse erzeugt werden und deren Konstruktor ein Interface erwartet, das die Hostklasse implementiert (übergib nicht das $this der Hostklasse. Dann droht wieder OOS).

    Rolf

    --
    sumpsi - posui - clusi

    1. Objektorientierter Spaghetticode ↩︎

    1. moin,

      Traits sind dazu da, in beliebige Klassen eingemixt zu werden. Darum sollen sie über ihren Nutzer nichts wissen.

      ich weis 😕

      Dieses Wissen kannst Du konkretisieren, wenn der Trait eine abstrakte Methode definiert.

      ach stimmt! Das ist mir garnicht eingefallen! Besten Dank!

      Was technisch geht, aber aus Sicht von Software Engineering keine gute Idee ist, ist das Verwenden von $this.

      Das habe ich leidvoll erfahren.

      Der Trait gilt als Teil der Klasse und kann deshalb auf allem herumturnen, was in $this zu finden ist, private oder nicht, egal ob aus dem Trait stammend oder der Hostklasse.

      jepp

      [...]. Aber ein Trait sollte keine unterschwelligen Annahmen treffen, dass die Hostklasse bestimmte Eigenschaften oder Methoden besitzt.

      das ist genau mein ziel.

      Versuche mal, ob Du mit abstract functions etwas erreichen kannst.

      werde ich machen. hab einen sehr konkreten plan. Danke nochmals😀!!!

      lgmb

    2. moin,

      ok. Hat super geklappt!

      class SQL {
        
        /* Traits */
        use SQLClause;
      
        /**
         * @var string
         */
        private $statement;
      
        /**
         * @var array
         */
        private $params;
      
        /**
         * @var array
         */
        private $stack;
      
          
        /**
         * @params string
         */
        public function addStatement( string $piece ) : void {
            $this->statement .= $piece;
        }
        
        /**
         * @params string
         */
        public function addParams( string $value ) : void {
            $this->params[] = $value;
        }
        
        /**
         * @params string
         */
        public function addStack( string $chain ) : void {
            $this->stack[] = $chain;
        }
      
      trait SQLClause {
      
        // other clause functions
      
        public function where( array $conditions ) : SQL {
          $this->addStack( SQLSyntax::WHERE );
          $this->addStatement( "WHERE " );
          // foreach and other stuff
             $this->addParams( $value );
          return $this;
        }
        
        // other clause functions
      
      }
      

      jedoch sieht es mit den nicht initialisierten (oder Leer String) Properties der externen Klasse SQL Übel aus, die noch durch den Konstruktor initialisiert werden müssen.

      ich habe da noch eine Methode in der externen Klasse SQL geschrieben die von einem Trait SQLClause erwartet wird. Da bin ich auf den Fehler gestoßen.

      /**
       * @var array $scheme
       */
      private $scheme;
      
      // …
      public function __constructor( string $scheme ) {
          $this->scheme = $scheme;
      }
      /**
       * @params string
       */
      public function getScheme() : string {
          return $this->scheme;
      }
      

      in der SQLClause::from( string $table ) : SQL wird dann zusätzlich noch durch $this->getScheme() ein Datenbank Schema vor der Tabelle hin gesetzt

      $this->addStatement( "\nFROM `" .$this->getScheme(). ".{$table}` " ) : SQL;
      

      Er sag mir immer wieder…

      Return value of SQL::getScheme() must be of the type string, null returned
      

      Ich hab ne leihenhafte Vermutung das die Instanziierung der externen Klasse SQL vorab die Trait SQLClause einbindet und SQLClause die SQL Properties in der Methopde blank verwendet bevor eine instanziierung der Properties im SQL Konstruktor vollzogen ist. Das habe ich mit einem String in einem Property getestet private $scheme = 'firma'; und würde FROM 'firma.kunde' enstehen und es funzt. Aber meine vermutung ist noch nicht ausgereift und eben eine leihenhafte. Ich hoffe sehr das es verständlich ist.

      lgmb

    3. moin,

      ich Dummkopf hab doch tatsächlich dir ein weiteres Problem geschilder und unbewusst die Lösung gepostet. Ich hab die Magische Methode unabsichtlich __construct() in __constructor() umbenannt. Naja, das wäre auch gegessen. Schönen Abend euch.

      lgmb

  2. Tach!

    Natürlich könnte ich das alles zusammengefasst in eine Klasse schreiben, jedoch verliere ich den Überblick und das ist kein gute Stil und mir ist bewusst, das die eigentliche Funktionsweise von Traits ein wenig von meinem Code abweicht aber nur so schien es mir strukturierter.

    Wenn Übersichtlichkeit dein eigentlicher Grund ist, dann solltest du lieber darüber nachdenken, wie man die Aufgaben kleiner und abgeschlossener formuliert, um sie jeweils eigenen Klassen zuweisen zu können. Sie in Traits auszulagern, um alles dann wieder zu einer großen Klasse zusammenzufügen, macht die Sachlage nicht besser. Traits sind nicht die dafür vorgesehene Lösung.

    dedlfix.

    1. Genau so sehe ich das auch. Von daher baue ich SQL KLassen der Verwendung entsprechend als Dataaccess-Layer. Da ist dann alles drin, vom create-statement bis zum drop und natürlich der ganze CRUD Komplex: Alles auf eigene Methoden abgebildet. Beim Anwenden einer solchen API wird SQL nach außen, also in der Anwendung selbst, gar nicht mehr sichtbar. MFG

      1. moin,

        Genau so sehe ich das auch. […]

        s.o. 😉

        lgmb

        1. Und zum Vermeiden von CODE Redundanzen hätte ich noch eine Universalfunktion anzubieten. Der Aufruf sieht so aus:

          use strict;
          use warnings;
          use insert;
          use dbh;
          
          my $mock = bless{};             # Attrappe
          my $dbh = $mock->dbh('myweb');  # Verbindung mit DB by Name
          $mock->insert($dbh, 'log', url => "/index.html", hugo => 'asdf');
          

          ALso eine universelle Insert-Methode für Instanzen beliebiger Klassen (mock). Übergeben wird die Instanz, die DB-Verbindung, der Tabellenname und ein assoziatives Array mit col=>value Pairs.

          Solch eine Universalfunktion ist nicht nur auf Perl beschränkt. Falls es eine auto_increment Spalte gibt, ist Last_Insert_Id() der Return Value. Fehlerbehandlung per Exception.

          Nur als Idee. MFG

    2. moin,

      Wenn Übersichtlichkeit dein eigentlicher Grund ist, dann solltest du lieber darüber nachdenken, wie man die Aufgaben kleiner und abgeschlossener formuliert, um sie jeweils eigenen Klassen zuweisen zu können.

      ok. was könnte ich deine und @pl Meinung nach tun um Mein ziel zu erreichen?

      Sie in Traits auszulagern, um alles dann wieder zu einer großen Klasse zusammenzufügen, macht die Sachlage nicht besser. Traits sind nicht die dafür vorgesehene Lösung.

      Wenn ich alles zusammen in eine Ding verfrachte treten die genannten Probleme auf die ich erwähnte und es sieht sehr monolytisch aus. Was sind den Probleme bei der Traits eine von viele Lösungen sind?

      lgmb

      1. Tach!

        ok. was könnte ich deine und @pl Meinung nach tun um Mein ziel zu erreichen?

        Ich weiß nicht, was dein eigentliches Ziel ist. Die benötigte Funktionalität muss den Weg weisen.

        Sie in Traits auszulagern, um alles dann wieder zu einer großen Klasse zusammenzufügen, macht die Sachlage nicht besser. Traits sind nicht die dafür vorgesehene Lösung.

        Wenn ich alles zusammen in eine Ding verfrachte treten die genannten Probleme auf die ich erwähnte und es sieht sehr monolytisch aus. Was sind den Probleme bei der Traits eine von viele Lösungen sind?

        Ein Beispiel hat RolfB schon genannt, Logging. Es ist das Hinzumixen von Funktionalität, nicht das Zusammenbauen der eigentlichen Klasse. Sagen wir, eine Klasse hat eine bestimmte Aufgabe. Was das konkret ist, spielt keine Rolle. Man möchte aber, dass die Klasse Debuginformationen liefern kann. Und nicht nur diese Klasse, sondern das soll generell möglich sein. Dazu kann man sich zunächst ein Interface definieren, das die benötigte Funktion beschreibt:

        interface Debugable {
          public function toDebug(): string;
        }
        

        Eine Klasse, die eine Debugausgabe ihrer selbst erstellen können soll, muss das Interface implementieren. Nun kann es sein, dass einige Klassen eine spezielle Ausgabe erzeugen sollen, bei anderen hingegen ist es immer derselbe Vorgang. Dessen Code möchte man nun nicht in jede Klasse kopieren, sondern nur einmal schreiben. Vererbung über eine Basisklasse wäre eine Möglichkeit, aber keine besonders gute. Die Basisklasse wird so zu einer Gott-Klasse und viele Klassen hängen aufgrund dieser Nebenfunktionalität mit dieser Basisklasse zusammen, auch wenn sie sonst keine gemeinsamen Merkmale haben. Mehrfachvererbung geht in PHP auch nicht. Wenn man weitere Basisfunktionalität haben möchte, muss man das so organisieren, dass es eine Kette ergibt: A->B->C. Wenn C zwar A und B braucht, aber A weder B braucht noch umgekehrt, muss B hier trotzdem die Dinge von A erben. Hier kann ein Trait helfen, der A enthält, in obigem Beispiel also die generische Implementation der Debugausgabe. Es ergibt sich keine verkettete Abhängigkeit, sondern der Trait kommt sozusagen von der Seite rein, während der Weg für eine Vererbung anhand der Notwendigkeiten der Hauptfunktionalität frei bleibt.

        Unabhängig von obigem Beispiel bleibt die Frage, ob Vererbung oder auch Traits überhaupt für einen bestimmten Anwendungsfall sinnvoll sind, oder ob sich nicht vielleicht Wege finden, die Verbindungen nicht so starr zu gestalten. Eine Firma muss auch nicht für sämtliche Gewerke Mitarbeiter einstellen, sondern kann externe Dienstleister mit artfremden Aufgaben betrauen, aber auch als Zulieferer oder zum Ausführen bestimmter Zwischenschritte des Produktionsprozesses. Ähnlich kann man auch Code gestalten, und Aufgaben an Services delegieren. Services erledigen eine abgrenzbare Aufgabe. Der Vorteil daran ist, dass man diese Teilaufgabe auch separat entwickeln und testen kann, ohne viele Abhängigkeiten mit einer Monsterklasse pflegen zu müssen.

        dedlfix.

        1. moin,

          sehr schön erklärt!

          Auf dieses Wissen habe ich ja eine SQL Klasse mit Traits entwickelt um die Stolpersteine zu umgehen. Oder habe ich da was nicht richtig verstanden???

          Auf das SQL Klassen Konstrukt setzt ja eine Art DAL auf. Bin noch am rum tüfteln. Zuviel des guten würde man jetzt richtiger weise sagen aber es ist doch nur für mein Training.

          lgmb

          1. Tach!

            Auf dieses Wissen habe ich ja eine SQL Klasse mit Traits entwickelt um die Stolpersteine zu umgehen. Oder habe ich da was nicht richtig verstanden???

            Der wesentliche Punkt an der Geschichte war der letzte Absatz, also die Frage nach dem Sinn. Es geht bei Traits nicht darum, die Hauptfunktionalität einer Klasse aufzuteilen, um sie mit anderen Mitteln wieder zusammenzusetzen. Das kann man machen, das wird auch funktionieren, aber das ist am Ende nicht wirklich ein Fortschritt. Genausogut könntest du einfach in einer Monsterklasse mit Code-Folding im Editor die Einzelteile ausblenden. Es bleibt trotzdem ein Monster, auch wenn nur noch wenige Zeilen sichtbar sind.

            Auf das SQL Klassen Konstrukt setzt ja eine Art DAL auf. Bin noch am rum tüfteln. Zuviel des guten würde man jetzt richtiger weise sagen aber es ist doch nur für mein Training.

            Vor allem muss man sich genau anschauen, was am Ende entsteht, und was für eine Vorgehensweise damit ersetzt worden ist. Ein Generator, der SQL-Statements erzeugt, ersetzt im Wesentlichen die Syntax von SQL mit einer anderen. Da muss man schon eine gute Begründung haben, warum das besser als SQL sein soll.

            dedlfix.

            1. moin,

              Code:

              $sql
                ->select()
                ->from( 'foo' )
                ->join( SQL::INNER, 'bar' )
                ->where( [
                  $sql->between( 'quz', 5, 15 )
                ])
                ->execute( 5 );
              

              Ausgabe:

              SELECT *
              FROM `firma.foo`
              INNER JOIN `firma.bar`
                ON `firma.foo`.id = `firma.bar`.id
              WHERE
                `firma.foo`.quz BETWEEN 5 AND 15
              
              

              soo sähe das dann aus. Aber sind noch problmchen wo ich noch zu arbeiten habe. "Lerning by Doing" und ab und zu die Profis in SELFHTML fragen.

              Fehler mache mein Progrämmchen wenn die SQL Chain-Notation vertauschen.

              lgmb

              1. Tach!

                soo sähe das dann aus. Aber sind noch problmchen wo ich noch zu arbeiten habe.

                Wenn du dazu eine Monsterklasse erstellst, hast du mehr als nur Problemchen - früher oder später. Wenn du das so umsetzen möchtest, halte dich an RolfBs Vorschlag. Separier das in einzelne Klassen. Traits sind dafür nicht notwendig.

                dedlfix.

                1. moin,

                  soo sähe das dann aus. Aber sind noch problmchen wo ich noch zu arbeiten habe.

                  Wenn du dazu eine Monsterklasse erstellst, hast du mehr als nur Problemchen - früher oder später.

                  Danke für den Hinweis. Ich denke ich werde wirklich ersteinmal eine Monster Klasse aufbauen weil ich die Komplexität so ohne weiteres nicht erfassen kann. Danke für dafür! Ist mir hilfreich

                  lgmb

  3. Die Frage ist: Was ist das Ziel? Eine universal SQL KLasse womit man Statements per Eigenschaften und Methoden und Traits zusammenbaut? MFG

    1. moin,

      Die Frage ist: Was ist das Ziel? Eine universal SQL KLasse womit man Statements per Eigenschaften und Methoden und Traits zusammenbaut? MFG

      das ist von mir vorgesehen ja.

      lgmb

      1. Hallo MB,

        bisher habe ich mit deiner eigentlichen Aufgabe noch nicht befasst.

        Aber ich habe schon ähnliche SQL Wrapper geschrieben, in PHP und auch in C#. Du musst berücksichtigen, dass Du mit solchen Wrappern fast immer weniger Möglichkeiten hast als mit direkt formuliertem SQL. Der Sinn solcher Wrapper ist es, Datenbankabfragen unabhängig von einer konkreten Datenbank zu formulieren. D.h. du baust, wenn das zielführend sein soll, nicht nur einen Wrapper, sondern eine Wrapper-Familie, mit SQL-Generatoren für verschiedene Datenbanken.

        Also eigentlich nichts anderes als ein ORM Tool, was es schon reichlich fertig gibt. Es selbst zu schreiben kann zwei Gründe haben: man möchte lernen, oder die fertigen Tools sind einem zu schwergewichtig.

        Ich denke, dass Dedlfix recht hat. Wenn Du einen SQL Generator haben willst, brauchst Du keine Traits. Sondern Klassen, die du zu einem Statement-Tree zusammensetzt. So ein Tree kann so aussehen:

        SELECT-Statement
           - ResultExpressionCollection
              - { ResultExpression }
           - SourceTableCollection
              - { SourceTable }
           - FilterCollection
              - ?
           - GroupExpressionCollection
              - { GroupExpression }
           - GroupFilterCollection
              - { GroupFilter }
           - OrderExpressionCollection
              - { OrderExpression }
           - Limit
        

        Das ist überhaupt nicht trivial, eine ResultExpression kann einfach eine Referenz auf eine Spalte sein, aber auch ein Subselect. Eine SourceTable kann eine Tabelle sein, oder ein Subselect, oder ein Join. Die FilterCollection (WHERE) ist ein Alptraum, wenn Du beliebige Bedingungen zulassen willst. Vermutlich brauchst Du beliebig zusammenstöpselbare AND- und OR-Gruppen, in denen Bedingungen stecken. Eine Bedingung ist ein LIKE, ein IN, ein EXIST, oder =. Die Werte für Bedingungen sind einfache Werte (Spalten, Konstanten), mathematische Ausdrücke, oder Subselects. Wie soll sowas dann am Ende benutzt werden?

        $sql = new SqlSelect();
        $sql.ResultColumns.Add(new ColumnReference("foo", "a"));
        $sql.ResultColumns.Add(new ColumnReference("bar", "a"));
        $sql.ResultColumns.Add(new ColumnReference("name", "b"));
        
        $j1 = new InnerJoin();
        $j1.Tables.Add(new TableReference("table1", "a"));
        $j1.Tables.Add(new TableReference("table2", "b"));
        $j1.JoinCondition = new Comparison("=",
                                           new ColumnReference("id", "a"),
                                           new ColumnReference("id", "b"));
        
        $sql.Tables.Add(new TableReference($j1, "x"));
        
        $sql.Filters.Add(new Comparison(">", new ColumnReference("foo", a),
                                             new ConstantValue("47"));
        
        $sql.GetStatement();
        // SELECT a.foo, a.bar, b.name
        // FROM (table1 a JOIN table2 b ON (a.id = b.id)) x
        // WHERE a.foo > 47
        

        Vermutlich müssten noch ein paar Klammern mehr generiert werden, um auf Nummer Sicher zu gehen. Durch Convenience-Varianten der Add-Methoden könnte man auch Schreibarbeit verringern, also z.B.
        $sql.ResultColumns.AddColumn("foo", "a");.

        De facto wäre das aber die Aufgabenstellung: Konstruktion einer Baumstruktur für eine SQL Abfrage, aus der dann das SQL zusammengesetzt wird. Unter Berücksichtigung von Kleinigkeiten wie der Unterschied zwischen LIMIT in MYSQL, das am Ende steht, und TOP in MS-SQL, das gleich hinter SELECT steht.

        Wäre das deine Vorstellung? Ich will jetzt (noch) nicht mit dem ganzen Forum diskutieren, ob diese Vorstellung gut oder schlecht ist. Ich möchte Dir, MB, erstmal nur spiegeln, was ich glaube, verstanden zu haben, und deine Rückmeldung dazu.

        Rolf

        --
        sumpsi - posui - clusi

        Folgende Beiträge verweisen auf diesen Beitrag:

        1. moin,

          [...]. Du musst berücksichtigen, dass Du mit solchen Wrappern fast immer weniger Möglichkeiten hast als mit direkt formuliertem SQL. […]

          Ist mir bewusst. Ich will jedoch die SQL Konstrukte so schreiben, das sie alles abdecken und nicht proprietär sind. Klar es frisst zwar Resourcen, aber dafür hat man eine sehr saubere SQL Anweisung. Und das ist genau mein Ziel!

          […] Der Sinn solcher Wrapper ist es, Datenbankabfragen unabhängig von einer konkreten Datenbank zu formulieren.

          […]. Es selbst zu schreiben kann zwei Gründe haben: man möchte lernen, oder die fertigen Tools sind einem zu schwergewichtig. [...]

          Beides ist der Zweck

          Ich denke, dass Dedlfix recht hat. Wenn Du einen SQL Generator haben willst, brauchst Du keine Traits.

          Als ich mir @dedlfix Argumente duchgelesen habe schein es mir sinnvoll zu sein. Aber ich will mir ja keine Mörder Aufgabe stellen.

          […], aber auch ein Subselect.

          Ich beschränke den möglichen SQL Spagetti Code, so hat man mehrere ab trennbare Statements. Insofern denke ich wäre es sinnvoller eine Klasse zu schreiben die mit Traits arbeitet.

          Wäre das deine Vorstellung?

          Ja weniger komplex.

          […]. Ich möchte Dir, MB, erstmal nur spiegeln, was ich glaube, verstanden zu haben[…]

          Herzlichen Dank für die Rüchmeldunganfrage. Wie gesagt meine Intension hast du erfasst, jedoch trivialer. Danke!

          lgmb

          1. Hallo MB,

            Ich beschränke den möglichen SQL Spagetti Code, so hat man mehrere ab trennbare Statements

            Was ist SQL Spaghetti? Eine komplexe Beschreibung der Daten, die Du haben willst? Wenn das die Daten sind, die Du brauchst, dann sind sie es. SQL ist eine WAS-Sprache, keine WIE-Sprache. Spaghetti ist eigenlich eine Eigenschaft von Konstrukten in WIE-Sprachen (z.B. PHP).

            Wenn Du deine Zugriffe gerne strukturierter haben möchtest, und eine großen Zugriff in mehrere kleine aufteilst, kann sich das unangenehm auswirken. Wenn der SQL Server auf der gleichen Maschine läuft wie PHP, und wenn die Last im Web gering ist, dann fällt es nicht auf, aber in einer größeren, heftig unter Dampf stehenden Web App sieht das anders aus. Da hat man mehrere technische Schichten: Ein Cluster aus Webservern (HTTP Requeste verstehen und senden), ein Cluster aus App Servern (empfangene Daten verarbeiten und Ergebnis erzeugen), und darunter ein Cluster aus SQL Servern für die Datenhaltung. In einem solchen Umfeld gilt das "chunky, not chatty" Prinzip: möglichst wenige Server-Roundtrips. Weil jeder Hop kostbare Millisekunden frisst.

            Ein Programm, das zunächst eine Tabelle XY ausliest, und dann pro Zeile eine weitere SQL Abfrage macht um weitere Daten dazuzuholen, ist in einem solchen Szenario unakzeptabel langsam. Aber du kannst deinem Generator ja die Möglichkeit einbauen, beispielsweise Subselects an den gewünschten Stellen zu injizieren.

            Rolf

            --
            sumpsi - posui - clusi
            1. moin,

              Was ist SQL Spaghetti?

              Vermutlich ein Neologismus angelehnt an den Spagetti Code. Selects, joins, subselects, subsubselects alles in einem Statement.

              Wenn Du deine Zugriffe gerne strukturierter haben möchtest, und […]. Weil jeder Hop kostbare Millisekunden frisst.

              Ist mir bewusst durch euch. Danke für den Hinweis.

              Ein Programm, das zunächst eine Tabelle XY ausliest, und dann pro Zeile eine weitere SQL Abfrage macht um weitere Daten dazuzuholen, ist in einem solchen Szenario unakzeptabel langsam.

              verstehe. Wie ich grade bei @dedlfix erwähnte, werde ich solche treffend "unakzeptabel langsame" monolytische Klasse bauen 😂.

              Aber du kannst deinem Generator ja die Möglichkeit einbauen, beispielsweise Subselects an den gewünschten Stellen zu injizieren.

              Da bin ich noh zu frisch. Ich tu mich schwer einen Generator zu bauen. Erst einmal "Das Rad neu erfinden". Danke nochmals für den Wegweiser @Rolf B , @dedlfix .

              lgmb

              1. Hallo MB,

                monolytische

                Das Suffix -lyse oder -lytisch stammt vom altgriechischen Suffix -lúō, lösen, zerlegen, etc.

                Monolytisch wäre also einfach zerlegt, einzeln gelöst, oder sowas. Aber eigentlich gibt's das Wort gar nicht 😀 (frag duden.de). Es gibt Google-Treffer dazu, aber die meisten sind die üblichen Google-Trolle, die auf jedes Wort einen Treffer liefern, um Dir ihren Cookie zu verpassen. Und tatsächlich ein paar, die das Wort real verwenden. Die

                Du meinst monolithisch, von lithos, Stein - aus einem Stein (=Stück) gemacht

                Rolf

                --
                sumpsi - posui - clusi
                1. moin,

                  Du meinst monolithisch, von lithos, Stein - aus einem Stein (=Stück) gemacht

                  meinte ich doch 😂.

                  lgmb

        2. Ich will jetzt (noch) nicht mit dem ganzen Forum diskutieren, ob diese Vorstellung gut oder schlecht ist.

          Tja :P Ich hoffe du lässt dich trotzdem auf mich ein.

          Der Sinn solcher Wrapper ist es, Datenbankabfragen unabhängig von einer konkreten Datenbank zu formulieren.

          Der Vorteil ergibt sich, wenn man mit verschiedenen Datenbank-Management-Systemenen zu arbeiten hat. Dann kann man nämlich das Wissen, das man in einem Projekt über die Schnittstelle erworben hat, in anderen Kontexten wiederverwenden. Dafür würde sich aber PHPs eigene PDO-Schnittstelle schon qualifiezieren, die abstrahiert ja ebenfalls schon von konkreten DBMS.

          Es gibt darüber hinaus auch direkte Vorteile bei der Arbeit in einem konkreten Projekt. PDO und mysqli arbeiten auf einer sehr primitiven Ebene, SQL-Statements werden durch Strings repräsentiert. Das kann schnell ins Auge gehen, etwa wenn ein String einen SQL-Syntax-Fehler enthält oder wenn das Statement nicht mit dem Datenbank-Schema kompatibel ist. Das erste Problem ist heute durch intelligente Editoren größtenteils gelöst. Das zweite Problem ist schwieriger, dafür ist eine präzisere Repräsentation für das Statement und für das Datenbank-Schema notwendig.

          Also eigentlich nichts anderes als ein ORM Tool, was es schon reichlich fertig gibt.

          Ein ORM ist nochmal eine Abstraktions-Ebene darüber. Für mich ist das defnierende Charaktermerkmal, dass ein ORM Domäne-getrieben arbeitet: Die Programmiererin arbeitet ausschließlich mit ihrem Domain-Model und ruft das ORM nur an, wenn sie die flüchtige Repräsentation im Hauptspeicher mit der persistenten Datenhaltung synchronisieren möchte. Intern generiert das ORM das dafür notwendige Datenbank-Schema und die notwendigen Querys. Das ist zumindest die ideale Sicht, die ich auf ein ORM habe. In der Praxis ist die Trennung oft nicht so glasklar.

          Das System von MB arbeitet dagegen Datenbank-getrieben, er geht von keinem Domain-Modell aus, sondern möchte es dem Programmierer erleichtern, beliebige Datenbank-Abfragen zu verfassen.

          Das ist überhaupt nicht trivial, eine ResultExpression kann einfach eine Referenz auf eine Spalte sein, aber auch ein Subselect. Eine SourceTable kann eine Tabelle sein, oder ein Subselect, oder ein Join. Die FilterCollection (WHERE) ist ein Alptraum, wenn Du beliebige Bedingungen zulassen willst. Vermutlich brauchst Du beliebig zusammenstöpselbare AND- und OR-Gruppen, in denen Bedingungen stecken. Eine Bedingung ist ein LIKE, ein IN, ein EXIST, oder =. Die Werte für Bedingungen sind einfache Werte (Spalten, Konstanten), mathematische Ausdrücke, oder Subselects.

          Das ist im Grunde genommen nicht besonders schwierig, nur sau viel Arbeit, weil die SQL so umfangreich ist. Man kann sich von der SQL-Grammatik leiten lassen und einen Datentypen für den abstrakten Syntax-Baum erstellen. Hat man die Grammatik bspw. in BNF vorliegen, kann man für jedes Nicht-Terminal-Symbol eine Klasse erstellen, die einen (inneren) Knoten im Syntax-Baum repräsentiert. Terminal-Symbole werden im Syntax-Baum zu Blättern und bekommen ebenfalls jeweils eine eigene Klasse. Nicht-Terminale, die mehrfach auf der linken Seite einer Produktions-Regel auftauchen, können ein gemeinsames Interface implementieren. So kann man PHPs Typsystem benutzen, um die Wohlgeformtheit der Bäume sicherzustellen.

          Wie soll sowas dann am Ende benutzt werden?

          Zum Beispiel mit einer funktionalen Schnittstelle, eine Factory-Funktion pro Knoten und Blatt. Dein Beispiel einmal aufgegriffen, könnte das bspw. so aussehen:

          $a = reference();
          $b = reference();
          $x = reference();
          $select = select(
              [
                  field($a, 'foo'),
                  field($a, 'bar'),
                  field($b, 'foo')
              ],
              from (
                  as_(
                      join(
                          as_(table('table1'), $a),
                          as_(table('table2'), $b),
                          on(
                              equals(
                                  field($a, 'id'),
                                  field($b, 'id')
                              )
                          )
                      ),
                      $x
                  )
              ),
              where (
                  greater(
                      field($a, 'foo'),
                      integer_(47)
                  )
              )
          );
          

          Bei den Aliassen habe ich in die Trickkiste gegriffen: Aliasse modellieren eigentlich Querverbindungen innerhalb des Syntax-Baums. In meiner Darstellung oben, gehe ich deshalb von einem abstraken Syntax-Graphen auf. Die Querverbindungen werden über PHP-Variablen modelliert. Das muss man nicht so machen, aber ich finde es ganz nett, weil es sichtbar macht, dass der vergebne Name eines Alias immateriell ist, also keine eigene Stuktur-Information trägt.