Hallo MB,
Kontextwechsel
noch nie gehört? Wenn ich Daten aus einer Variablen in ein SQL Statement einsetze, statt PREPARE und Parametermarker zu verwenden, muss ich sie maskieren.
Dieser Comic von Randall Munroe bringt es göttlich auf den Punkt. „sanitize your database inputs“ - das ist der Kontextwechsel. Du bringst einen String-Wert in ein SQL Statement ein, und musst ihn darum so maskieren, dass SQL Steuerzeichen im String das Statement nicht stören. Dafür hat mysqli die real_escape_string Funktion, und PDO hat quote.
Bei Spaltennamen muss man auch aufpassen, Rolf B
ist ein völlig korrekter Spaltenname. Aber er grillst Dir dein SQL, wenn Du ihn nicht in Backticks einschließt (MYSQL). MS SQL Server verwendet eckige Klammern oder Spec-gemäß doppelte Anführungszeichen (sic!).
new InitDBContextPOPO
Das ist kein POPO. Plain Old * Objects gehören thematisch zum fachlichen Modell. Dein Objekt würde, glaube ich, lieber QueryContext heißen. Dein Tables-Array ist - scheint mir - falschrum gebaut. Die Aliasnamen müssen üblicherweise eindeutig sein in einer Query, Tablenamen nicht. Wenn Du einen self join machen musst, geht das bei Dir schief. Es sollte 'tA' => 'tableA' heißen.
->generalCondition(..., LogicConstant::_NONE)
Warum eigentlich _NONE und nicht NONE?
Abgesehen von der allgemeinen Geschwätzigkeit deines Konzepts - hier würde ein Defaultparameter helfen. Ich nehme an, dass generalCondition auch eine Version hat, in der Du ein Array von LogicContainern als 1. Parameter übergibst und in diesem Fall ergibt die LogicConstant einen Sinn. Wenn aber nur ein einziger Container übergeben wird, ist NONE ein sinnvoller Default.
Was auch helfen kann, ist ein Set von Helper-Funktionen, die das Erzeugen von Interfaces und Containern kapseln. Dann verschwindet eine Menge Technomantie aus einem Query-Build. Und durch schöne Variablennamen liest sich der Build der Query gleich wie ein Buch.
$isMiddleAged = new BetweenClause($ageColumn, 30, 59);
$hasLargeSize = new InClause($sizeColumn, [ 'L', 'XL', 'XXL' ]);
$logic = $isMiddleAged -> or( $hasLargeSize );
oder kürzer
$logic = new BetweenClause(...)
->or(new InClause(...));
Es gibt sicherlich eine abstrakte Superklasse für Logikelemente, mit Ableitungen für Basiskonstrukte (Subselect, Between, In, Vergleich) und Logikoperationen (AND, OR).
In der sähe die or-Methode ganz einfach so aus:
public function or($otherLogic) {
return new LogicContainer( $this, $otherLogic, LogicConstant::_OR);
}
Und diese Methoden heißen or und and - nicht etwa addOrCondition oder so. Man nennt sowas ein Fluid Interface - es sieht aus wie flüssige Sprache. Die Container sollten übrigens rein binär sein. Ein OR aus mit N Operanden lässt sich immer auf (N-1) ORs mit 2 Operanden zurückführen, und das macht die Programmierung vieeeel einfacher.
Warum heißen die Methoden des Querybuilders eigenlich generalXXXX? Gibt es auch specialXXXX Varianten? Eigentlich sollte ein Builder doch der Select-Struktur folgen.
$builder->select( $columnExpressions )
->from( $source )
->where( $whereLogic )
->groupBy( $groups )
->having( $havingLogic )
->orderBy( $columnExpressions )
Warum da neue Namen erfinden? Das $builder Objekt sollte übrigens nur die Methoden select, insert, update, delete und union kennen. Diese erzeugen spezifische Builder für die Befehle (die natürlich alle Subklassen eines abstrakten Builders sind).
Vielleicht kann $builder auch Helper enthalten, die ColumnInterfaces oder LiteralInterfaces erzeugen. Und diese Interfaces können ihrerseits Builder-Helper enthalten:
$fooIsBar = $builder->column("foo")
->equals($builder->string("o'hara"))
$everything = $builder->column("everything");
$isInSubquery = $everything->equals($subQuery)
könnte dann ein ExpressionInterface für einen Test auf Gleichheit erzeugen, und $fooIsBar->getSql()
liefert den String \
foo` = 'o'hara'` - oh je, das rafft Markdown nicht - . Das Backslash in o'hara stammt aus dem Kontextwechsel, das ' Zeichen darf nicht unmaskiert in eine Query hinein.
Das liest sich jedenfalls besser als deine Comparison Konstruktion. Wichtig ist da auch eine API Konsistenz. Es ist merkwürdig, wenn ein LogicContainer die LogicConstant am Ende hat und eine Comparison die „EquotiationConstant“ (das Wort gibt's nicht) vorn. Diese Constant möchte vermutlich lieber ComparisonOperator heißen…
was meinst Du mit manuelle Formatierung
Ich meinte die von Dir überall verstreuten Aufrufe von toString oder auch getResult. Sowas sollten die Builder-Komponenten bei Bedarf selbst tun.
Wo und was nachlesen
Syntax von BETWEEN. Im MYSQL Handbuch. Findet sich im Internet. Lohnende Lektüre 😂
Jedenfalls ist so ein fluid interface aus meiner Sicht die eleganteste Lösung. Bestimmt besser als mein Schnaps mit formatSqlFragment von gestern.
Ich habe jetzt viel durcheinander geschrieben, es geht nicht alles stringent in eine Konzeptrichtung. Such Dir was 'raus 😉
Rolf
--
sumpsi - posui - clusi