{"id":276,"date":"2015-09-05T12:38:05","date_gmt":"2015-09-05T10:38:05","guid":{"rendered":"http:\/\/staratnight.de\/blog\/?p=276"},"modified":"2018-12-02T16:27:32","modified_gmt":"2018-12-02T14:27:32","slug":"crud-in-php-und-mysql-mit-pdo-erstellen","status":"publish","type":"post","link":"https:\/\/staratnight.de\/blog\/crud-in-php-und-mysql-mit-pdo-erstellen\/","title":{"rendered":"CRUD in PHP und MySQL mit PDO erstellen"},"content":{"rendered":"<p>In diesem Artikel m\u00f6chte ich recht ausf\u00fchrlich beschreiben, wie man mit Hilfe von PDO aus PHP heraus elegant auf eine MySQL-Datenbank zugreift. Dabei steht nat\u00fcrlich wie immer die saubere Trennung der Verantwortlichkeiten und gut les- und wartbarer Code im Vordergrund.<\/p>\n<p>&nbsp;<\/p>\n<h1>CRUD<\/h1>\n<p>Unter dem Akronym CRUD werden die wichtigsten SQL-Befehle zur Manipulation von Daten zusammengefasst. Es bedeutet:<!--more--><\/p>\n<ul>\n<li><strong>C<\/strong>reate<br \/>\nErstellen neuer Datens\u00e4tze. Erfolgt via SQL-Befehl <strong>INSERT<\/strong><\/li>\n<li><strong>R<\/strong>ead<br \/>\nLesen vorhandener Datens\u00e4tze aus der Datenbank. Erfolgt via SQL-Befehl <strong>SELECT<\/strong><\/li>\n<li><strong>U<\/strong>pdate<br \/>\nAktualisieren vorhandener Datens\u00e4tze in der Datenbank. Erfolgt via SQL-Befehl <strong>UPDATE<\/strong><\/li>\n<li><strong>D<\/strong>elete<br \/>\nL\u00f6schen vorhandener Datens\u00e4tze aus der Datenbank. Erfolgt via SQL-Befehl <strong>DELETE<\/strong><\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p><strong>Wichtig:<\/strong> Ich gehe an dieser Stelle davon aus, dass der Umgang mit SQL und den entsprechenden Befehlen bekannt ist. Es gibt einige Quellen im Internet, die hierf\u00fcr ganz gut geeignet scheinen (etwa auf <a href=\"http:\/\/sql.lernenhoch2.de\/lernen\/\">Lernen\u00b2<\/a>).<\/p>\n<p>&nbsp;<\/p>\n<h1>PDO<\/h1>\n<p>Die <a href=\"http:\/\/php.net\/manual\/de\/intro.pdo.php\"><strong>P<\/strong>HP <strong>D<\/strong>ata <strong>O<\/strong>bjects<\/a> sind seit PHP 5 eine datenbankunabh\u00e4ngige Programmierschnittstelle f\u00fcr den Zugriff auf Datenbanken. Zur Anbindung einer konkreten Datenbank ben\u00f6tigt man einen passenden <a href=\"http:\/\/php.net\/manual\/de\/pdo.drivers.php\">PDO-Treiber<\/a> f\u00fcr\u00a0 diese Datenbank.<\/p>\n<p>F\u00fcr MySQL reicht es, die folgende Zeile in der Datei php.ini in unserem PHP-Verezichnis zu aktivieren,in dem das f\u00fchrende Semikolon (;) entfernt wird:<\/p>\n<pre class=\"lang:ini decode:true\">extension=php_pdo_mysql.dll<\/pre>\n<p>&nbsp;<\/p>\n<h2>Datenbankverbindung herstellen<\/h2>\n<p>Damit wir unsere Datenbank manipulieren k\u00f6nnen, m\u00fcssen wir zuerst eine Verbindung zur Datenbank aufbauen. Das machen wir, indem wir ein neues PDO-Objekt erstellen:<\/p>\n<pre class=\"lang:php decode:true\">try\r\n{\r\n  $connection = new PDO('mysql:host=localhost;dbname=datenbankname', 'benutzer', 'passwort'); \r\n}\r\ncatch(PDOException $e)\r\n{\r\n  \/\/ Fehlerbehandlung \r\n}<\/pre>\n<p>Was ben\u00f6tigen wir, um dieses Objekt zu erstellen?<\/p>\n<ol>\n<li>Den Namen des <strong>Hosts<\/strong>, auf dem die Datenbank erreicht werden kann. Typischerweise ist das auf einem Webhost &#8222;localhost&#8220;.<\/li>\n<li>Den <strong>Namen der Datenbank<\/strong>, gegen die wir uns verbinden m\u00f6chten<\/li>\n<li>Den <strong>Datenbank-Benutzer<\/strong>, der Rechte an der Datenbank hat<\/li>\n<li>Das <strong>Passwort des Benutzers<\/strong>.<\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n<p>Die Verbindung wird \u00fcber eine sogenannte DataSource \u00fcber eine Connection-URI angegeben. Der Aufbau dieser ist von Datenbank zu Datenbank unterschiedlich.<\/p>\n<p>&nbsp;<\/p>\n<h2>Ausf\u00fchren von Statements<\/h2>\n<p>Nun, da wir eine Verbindung zu unserer Datenbank haben, k\u00f6nnen wir auch darauf zugreifen und die ersten CRUD-Statements ausf\u00fchren.<\/p>\n<p>Als Beispiele verwende ich hier Tabellen aus meiner Kochbuch-Anwendung. Hierbei werden Rezepte (Recipes) und Zutaten (Ingredients) verwaltet.<\/p>\n<p>&nbsp;<\/p>\n<p>Im Grunde gibt es zwei Arten, auf die wir unsere Statements ausf\u00fchren k\u00f6nnen.<\/p>\n<p>&nbsp;<\/p>\n<h3>Einfache Queries<\/h3>\n<p>M\u00f6chten wir nur einfache Lese-Anweisungen ausf\u00fchren, ohne dass wir Parameter verwenden, k\u00f6nnen wir dies ganz einfach dadurch tun, in dem wir ein SQL-Statement \u00fcber die <em>query<\/em>-Methode des PDO-Objekts aufrufen:<\/p>\n<pre class=\"lang:php decode:true\">$result = $connection-&gt;query('SELECT * FROM Ingredient');<\/pre>\n<p>Auf diese Weise k\u00f6nnen wir einfache SQL-Statements ausf\u00fchren. Dies sollten wir aber nur dann verwenden, wenn wir\u00a0 &#8230;<\/p>\n<ul>\n<li>&#8230; eine <strong>READ<\/strong>-Operation durchf\u00fchren wollen<\/li>\n<li>&#8230; keine Parameter an das Statement \u00fcbergeben m\u00fcssen<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h3>Prepared Statements<\/h3>\n<p>Haben wir hingegen Parameter, die wir an die Datenbank weitergeben m\u00f6chten oder wollen wir manipulierende Aktionen durchf\u00fchren (Create, Update, Delete) arbeiten wir sogenannten <strong>Prepared Statements<\/strong>.<\/p>\n<p>Die Ausf\u00fchrung eines solchen erfolgt immer in drei Schritten.<\/p>\n<ol>\n<li>Vorbereiten des Statements (prepare)\n<pre class=\"lang:php decode:true \">$statement = $con-&gt;prepare('SELECT * FROM Ingredient WHERE Name=:name');<\/pre>\n<\/li>\n<li>Best\u00fccken der Parameter mit Werten (bind)\n<pre class=\"lang:php decode:true\">$statement-&gt;bindParam(':name', $name, PDO::PARAM_STR);<\/pre>\n<\/li>\n<li>Ausf\u00fchren des Statements (execute)\n<pre class=\"lang:php decode:true\">$statement-&gt;execute();<\/pre>\n<\/li>\n<\/ol>\n<p>Durch das Binden der Parameter wird der Platzhalter in dem Statement (im Beispiel <em>:name<\/em>) durch den Wert ersetzt. Erst so wird das Statement vollst\u00e4ndig und kann ausgef\u00fchrt werden. Hierbei muss neben dem Namen des Parameters und dem Wert auch der Datentyp angegeben werden. Standardwert ist dabei <em>PDO::PARAM_STR <\/em>f\u00fcr einen<em> String<\/em>-Parameter<em>. <\/em>Weitere m\u00f6gliche Werte sind:<\/p>\n<ul>\n<li>Integer -&gt; PARAM_INT<\/li>\n<li>Boolean -&gt; PARAM_BOOL<\/li>\n<li>LOB -&gt; PARAM_LOB<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>Das Binding kann auch anonym erfolgen. Dann wird der Platzhalter als <em>?<\/em> ausgedr\u00fcckt. Beim Execute k\u00f6nnen dann die Parameter gleich als Array mitgegeben werden. Diese m\u00fcssen dann allerdings in der richtigen Reihenfolge angegeben werden. Hier das gleiche Beispiel nochmal mit anonymen Parametern:<\/p>\n<pre class=\"lang:php decode:true\">$statement = $pdo-&gt;prepare('SELECT * FROM Ingredient WHERE Name=?');\r\n$statement-&gt;execute(array($name));<\/pre>\n<p>Mir pers\u00f6nlich gef\u00e4llt das explizite Binden besser, in den folgenden Beispielen werde ich dies verwenden.<\/p>\n<p>&nbsp;<\/p>\n<p><strong>Wichtig:<\/strong> Um Parameter an SQL-Statements zu \u00fcbergeben, arbeiten wir nie &#8211; <span style=\"text-decoration: underline;\">niemals<\/span> &#8211; mit aneinandergeketteten Strings. Warum das so ist, wird in der Wikipedia genau erkl\u00e4rt. Siehe dort unter <a href=\"https:\/\/de.wikipedia.org\/wiki\/SQL-Injection\">SQL-Injection<\/a>.<\/p>\n<p>&nbsp;<\/p>\n<h2>Ergebnis auswerten<\/h2>\n<p>Nun haben wir unser Statement zusammengebaut und ausgef\u00fchrt. Als n\u00e4chstes wollen wir nat\u00fcrlich auch das Ergebnis aus der Datenbank sehen. Dazu bietet uns PDO eine Reihe von <em>fetch<\/em>-Methoden, mit dessen Hilfe wir das Ergebnis abholen k\u00f6nnen.<\/p>\n<ul>\n<li><strong>fetch(fetch_style)<\/strong><br \/>\nRuft die n\u00e4chste Zeile aus der Ergebnismenge ab. \u00dcber den ersten Parameter k\u00f6nnen wir bestimmen, wie wir unser Ergebnis aufbereitet haben m\u00f6chten. Daf\u00fcr haben wir verschiedene M\u00f6glichkeiten. Ich werde hier nur auf einige davon eingehen. Weitere sind in der PHP-Doku zur <a href=\"https:\/\/secure.php.net\/manual\/de\/pdostatement.fetch.php\">Methode <em>fetch<\/em><\/a> zu finden.<\/p>\n<ul>\n<li><em>PDO::FETCH_ASSOC<\/em><br \/>\nGibt ein Assoziatives Array zur\u00fcck, bei dem der Spaltenname aus der Tabelle der Schl\u00fcssel ist<\/li>\n<li><em>PDO::FETCH_NUM<br \/>\n<\/em>Gibt ein indiziertes Array zur\u00fcck, wobei die Spaltennummern dem Index entsprechen. Die erste Spalte hat dabei den Index 0.<em><br \/>\n<\/em><\/li>\n<li><em>PDO::FETCH_BOTH<\/em> (Standardwert)<br \/>\nKombiniert <em>PDO::FETCH_ASSOC<\/em> und PDO::FETCH_NUM. Es k\u00f6nnen also sowohl die Spaltennamen, als auch eine bei 0 beginnende Spaltennummer als Schl\u00fcssel verwendet werden<\/li>\n<li><em>PDO::FETCH_OBJ<br \/>\n<\/em>Gibt ein anonymes Objekt zur\u00fcck, bei dem die Werte in Eigenschaften gespeichert werden, die dem Namen der Spalte entsprechen.<em><br \/>\n<\/em><\/li>\n<\/ul>\n<\/li>\n<li><strong>fetchAll(fetch_style)<\/strong><br \/>\nRuft alle Zeilen aus der Ergebnismenge ab und liefert diese in einem Array zur\u00fcck. \u00dcber den ersten Parameter k\u00f6nnen wir dabei erneut bestimmen, wie das Ergebnis aufbereitet werden soll. Die M\u00f6glichen Werte sind dabei die gleichen wie beim <em>fetch()<\/em><br \/>\nAbh\u00e4ngig davon wird dann entweder ein mehrdimensionales Array oder ein Array mit Objekte mit den einzelnen Zeilen zur\u00fcckgegeben.<\/li>\n<li><strong>fetchObject(class_name)<\/strong><br \/>\nFunktioniert im Grunde genauso wir <em>fetch(PDO::FETCH_OBJ)<\/em>. Allerdings haben wir hier die M\u00f6glichkeit, gleich eine Klasse zu \u00fcbergeben, die dann als Objekt zur\u00fcckgegeben wird. Das erh\u00f6ht die Typsicherheit und gef\u00e4llt mir pers\u00f6nlich sehr gut.<\/li>\n<li><strong>fetchColumn(column_number)<br \/>\n<\/strong>Funktioniert im Grunde wie\u00a0<em>fetchAll(fetch_style)<\/em>, mit dem Unterschied, dass wir hier nur eine einzige Spalte abrufen. \u00dcber den Parameter wird die Spaltennummer angegeben, die wir abrufen m\u00f6chten. Ohne Parameter wird die erste Spalte in Form eines Array zur\u00fcckgegeben.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>Schauen wir doch mal, wie wir unsere Zutat nun abrufen k\u00f6nnen.<\/p>\n<pre class=\"lang:php decode:true \">$statement = $con-&gt;prepare('SELECT * FROM Ingredient WHERE Name=:name');\r\n\r\n$statement-&gt;bindParam(':name', $name, PDO::PARAM_STR);\r\n$statement-&gt;execute();\r\n\r\n$ingredients = array();\r\nwhile ($ingredient = $statement-&gt;fetch()) {\r\n\t$ingredients[] = $ingredient;\r\n}<\/pre>\n<p>Nun k\u00f6nnen wir auf die einzelnen Werte unserer Zutat zugreifen:<\/p>\n<pre class=\"lang:php decode:true\">$ingredientName = $ingredients[0]['Name'];<\/pre>\n<p>&nbsp;<\/p>\n<p>Besser gef\u00e4llt mir aber wie bereits gesagt der Zugriff \u00fcber das qualifizierte Objekt. Der Code dazu sieht folgenderma\u00dfen aus:<\/p>\n<pre class=\"lang:php decode:true\">$statement = $con-&gt;prepare('SELECT * FROM Ingredient WHERE Name=:name');\r\n\r\n$statement-&gt;bindParam(':name', $name, PDO::PARAM_STR);\r\n$statement-&gt;execute();\r\n\r\n$ingredients = array();\r\nwhile ($ingredient = $statement-&gt;fetchObject('model\\\\Ingredient')) {\r\n\t$ingredients[] = $ingredient;\r\n}<\/pre>\n<p><strong>Hinweis:<\/strong> Ich arbeite mit Namespaces, weshalb der Name der Klasse <em>Ingredient<\/em> mit qualifiziertem Namespace (<em>model<\/em>) erfolgen muss.<\/p>\n<p>Der Zugriff auf den Wert der Zutat ist nun meiner Ansicht nach deutlich besser, da wir z.B. auf Tippfehler im Spaltennamen hingewiesen werden:<\/p>\n<pre class=\"lang:php decode:true\">$ingredientName = $ingredients[0]-&gt;Name;<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Wichtig: <\/strong>Bei fetchObject([Klassenname]) muss der Spaltenname der Tabelle <span style=\"text-decoration: underline;\"><strong>identisch<\/strong> <\/span>sein mit dem Variablennamen in der Model-Klasse. Dabei kommt es insbesondere auf die Gro\u00df-Kleinschreibung an. Hei\u00dft die Spalte in der Tabelle z.B. <em>Name<\/em>, muss auch die Variable <em>$Name<\/em> hei\u00dfen. Sie darf nicht <em>$name<\/em> hei\u00dfen!<\/p>\n<p>&nbsp;<\/p>\n<h1>Tipps<\/h1>\n<p>Zum Abschluss noch ein paar Tipps, die mir beim Arbeiten mit PDOs aufgefallen sind<\/p>\n<h2>Z\u00e4hlen von Datens\u00e4tzen<\/h2>\n<p>Zum z\u00e4hlen von Datens\u00e4tzen wird in SQL <em>count()<\/em> verwendet. Dabei kommt als Ergebnis genau eine Zeile mit einer Spalte zur\u00fcck. Auf diese kann man folgenderma\u00dfen zugreifen:<\/p>\n<pre class=\"\">$result = $con-&gt;query('SELECT count(*) FROM Entity');\r\nreturn $result-&gt;fetchColumn();\r\n<\/pre>\n<p>&nbsp;<\/p>\n<h2>Indexwert einer Spalte um eins erh\u00f6hen<\/h2>\n<p>Verwaltet man seinen Schl\u00fcssel selbst, also ohne Autoinkrement, ist es erforderlich, dass man den Wert &#8211; der typischerweise ein Integer ist &#8211; ermittelt und vor dem Einf\u00fcgen eines neuen Datensatzes um eins erh\u00f6ht.<\/p>\n<p>Mit Hilfe der SQL-Funktion max() ist dies einfach m\u00f6glich:<\/p>\n<pre class=\"\">$result = $con-&gt;query('SELECT max(*) FROM Entity');\r\nreturn $result-&gt;fetchColumn() + 1;<\/pre>\n<p>&nbsp;<\/p>\n<h2>Nur einen Datensatz lesen<\/h2>\n<p>M\u00f6chten wir bei einer bestimmten Abfrage nur ein Ergebnis erhalten, k\u00f6nnen wir dies entsprechend forcieren, in dem wir LIMIT als Schl\u00fcsselwort verwenden. Wollen wir z.B. nur genau eine Zutat abrufen, (weil wir wissen, dass es eben nur einmal &#8222;Pfeffer&#8220; gibt), geht das folgenderma\u00dfen:<\/p>\n<pre class=\"lang:php decode:true \">$statement = $con-&gt;prepare('SELECT * FROM Ingredient WHERE Name=:name LIMIT 1');\r\n\r\n$statement-&gt;bindParam(':name', $name, PDO::PARAM_STR);\r\n$statement-&gt;execute();\r\n$ingredient = $statement-&gt;fetchObject('model\\\\Ingredient')<\/pre>\n<p>&nbsp;<\/p>\n<p>Mit Hilfe dieser einfachen Funktionen lassen sich alle m\u00f6glichen Datenbankanwendungen entwickeln.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In diesem Artikel m\u00f6chte ich recht ausf\u00fchrlich beschreiben, wie man mit Hilfe von PDO aus PHP heraus elegant auf eine MySQL-Datenbank zugreift. Dabei&hellip;<\/p>\n","protected":false},"author":2,"featured_media":432,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[14],"tags":[],"class_list":["post-276","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-php"],"_links":{"self":[{"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/posts\/276","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/comments?post=276"}],"version-history":[{"count":10,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/posts\/276\/revisions"}],"predecessor-version":[{"id":322,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/posts\/276\/revisions\/322"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/media\/432"}],"wp:attachment":[{"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/media?parent=276"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/categories?post=276"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/tags?post=276"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}