Hi!
Damit verdeutlichst du mir implizit, dass meine Klasse so ähnlich sein sollte wie MySQLi, nur dass sie mehrere Verbindungen verwaltet. Da ich aber aufgrund deines restlichen Textes das nicht glauben kann, denke ich, dass ich da etwa zu weit hineininterpretiert habe.
Nein, ich wollte damit sagen, dass sie derzeit nur wie ein einfacher Wrapper um mysqli aussieht mit einer Ergänzung fürs Quotieren/Maskieren. Die Mehr-DBMS-Fähigkeit war da noch nicht drin. Wenn du diese unbedingt haben willst (YAGNI!) dann musst du dir da meherere Instanzen erzeugen, die du irgendwie allen zur Verfügung stellen musst, wenn da nicht jeder seine Verbindung neu öffnen soll. Deswegen mein Factory-Vorschlag. Aber, wie gesagt: YAGNI - lass es weg, bis du echten Bedarf hast.
Jein. Sieh die Einfachheit meiner Klasse. Der Mehrwert liegt darin, dass kein Connect mit Test, kein Kodierungsaushandeln, nicht zwangsläufig ein Quoten und Zusammenbauen eines Statements stattfinden muss. Einfach nur (ein einmaliges Initialisieren am Scriptanfang und) ein query()-Aufruf in einem try-catch-Block. Der Verwender kann sich ganz auf seine Geschäftslogik konzentrieren.
Hieße das nicht eher, dass die Initiation bereits in einem try-catch-Block sein muss, damit man die Exception, die bei Fehlschlag des Verbindungsaufbaus geworfen wird, abfängt? Wozu benötige ich denn den try-catch-Block um query() bzw. wann wird da eine Ausnahme ausgelöst?
Das Init ist nur zur Entgegennahme der Konfigurationsparameter vorgesehen. Das eigentliche Öffnen der Verbindung passiert mit einem Lazy Connect. Das ist mit einem quasi internen Singleton realisiert. Wenn eine der öffentlichen Methoden eine Verbindung braucht, ruft es eine private Funktion auf, die die Verbindung (mysqli-Objekt) zurückgibt und sie gegebenenfalls vorher öffnet.
Natürlich ist es auch sinnvoll Fehler beim internen Umgang mit dem mysqli-Objekt abzufangen und dafür try-catch-Blöcke zu verwenden - wenn mysqli Exceptions werfen tät - tut es aber nicht. Man muss also selbst nach Fehlern Ausschau halten, durch Auswerten der Funktionsergebnisse. Zumm Verwender hin kann man ja ganz OOP-like eine Exception werfen. Aber mal angenommen, mysqli würfe Exceptions (PDO macht das, wenn man es lieb darum bittet), dann würde ich trotzdem try-catch intern einsetzen, aber nach außen hin nicht einfach die originale mysqli-Exceptions rethrowen, sondern nur eine allgemeine Exception werfen. Wenn man will, kann man ja die originale Exception als eine Eigenschaft der neu erstellten allgemeinen Exception anhängen. Zum Thema Exceptions folgt gleich noch etwas.
Sollte ich mal das Factory-Pattern außer Acht lassen, sagst du mir somit, dass ich das ganze statisch umsetzen soll. Ich sehe jetzt auch die Vorteile, die das bringen würde. Aber wie läuft es da wieder mit der Fehlerbehandlung? Bei einem erzeugten Objekt, weiß ich, dass es initialisiert wurde, bei der statischen Umsetzung kann es passieren, dass ein "Query()" vor der Initialisierung aufgerufen wird.
Wenn das passiert, muss es eine NotInitializedException geben. Das wäre nämlich ein Programmierfehler. Der Verwender muss dafür sorgen, dass Init() vor der ersten Verwendung aufgerufen wurde.
Da würde ich dann eine zusätzliche if-Abfrage benötigen. Zwar gebe ich zu, dass das kein Problem ist, aber trotzdem meine ich, dass das irgendwie "schlampig" zu sein oder gar der OOP widerspricht. (Eine Klasse zu haben, von der man keine [sinnvollen] Objekte instanzieren kann.)
Nö, bestes Beispiel für eine instanzlose Klasse wäre eine Math-Klasse. Von der braucht man in der Regel keine Instanz sondern nur deren Methoden. (Nicht unter PHP, da kann man gleich die math-Funktionen verwenden, aber unter C# und Java beispielsweise.) Math ist wieder ein recht extremes Beispiel, da da ein paar autarke Funktionen im Grunde genommen nur der Ordnung halber in einer Klasse aufgehängt worden sind. C#, ein durchweg objektorientiertes System, kennt extra das Schlüsselwort static nicht nur für Funktionen sondern auch für Klassen, eben wie solche wie die Math. Die Math-Klasse wird hier mehr oder weniger nur als Namensraum missbraucht und weil es keine klassenlosen Funktionen gibt.
Wie auch immer, bei der DB ist die Sachlage etwas anders, denn die hat intern ja so einige Methoden, die zusammenarbeiten. Und es gibt sogar eine Instanz, auch wenn diese nie das Licht der Öffentlichkeit erblickt. (Die wird im konkreten Fall vermutlich eine Instanz der mysqli-Klasse sein, aber wenn es mysqli nicht gäbe, müsste man dessen Funktionalität mit eigenen internen Methoden nachbauen.)
Nun zur Fehlerbehandlung: Dem Verwender ist es egal, aus welchem Grunde seine Abfrage fehlschlug. Programmierfehler (Syntax-Fehler im SQL-Statement) hat man durch ausreichende Tests ausgeschlossen. Den Rest kann man nicht direkt beeinflussen. Wenn das Netzwerk weg ist, der DBMS-Server down ist, jemand am Rechtesystem rumgefummelt hat, ..., nichts davon kann durch die Geschäftslogik korrigiert werden. Sie kann nur daraufhin ein Notprogramm fahren. Der eigentliche Fehler sollte schon von der DB-Klasse in Richtung Log-System und damit in Richtung Administrator gemeldet worden sein. Zum Verwender hin braucht es nur eine allgemeine Aussage, damit der den Anwender mit einem "Geht grad nicht" benachrichtigen oder ihm was anderes vorsetzen kann.
Die Quote()-Methode braucht es auch nur selten, denn, erwähnt hatte ich es noch nicht, aber die Query()-Methode nimmt das Statement mit Platzhaltern entgegen sowie ein Array mit den Werten und baut sich selbst ein ordentliches, sicheres Statement selbst zurecht. Ob es dazu Prepared Statements oder sprintf() nimmt, steht auf einem anderen Blatt.
Also sähe dann ein Aufruf etwa so aus:
DB::query('SELECT x.foo, y.bar FROM x, y WHERE x.name=%s AND y.name=%s', array($_POST['name'], $_POST['name']));
Ja, genauso dachte ich mir das. Das wäre sie sprintf-Variante.
(da interessiert mich auch, ob das SQL so richtig wäre)
Da fehlt vermutlich noch die Join-Bedingung (in WHERE: x.y_id = y.id - oder sowas in der Art).
Das ist eigentlich ein ganz gute Idee. Das Quotieren würde allerdings dann immernoch für die Identifier nötig werden.
Ja, kann man sich aber schenken, wenn man die Identifier nicht dynamisch einzufügen gedenkt oder sicherstellen kann, dass keine Backticks im Namen vorkommen (wer macht das schon?) sowie Eingaben, die Spaltennamen werden sollen, gegen eine Liste der erlaubten Namen geprüft werden. Wann hat man soch einen Fall? Vermutlich nur beim Sortieren nach einer vom Anwender ausgewählten Spalte. Da muss man sowieso testen, dass die Spalte eine der existierenden/erlaubten ist, damit man keinen Syntaxfehler riskiert. (Man kann auch nach der Spaltennummer (gemäß Reihenfolge in der SELECT-Klausel) sortieren, dann muss man nicht mit dem Spaltennamen hantieren.)
Außerdem könnte ich dann nicht wissen, ob ein %s ein Identifier oder ein String/eine Zahl/etc. ist.
Für String/Zahl/null kann man den Typ des Arguments auswerten und weiß dann, ob gequotet werden muss (String) oder nicht darf (null) (oder ob es egal ist (int/float)). Beim Identifier hat man ein Problem, den muss man irgendwie gänzlich separat behandeln - da fällt mir grad keine clevere Lösung ein.
Das ist dann wirklich nur mehr oder weniger ein Wrapper. Wenn du das so machst, musst du immer ein DB-Objekt herumtragen. Oder du nimmst das Factory-Pattern, um dir bei tatsächlichem Bedarf eine Instanz zu holen. Die kannst du nach getaner Arbeit fallen lassen, weil sie sich der Nächste auch wieder über die Factory holt. [...]
Das würde doch eigentlich auch nur bei mehreren Verbindungen Sinn machen. Weil für jeweils eine Verbindung müsste ich dann nicht extra eine Factory schreiben.
Zweimal ja.
Außerdem sehe ich gerade wieder keinen Unterschied zwischen:
DB::InitConnection('foo', Parameter_der_ersten_Verbindung);
$db=DB::GetConnection('foo');
> und
> `$db=new DB(Parameter_der_ersten_Verbindung);`{:.language-php}
> Vom Sinn her ist das für mich fast das gleiche.
Die Factory kann sich vor der Rückgabe an den Verwender die erzeugte Instanz merken und so die selbe Instanz auch noch an andere Verwender rausgeben. Beim direkten Instantiieren bist du der alleinige Besitzer der Instanz und jeder new-Aufruf erstellt zwingend eine neue.
\-----
Übrigens, falls du dich fragst, warum ich nicht vorschlage, DB von mysqli abzuleiten ... nun, könnte man machen. Nur hat man dann alle öffentlichen Eigenschaften und Methoden in die DB-Klasse "hineingeerbt" und damit wäre sie nicht mehr so schön schlank wie ich sie mit ihren zwei (drei) statischen Methoden entworfen habe, denn was einmal public ist, lässt sich nicht wieder verstecken.
Lo!