{"id":475,"date":"2010-01-10T16:33:26","date_gmt":"2010-01-10T15:33:26","guid":{"rendered":"http:\/\/blog.benny-baumann.de\/?p=475"},"modified":"2010-01-10T16:33:26","modified_gmt":"2010-01-10T15:33:26","slug":"php-und-seine-datenbank-schnittstellen","status":"publish","type":"post","link":"http:\/\/blog.benny-baumann.de\/?p=475","title":{"rendered":"PHP und seine Datenbank-Schnittstellen"},"content":{"rendered":"<p>F\u00fcr ein kleineres PHP-Projekt meinerseits brauchte ich eine Anbindung an eine Datenbank. Da die Resourcen recht knapp auf dem System sind, ich aber eine Reihe von Dingen im Hintergrund erledigt brauche, habe ich mich ein wenig umgesehen, was PHP bietet, bzw. welche fertigen Libs es gibt. Anforderungen waren dabei bewusst einfach gew\u00e4hlt: Die Bibliothek der Wahl musste Prepared Statements unterst\u00fctzen, sollte schlank sein und eine abgerissene Datenbank-Verbindung automatisch als solche erkennen und wiederherstellen k\u00f6nnen. Wie so oft bei PHP waren die meisten Implementierungen entweder zu aufgebl\u00e4ht, oder aber erf\u00fcllten die anderen Bedingungen nicht.<!--more--><\/p>\n<p>Am Ende blieb also lediglich, die Implementierung selber vorzunehmen. Da f\u00fcr mein Projekt die Anbindung an MySQL ben\u00f6tigt wurde, gab es nun allein bei PHP >5 Varianten, wie man h\u00e4tte zu MySQL verbinden k\u00f6nnen:<\/p>\n<ol>\n<li>MySQL Extension &#8211;> Kann keine Prepared Statements<\/li>\n<li>MySQLi Extension &#8211;> Kann Prepared Statements, aber &#8230;<\/li>\n<li>MySQL Native Driver &#8211;> Kann Prepared Statements, aber &#8230;<\/li>\n<li>PDO (PHP Data Objects) &#8211;> Kann Prepared Statements, aber &#8230;<\/li>\n<li>ODBC oder andere Wrapper &#8211;> K\u00f6nnen oft keine Prepared Statements oder sind zu langsam<\/li>\n<\/ol>\n<p>Nimmt man bereits diese Vorauswahl, bleiben von den 5 M\u00f6glichkeiten noch 3 mit einem dicken ABER \u00fcbrig. Schauen wir also einmal genauer und stelen fest:<\/p>\n<ol>\n<li>MySQLi Extension &#8211;> Bescheidenes Interface (gelinde gesagt)<\/li>\n<li>MySQL Native Driver &#8211;> Overkill f\u00fcr meine Anwendung<\/li>\n<li>PDO (PHP Data Objects) &#8211;> Wir wrappen einen Wrapper mit einem mWrapper, damit das Interface trotzdem Schei\u00dfe aussieht<\/li>\n<\/ol>\n<p>Nehmen wir also den Native Driver einmal raus, verbleiben noch genau zwei M\u00f6glichkeiten, einen Datenbank-Layer zu implementieren, die sicherlich auch von den meisten verwendet werden: MySQLi oder PDO.<\/p>\n<p>Wenn man sich nun anschaut, ob man mit einem bescheidenen Interface arbeiten muss oder gegen ein anderes, was genauso bescheiden ist, ist es einem im Endeffekt eigentlich egal, welches man davon nimmt. Vorteilhaft w\u00e4re f\u00fcr meinen Zweck zwar theoretisch PDO gewesen, da ich es gerne Datenbank-unabh\u00e4ngig gehabt h\u00e4tte, aber das habe ich recht schnell sein gelassen. Der Grund daf\u00fcr liegt in der Syntax der eingangs erw\u00e4hnten Prepared Statements, die ich als Voraussetzung hatte.<\/p>\n<p>Womit wir wieder bei dem Punkt bescheidene Interfaces w\u00e4ren: Ein Wunschkriterium war u.a. auch, dass der Datenbank-Layer Support f\u00fcr Named Parameters in Prepared Statements implementiert. Dies ist f\u00fcr PDO nicht der Fall. Vielmehr muss man durch massenhafte Rudelvorkommen von Fragezeichen die entsprechenden Stellen markieren, an denen man seine Parameter eingef\u00fcgt haben m\u00f6chte. Aus Perspektive der Lesbarkeit also das, was man zu allerletzt haben m\u00f6chte. Aber auch MySQLi bestach in seinem Interface: So wurden zwar benannte Parameter unterst\u00fctzt, &#8222;aus Kompatibilit\u00e4t&#8220; wurde aber bei Mehrfach-Vorkommen eines Parameters immer nur das erste Vorkommen initialisiert &#8211; auch bei Mehrfach-Aufrufen. Stattdessen l\u00e4uft man in Fehlermeldungen, man h\u00e4tte zu wenige Parameter \u00fcbergeben. Eindeutig ein FAIL! WER bittesch\u00f6n denkt sich, dass man beim Bau einer Schnittstelle kompatibel zu den Fehlern anderer sein MUSS?!?!?!<\/p>\n<p>Also gut. In meiner Bibliothek hab ich das jetzt insofern behoben, dass meine Bibliothek einen Workaround um diese Sinnlosigkeit macht: Wenn ich mehrfach den gleichen Namen vergebe, m\u00f6chte ich auch, dass dort mehrfach der gleiche Wert ankommt. Und wie macht man das, wenn der zugrundeliegende Layer das nicht kann? Richtig, man emuliert es, durch Ersetzen von Variablen innerhalb der Query:<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n  \/**\r\n   * DBStatement::_prepareHelper()\r\n   *\r\n   * @param mixed $m\r\n   * @return\r\n   *\/\r\n  private function _prepareHelper($m)\r\n  {\r\n    $paramname = $m[1];\r\n\r\n    if (!isset($this-&gt;params[$paramname])) {\r\n      $this-&gt;params[$paramname] = array();\r\n    }\r\n\r\n    $dbmsparam = $this-&gt;paramCount++;\r\n\r\n    $this-&gt;params[$paramname][] = $dbmsparam;\r\n\r\n    return \"?\";\r\n  }\r\n\r\n  \/**\r\n  * DBStatement::reprepare()\r\n  *\r\n  * @return\r\n  *\/\r\n  public function reprepare()\r\n  {\r\n    $this-&gt;params = array();\r\n    $this-&gt;paramCount = 0;\r\n\r\n    $_query = preg_replace_callback(\"\/:(\\w+)\\b\/\", array($this, '_prepareHelper'), $this-&gt;query);\r\n\r\n    if (!$this-&gt;connection->ensureConnected()) {\r\n      return false;\r\n    }\r\n\r\n    $this-&gt;query_res = $this-&gt;connection-&gt;getHandle()-&gt;prepare($_query);\r\n\r\n    \/\/ ...\r\n  }\r\n<\/pre>\n<p>Dieser Source ist im Grund nix weiter, als ein stark vereinfachter SQL-Parser, der lediglich Parameter in einer SQL-Query findet. Es sind noch eine Reihe von Vereinfachungen enthalten (? wird nicht als Parameter erkannt), aber das l\u00e4sst sich recht einfach nachr\u00fcsten. Auch das Ignorieren von Parametern innerhalb von Strings kann mit etwas Arbeit noch erg\u00e4nzt werden. Wenn man aber bedenkt, dass man schon zu solchen W\u00fcrgarounds greifen muss, um eine Abfrage vern\u00fcnftig an MySQLi abzusetzen, ist man \u00fcber solche Dinge wie &#8222;Wir binden das Ergebnis NUR an Variablen und verzichten auf die R\u00fcckgabe als Array&#8220; schon fast eine Formalit\u00e4t. Also: Here we go (THX an Neo f\u00fcr das Finden dieses <a href=\"http:\/\/www.php.net\/manual\/en\/mysqli-stmt.fetch.php#82742\">Tipps in den Kommentaren zur Dokumentation<\/a>):<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n  \/**\r\n   * Bind the query to a variable for reading the result data\r\n   *\r\n   * @param mysqli_stmt $stmt\r\n   * @param mixed $out\r\n   * @return bolean Result of call.\r\n   *\/\r\n  function stmt_bind_assoc(mysqli_stmt &amp;$stmt, &amp;$out) {\r\n    $data = mysqli_stmt_result_metadata($stmt);\r\n    $fields = array();\r\n    $out = array();\r\n\r\n    $fields[0] = $stmt;\r\n    $count = 1;\r\n\r\n    while ($field = mysqli_fetch_field($data)) {\r\n      $fields[$count] =&amp; $out[$field->name];\r\n      $count++;\r\n    }\r\n\r\n    return call_user_func_array('mysqli_stmt_bind_result', $fields);\r\n  }\r\n<\/pre>\n<p>Wobei das Lesen nun wie von MySQL gewohnt erfolgen kann:<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n        \/\/ Init the result buffer\r\n        $Result = array();\r\n        \/\/ Try to bind the result buffer to the query\r\n        if (!$this-&gt;stmt_bind_assoc($this-&gt;Query, $Result)) {\r\n          throw new Exception(\"Cannot bind the result buffer to the query object.\");\r\n          return false;\r\n        }\r\n        \/\/ Fetch the result line by line\r\n        while ($this-&gt;Query-&gt;fetch()) {\r\n          \/\/ Fetch and store the data into the object's result buffer\r\n          $this-&gt;ResultSet[] = $Result;\r\n        }\r\n<\/pre>\n<p>Baut man nun noch ein wenig Magik drumherum, kann man daraus ein wunderbares, logisch strukturiertes Interface machen, was sich wie die alte MySQL-Extension verh\u00e4lt, sowohl Zeilenweise als auch insgesamt abrufbar ist und mit gerade einmal 3 Klassen eigentlich alles abdeckt, was man braucht:<\/p>\n<ol>\n<li>DBConnection: Das Herzst\u00fcck jeder Verbindung zur Datenbank.<\/li>\n<li>DBStatement: Ein Prepared-Statement und Funktionen zu dessen Nutzung<\/li>\n<li>DBResultset: Ein Objekt, was jegliche Informationen \u00fcber das Ergebnis einer Abfrage kapselt<\/li>\n<\/ol>\n<p>Zus\u00e4tzlich ben\u00f6tigt man zwar u.U. eine weitere Klasse, die die Login-Informationen beinhaltet, aber das l\u00e4sst sich recht einfach realisieren. Verlgeicht man nun diese einfache Struktur mit dem Original-Interface von MySQLi, l\u00e4uft es einem allein bei MySQLi kalt den R\u00fccken runter: Nicht nur kann das Interface nicht sinnvoll dynamisch eingesetzt werden, erschwert wesentliche Aufgaben unn\u00f6tig und liefert obendrein essentielle Informationen an v\u00f6llig unlogischen Orten. Oder wer vermutet die letzte durch eine Abfrage generierte Datensatz-ID in der Verbindung, statt im Resultset-Objekt, was diese erzeugt hat? Um mal das Wort Konsistenz gar nicht weiter anzusprechen: Wenn man ein Prepared Statement ausf\u00fchrt, so muss man einige Ergebniswerte aus dem Prepared Statement lesen, obwohl diese zum Ergebnis geh\u00f6ren. Eben dieses bekommt man aber erst \u00fcber zahlreiche Umwege und dann enth\u00e4lt es auch nur die H\u00e4lfte der Angaben. Also muss man in einem Wrapper sich Daten aus 4 Klassen (mysqli, mysqli_stmt, mysqli_result und stdclass-Objekten) zusammensuchen, statt als Ergebnis einer Abfrage ein Resultset zu bekommen, in dem alle n\u00f6tigen Angaben zentral enthalten sind.<\/p>\n<p>Sobald ich mit dem Aufr\u00e4umen meines Datenbank-Layers und dessen Implementationn fertig bin, gibt es dazu auch noch ein Release.<\/p>\n<p>Wer PHP als PHrickle-Programm bezeichnet, hat eindeutig Recht.<\/p>\n<p class=\"wp-flattr-button\"><a href=\"http:\/\/blog.benny-baumann.de\/?flattrss_redirect&amp;id=475&amp;md5=47a56611e3752ca38e781eefff3cf20c\" title=\"Flattr\" target=\"_blank\"><img src=\"http:\/\/blog.benny-baumann.de\/wp-content\/plugins\/flattr\/img\/flattr-badge-large.png\" srcset=\"http:\/\/blog.benny-baumann.de\/wp-content\/plugins\/flattr\/img\/flattr-badge-large.png\" alt=\"Flattr this!\"\/><\/a><\/p>","protected":false},"excerpt":{"rendered":"<p>F\u00fcr ein kleineres PHP-Projekt meinerseits brauchte ich eine Anbindung an eine Datenbank. Da die Resourcen recht knapp auf dem System sind, ich aber eine Reihe von Dingen im Hintergrund erledigt brauche, habe ich mich ein wenig umgesehen, was PHP bietet, bzw. welche fertigen Libs es gibt. Anforderungen waren dabei bewusst einfach gew\u00e4hlt: Die Bibliothek der [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[29],"tags":[14,98,128,246,21],"class_list":["post-475","post","type-post","status-publish","format-standard","hentry","category-software","tag-bugs","tag-developement","tag-meinung","tag-mysql","tag-php"],"_links":{"self":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/475","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=475"}],"version-history":[{"count":2,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/475\/revisions"}],"predecessor-version":[{"id":477,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/475\/revisions\/477"}],"wp:attachment":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=475"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=475"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=475"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}