webtechnologien teil 8: pdo/mysql -...
TRANSCRIPT
2Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Übersicht
Arten der Kommunikation mit dem Datenbank-Server Grundsätzliche Operationen Weitere Routinen Tricks und Tipps
Siehe: http://dev.mysql.com/doc/refman/5.5/en/apis-php.htmlhttp://de3.php.net/manual/en/book.mysqli.php
3Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Einführung I
PHP hat jeweils eine eigene API zum Zugriff auf die Datenbanken in Form eines Treibers.
Bei MySQL ist es die MySQLi-Schnittstelle. Da sich die Schnittstellen zu verschiedenen Datenbanken
unterscheiden, wurde eine einheitliche PDO-Schnittstelle geschaffen.
4Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
PHP-Schnittstellen zu Datenbanken (Auszug)
Datenbank Datenbank Datenbank
Adabas D Oracle MySQL
dBase PostgreSQL MySQLi
DB2 Sybase MariaDB
Ingres Interbase
FilePro Informix
Und natürlich auch ODBC...Siehe dazu: http://de.wikipedia.org/wiki/Open_Database_Connectivity
5Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Schnittstellen zur MySQL-Datenbank
mysql-Schnittstelle (Veraltet, wird nicht mehr unterstützt)
mysqli-Schnittstelle (improved, die aktuelle Version)
PDO-Schnittstelle (Portable Data Object)Diese abstrahiert von den verschiedenen Schnittstellen zu den Datenbanken – aber nur syntaktisch. Die speziellen Eigenschaften der SQL-Dialekte der jeweiligen Datenbanken bleiben jedoch erhalten!
Diese Schnittstelle wird hier beschrieben.
Es gibt aus historischen Gründen drei Schnittstellen zu MySQL:
6Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Das PDO-Objekt
Die Benutzung der Schnittstelle erfolgt nur Objekt-orientiert. Durch das Erzeugen des Objekts wird gleichzeitig eine
Verbindung zur Datenbank aufgebaut, die beim Entfernen des Objekts geschlossen wird.
Das allgemeine Schema des Zugriffs sieht daher so aus:
Aufbau der Verbindung
Zugriffe auf die Datenbank
Abbau der Verbindung
7Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Bemerkungen
In diesem Foliensatz wird ein Teil eines Logins für eine Website beschrieben.
Aber(!): Hier wird nur der Aspekt des Zugriffs auf die Datenbank thematisiert, Aspekte der Sicherheit werden ignoriert.
Dies NICHT zur Grundlage eines echten Logins nehmen.
Grund: Passwörter dürfen nie im Klartext abgespeichert werden, sondern immer nur deren Hashwerte (oder ähnlich).
8Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Verbindung zur Datenbank I
DSN heißt Data Source Name: die Definition des Zugriffs auf die Datenbank. Dies ist ein String mit dem Aufbau:
Datenbanktyp: Keyword=Wert; Keyword=Wert; …– Datenbanktyp ist hier mysql– Für Keyword können hier eingesetzt werden:
Host: Angabe der IP/DNS-Adresse der Datenbank dbname: Name der ausgewählten Datenbank
Nutzer und Passwort sind Strings Der letzte Parameter definiert Optionen, die für die Kommunikation mit der
Datenbank wichtig sind (ein PHP-Hash mit definierten Keys)
object PDO(DSN [,Nutzer [,Passwort[,Optionen]]]);
Siehe: http://www.php.net/manual/de/book.pdo.php http://de.wikipedia.org/wiki/Data_Source_Name
9Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Verbindung zur Datenbank II - Beispiel
Die Datenbank liegt auf der eigenen Maschine und heißt "accounts": (1). Der User ist "nobody" mit dem Passwort "blabla426" (2) und (3). Mit den Optionen wird der Zeichensatz auf UTF-8 gesetzt: (4) bis (6). Es wird der Warn-Modus eingeschaltet: (9).
(1) $DSN= 'mysql:host=localhost;dbname=accounts';(2) $DB_USER= 'nobody';(3) $DB_PW= 'blabla426';(4) $DB_options = array((5) PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'(6) ); (7) try {(8) $db= new PDO($DSN,$DB_USER,$DB_PW,$DB_options);(9) $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);(10)} catch(PDOException $err) {(11) echo 'DB ERROR: '.$err->getMessage().PHP_EOL;(12)}
10Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Bemerkungen zu PHP/Optionen
Derartige Konstanten haben nie ein $ zum Beginn. Sie werden per Konvention in Großbuchstaben geschrieben (wie
in Java).
PDO::MYSQL_ATTR_INIT_COMMAND
PDO::MYSQL_ATTR_INIT_COMMAND ist in PHP eine static-Konstante.
Klassenname Name der Konstante
11Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Verbindung zur Datenbank III - Erläuterungen
Es sind noch andere Optionen für MySQL möglich, z.B.:– PDO::MYSQL_ATTR_LOCAL_INFILE (alternative Konfigurationsdatei)
um z.B. zum Testen eine andere Konfiguration als bei normalen Betrieb zu benutzen.
– PDO::MYSQL_ATTR_COMPRESSDaten der Verbindung werden komprimiert
Siehe dazu: http://de2.php.net/pdo_mysql
(4) $DB_options = array((5) PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'(6) );
MySQL-spezifische Konstante
12Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Verbindung zur Datenbank IV - Erläuterungen
Im Fehlerfalle wirft das PDO-Objekt eine Exception, die mit der obigen Konstruktion gefangen werden kann.
Die Methode getMessage() erzeugt einen String mit der Fehlermeldung.
PHP_EOL ist die Konstante mit der Bedeutung End-of-Line.
Die try-Catch-Konstruktion ist in PHP genauso wie in Java: Im catch-Teil wird ein Objekt deklariert, das weiter gehende Informationen enthält, hier von der Klasse PDOException (Zeile 10).
(7) try {(8) $db= new PDO($DSN,$DB_USER,$DB_PW,$DB_options);(10)} catch(PDOException $err) {(11) echo 'DB ERROR: '.$err->getMessage().PHP_EOL;(12)}
13Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Verbindung zur Datenbank V - Erläuterungen
Nachdem das Objekt generiert wurde, können dem Objekt Eigenschaften zugewiesen werden, die aber nur das PDO-Objekt betreffen, nicht die Benutzung von MySQL.
Es gibt drei Modi:– PDO::ERRMODE_SILENT
Es wird lediglich ein Errorcode geliefert, der explizit nach jedem PDO-Methodenaufruf abgefragt werden muss. Default!
– PDO::ERRMODE_WARNINGEs wird eine Warn-Meldung ausgegeben sowie der Errorcode geliefert.
– PDO::ERRMODE_EXCEPTIONEs wird eine Exception geworfen.
Siehe dazu: http://www.php.net/manual/de/pdo.error-handling.php
(7) try {(8) $db= new PDO($DSN,$DB_USER,$DB_PW,$DB_options);(9) $db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_WARNING);(10)} catch(PDOException $err) {
14Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Hinweise
Ob mit Exceptions oder mit einem der anderen Modi gearbeitet wird, hängt von der Programmierkonvention im betreffenden Projekt ab.
Die Benutzung von Exceptions führt zu einem umständlichen Code; dafür entfallen die vielen Fehlerabfragen.
Wer sowieso immer den Erfolg einer Routine in einer Fehlerabfrage abfragt, der sollte – während der Entwicklung im Warn-Modus– während der Produktion im Silent-Modus
arbeiten.
15Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Unser Beispiel weiter I
Der MySQL-Server muss natürlich laufen. Auch muss die Datenbank mit dem User "nobody" und dem Passwort
"blabla426" eingerichtet sein.
17Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Einrichten einer Datenbank II
Es hat geklappt!
Festlegen derKodierung
20Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Hinweis
Das Gewähren aller Rechte für einen Benutzer ist nur dann sinnvoll, wenn es – wie hier – um eine Spielanwendung geht.
In der Realität müssen die Rechte beschränkt werden. Diese Beschränkung lässt sich gut mit phpMyAdmin konfigurieren.
21Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Einrichten einer Datenbank IV
So sieht es schon einmal ganz gut aus...
22Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Unser Beispiel weiter II – Create Table
Nun bauen wir eine Tabelle in SQL:
(1) CREATE TABLE account ((2) id int(11) NOT NULL PRIMARY KEY,(3) name varchar(45) NOT NULL,(4) pw varchar(64) NOT NULL,(5) groupID int(11) NOT NULL(6) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
id ist der Schlüsselwert in der Tabelle (2). Der Login-Name und das Passwort dürfen nicht leer sein (3,4). Dann bereiten wir noch Gruppen vor, indem eine Gruppen-ID als
Integer zugelassen wird (5). Da wir Transaktionen haben wollen, muss die InnoDB-Version von
MySQL benutzt werden (6). In den letzten Versionen von MySQL ist dies der Defaultwert und braucht daher nicht angegeben zu werden.
Und natürlich UTF-8 als Zeichensatz (6).
23Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Unser Beispiel weiter III – Create Table
(1) $create_table = "CREATE TABLE account (";(2) $create_table.= "id int(11) NOT NULL PRIMARY KEY,";(3) $create_table.= "name varchar(45) NOT NULL,";(4) $create_table.= "pw varchar(64) NOT NULL,";(5) $create_table.= "groupID int(11) NOT NULL";(6) $create_table.= ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
(7) $rtn= $db->exec($create_table);(8) if($rtn===false) {(9) $err= $db->errorInfo();(10) echo 'DB ERROR: #'.$err[1]." ".$err[2].PHP_EOL;(11)}
Die Tabelle wird mit exec() angelegt (7). Der String für $create_table wird stückweise mit der verkürzten
Punkt-Notation zusammengesetzt (Zeilen 1 bis 6). In Zeile (8) steht die Fehlerabfrage mit ===.
24Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Create Table I - Erläuterungen
(7) $rtn= $db->exec($create_table);(8) if($rtn===false) {
exec("SQL-String") führt eine SQL-Operation aus, bei der keine Werte anschließend abgefragt werden, d.h. exec() kann nicht für Abfragen von Tabelleninhalten benutzt werden.
In der Variablen $rtn wird der Return-Code abgespeichert, der– im Fehlerfall den boole'schen Wert false hat,– ansonsten die Anzahl der betroffenen Zeilen in der Tabelle enthält.
Daher muss mit === abgefragt werden(weil die Anzahl ja auch 0 sein kann, was im Falle von == fehlerhafter Weise als false interpretiert wird)
Siehe: http://www.php.net/manual/de/pdo.exec.php
25Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Create Table II - Erläuterungen
(8) if($rtn===false) {(9) $err= $db->errorInfo();(10) echo 'DB ERROR: #'.$err[1]." ".$err[2].PHP_EOL;(11)}
Die Methode errorInfo() liefert ein Array zurück (9):– Index[0] enthält den SQL-Error-Code– Index[1] enthält den Error-Code des Treibers– Index[2] enthält die Fehlermeldung
Siehe: http://www.php.net/manual/de/pdo.errorinfo.php Dann gibt es noch die Methode errorCode(), die lediglich den SQL-Error-
Code liefert. Siehe:
– http://docstore.mik.ua/orelly/java-ent/jenut/ch08_06.htm – http://www.php.net/manual/de/pdo.errorcode.php
26Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Unser Beispiel weiter IV - Drop Table
(1) $create_table = "CREATE TABLE account ("; ... (6) $create_table.= ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";(7) $rtn= $db->exec($create_table);(8) if($rtn===false) {(9) $err= $db->errorInfo();(10) echo 'DB ERROR: #'.$err[1]." ".$err[2].PHP_EOL;(11)}(12)$delete_table= "DROP TABLE account;";(13)$rtn= $db->exec($delete_table);(14)if($rtn===false) {(15) $err= $db->errorInfo(); ...}
Die Tabelle account wird hier gelöscht (12,13). Das machen wir hier nur für unser Beispiel, damit wir in mehreren
Durchläufen etwas ausprobieren können. Für ein Login-Modul ist das natürlich unsinnig.
27Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Unser Beispiel weiter V – Einfügen von Daten
(1) $insert = "INSERT INTO account(id,name,pw,groupID)"(2) $insert.= "VALUES(0,'root','bitte!',0)";(3) $rtn= $db->exec($insert);(4) if($rtn===false) {(5) $err= $db->errorInfo();(6) echo 'DB ERROR: #'.$err[1]." ".$err[2].PHP_EOL;(7) }
In die Tabelle account wird ein einziger Record (3) eingefügt. Bitte beachten Sie, dass in SQL andere String-Begrenzer als in PHP
benutzt werden (2).
28Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Wir glauben ja nichts – sehen wir nach
Das ist die Ausgabe der Tabelle in phpMyAdmin.
29Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Unser Beispiel weiter VI – Einfügen von Daten
$val[]=array('id'=>0,'name'=>'root', 'pw'=>'bitte!','groupID'=>0);$val[]=array('id'=>1,'name'=>'user', 'pw'=>'danke!','groupID'=>0);$val[]=array('id'=>2,'name'=>'helmut','pw'=>'sdgafg','groupID'=>0);$val[]=array('id'=>3,'name'=>'evelyn','pw'=>'tztrff','groupID'=>0);
(1) foreach($val as $v) {(2) $insert = "INSERT INTO account(id,name,pw,groupID) "; (3) $insert.= "VALUES(${v['id']},'${v['name']}','${v['pw']}'";(4) $insert.= ",${v['groupID']})"; (5) $rtn= $db->exec($insert);(6) if($rtn===false) {(7) $err= $db->errorInfo();(8) echo 'DB ERROR: #'.$err[1]." ".$err[2].PHP_EOL;(9) }(10)}
Es wird nun eine Tabelle zum Füllen der Datenbank-Tabelle verwendet.
30Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Einfügen von Daten - Erläuterungen
(1) foreach($val as $v) {(2) $insert = "INSERT INTO account(id,name,pw,groupID) "; (3) $insert.= "VALUES(${v['id']},'${v['name']}','${v['pw']}'";(4) $insert.= ",${v['groupID']})";
Die Konstruktion ${v['id']} ist erforderlich, weil ein Ausdruck zur Auswertung innerhalb des Strings benutzt wird, daher die {}.
Die einzelnen Elemente des Hashs sind ja benannt, so dass relativ schnell klar wird, was wann in das Insert-SQL-Statement eingefügt wird.
31Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Wir glauben ja nichts – sehen wir nach
Das ist die Ausgabe der Tabelle in phpMyAdmin.
32Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Unser Beispiel weiter VII – Auslesen von Daten
(1) $db= new PDO($DSN,$DB_USER,$DB_PW,$DB_options);(2) $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);(3) $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC); ...(4) $query= "SELECT * FROM account";(5) $data= $db->query($query);(6) foreach($data as $row) {(7) print_r($row);(8) }
Es wird das Attribut PDO::ATTR_DEFAULT_FETCH_MODE beim Erzeugen des PDO-Objekts gesetzt, das die Art des Objekts beim Auslesen von Informationen mit der Methode query() bestimmt.Möglich sind:– PDO::FETCH_ASSOC: Es wird ein Hash mit den Namen der Spalten
geliefert (das ist zu empfehlen).– PDO::FETCH_BOTH: Ein Hash mit Array-Indices wird geliefert.– PDO::FETCH_OBJ: Ein Objekt wird generiert.
33Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Unser Beispiel weiter VIII – Auslesen von Daten
Array( [id] => 0 [name] => root [pw] => bitte! [groupID] => 0)Array( [id] => 1 [name] => user [pw] => danke! [groupID] => 0)
Array( [id] => 2 [name] => helmut [pw] => sdgafg [groupID] => 0)Array( [id] => 3 [name] => evelyn [pw] => tztrff [groupID] => 0)
Das ist die Ausgabe des letzten Beispiels durch print_r (Zeile 7).
Mit jedem Schleifendurchlauf wird eine Zeile aus der Tabelle gelesenund als Hash geliefert.
34Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Auslesen von Daten nach query()
(5) $data= $db->query($query);
Ein query() liefert den Inhalt einer (temporären) Tabelle als ein Objekt vom Typ PDOStatement.
Das foreach des Beispiels Zeile (6) durchläuft das Objekt $data, das genau eine Zeile der Tabelle enthält.
Mit fetch() wird jeweils die nächste Zeile des letzten Querys gelesen. fetchall() liest die ganze (temporäre) Tabelle in ein Array ein. fetchall() ist
natürlich nur bei kleinen Tabellen sinnvoll.
$row= $data->fetch();
$table= $data->fetchall();
alternativ
35Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Auslesen der Meta-Information I
(1) $query= "SELECT * FROM account";(2) $data= $db->query($query);(3) echo $data->columnCount().PHP_EOL;(4) for($i= 0;$i<$data->columnCount();$i++) {(5) var_export($data->getColumnMeta($i));(6) }
Meta-Informationen sind solche über etwas. Mit columnCount() wird die Anzahl der
Spalten der temporären Tabelle geliefert, mit getColumnMeta() eine Beschreibung der
Spalte. Rechts steht der Beginn der Ausgabe. Es ist
der Aufbau der Meta-Information (Deskriptor) erkennbar.
4array ( 'native_type' => 'LONG', 'pdo_type' => 2, 'flags' => array ( 0 => 'not_null', 1 => 'primary_key', ), 'table' => 'account', 'name' => 'id', 'len' => 11, 'precision' => 0,)
36Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Auslesen der Meta-Information II – Ganze Tabelle
(1) $data= $db->query($query);(2) $limit= $data->columnCount();(3) for($i= 0;$i<$limit;$i++) {(4) $meta= $data->getColumnMeta($i);(5) echo $meta['name']." ";(6) }(7) echo PHP_EOL;(8) foreach($data as $row) {(9) foreach($row as $val) {(10) echo $val." ";(11) }(12) echo PHP_EOL;(13)}
id name pw groupID 0 root bitte! 0 1 user danke! 0 2 helmut sdgafg 0 3 evelyn tztrff 0
Es wird eine Überschrift für eine Tabelle aus der Datenbank generiert.
Dann werden in einer doppelten for-Schleife die Zeilen ausgegeben: jeweils eine Zeile mit mehreren Einträgen.
37Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Auslesen der Meta-Information III – Ganze Tabelle
(1) $data= $db->query($query);(2) $limit= $data->columnCount();(3) for($i= 0;$i<$limit;$i++) {(4) $meta= $data->getColumnMeta($i);(5) printf('%10s ',$meta['name']);(6) }(7) echo PHP_EOL;(8) foreach($data as $row) {(9) foreach($row as $val) {(10) printf('%10s ',$val);(11) }(12) echo PHP_EOL;(13)}
Damit es etwas schöner aussieht, wird zur Ausgabe die Routine printf() verwendet, die einen Format-String zur Definition des Layouts erlaubt.
Die einzelnen Optionen des ersten Parameters von printf() sind http://de3.php.net/manual/de/function.printf.php zu entnehmen.
id name pw groupID 0 root bitte! 0 1 user danke! 0 2 helmut sdgafg 0 3 evelyn tztrff 0
38Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Nun endlich zum Login I
(1) $LoginName= 'root'; // Simulation des Formulars(2) $LoginPW= 'bitte!';(3) $query= "SELECT * FROM account WHERE name='$LoginName'";(4) $data= $db->query($query);(5) $rows= $data->fetchAll();(6) echo count($rows).PHP_EOL;(7) var_export($rows);(8) if((count($rows)==1)&&($LoginPW==$rows[0]['pw'])) {(9) echo "loggt in".PHP_EOL;(10)} else {(11) echo "NOT loggt in".PHP_EOL;(12)}
$LoginName und $LoginPW kommen aus dem Formular, was hier durch die Zuweisungen simuliert wird.
Beachten Sie die Abfrage in (8): es muss ein einziger Eintrag da sein (count()) und das PW muss stimmen.
1array ( 0 => array ( 'id' => '0', 'name' => 'root', 'pw' => 'bitte!', 'groupID' => '0', ),)loggt in
39Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Nun endlich zum Login II
(1) $LoginName= 'root';(2) $LoginPW= 'bitte!';(3) $query= "SELECT * FROM account WHERE name='$LoginName'";(4) $data= $db->query($query);(5) $rows= $data->fetch();(6) echo gettype($rows).PHP_EOL;(7) var_export($rows);(8) if(($rows!==false)&&($LoginPW==$rows['pw'])) {(9) echo "loggt in".PHP_EOL;(10)} else {(11) echo "NOT loggt in".PHP_EOL;(12)}
Das ganze geht auch mit einem fetch(), nur dass der Datentyp des Ergebnisses anders ist. Das zeigt sich in der Abfrage.
var_export($rows) gibt die Struktur des Ergebnisses aus.
arrayarray ( 'id' => '0', 'name' => 'root', 'pw' => 'bitte!', 'groupID' => '0',)loggt in
40Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Alternative zum Füllen der Tabelle I
(1) $insert = "INSERT INTO account(id,name,pw,groupID)";(2) $insert.= "VALUES(?,?,?,?)";(3) $stmt= $db->prepare($insert);(4) $id= 0; $name= 'root'; $pw= 'bitte!'; $groupID= 0;(5) $stmt->bindValue(1,$id, PDO::PARAM_INT);(6) $stmt->bindValue(2,$name, PDO::PARAM_STR);(7) $stmt->bindValue(3,$pw, PDO::PARAM_STR);(8) $stmt->bindValue(4,$groupID,PDO::PARAM_INT);(9) $stmt->execute();
Bei den gebundenen Parametern wird eine Schablone (1-2) erstellt, die dann dem Datenbank-Server mitgeteilt wird (3).
In der Schablone wird an die Parameter-Positionen ein ? gesetzt. In der Reihenfolge der ? werden die Parameter per bindValue(), als Wertekopie,
den ? mit einer Typangabe (z.B. PDO::PARAM_INT) zugeordnet. Beim execute() werden diese Werte zum Datenbank-Server geschickt, das SQL-
Statement dort ausgefüllt und dann ausgeführt.
id name pw groupID 0 root bitte! 0
41Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Alternative zum Füllen der Tabelle II
Das ist das allgemeine Schema bei der Benutzung von gebundenen Parametern.
Neben dem schon eingeführten bindValue() gibt es noch das bindParam(), das die Bindung über eine Referenz realisiert.
Neben den beiden Typen für String und Integer gibt es noch den Typ Boolean. Siehe dazu: http://www.php.net/manual/en/pdostatement.bindparam.php
Schablone erzeugen
Parameter zuordnen
Ausführen
bindValue()bindParam()
Integer: PDO::PARAM_INTString: PDO::PARAM_STRBool: PDO::PARAM_BOOL
42Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Alternative zum Füllen der Tabelle III
(1) $insert = "INSERT INTO account(id,name,pw,groupID)";(2) $insert.= "VALUES(?,?,?,?)";(3) $stmt= $db->prepare($insert);(4) $stmt->bindParam(1,$id, PDO::PARAM_INT);(5) $stmt->bindParam(2,$name, PDO::PARAM_STR);(6) $stmt->bindParam(3,$pw, PDO::PARAM_STR);(7) $stmt->bindParam(4,$groupID,PDO::PARAM_INT);
(8) foreach($val as $row) {(9) $id= $row['id'];(10) $name= $row['name'];(11) $pw= $row['pw'];(12) $groupID= $row['groupID'];(13) $stmt->execute();(14)}
id name pw groupID 0 root bitte! 0 1 user danke! 0 2 helmut sdgafg 0 3 evelyn tztrff 0 Das Hash $val ist wie oben gesetzt.
43Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Alternative zum Füllen der Tabelle IV - Bemerkungen
(3) $stmt= $db->prepare($insert);(4) $stmt->bindParam(1,$id, PDO::PARAM_INT); ...(8) foreach($val as $row) {(9) $id= $row['id'];(10) $name= $row['name'];(11) $pw= $row['pw'];(12) $groupID= $row['groupID'];(13) $stmt->execute();(14)}
In der Schleife wird immer wieder auf die gebundenen Variablen zugegriffen. Bei bindValue() wird der Wert zum Zeitpunkt des Bindens fest dem ? zugeordnet. Bei bindParam() wird der aktuelle Wert zum Zeitpunkt des execute() benutzt.
$val[]=array('id'=>0,'name'=>'root', 'pw'=>'bitte!','groupID'=>0);$val[]=array('id'=>1,'name'=>'user', 'pw'=>'danke!','groupID'=>0);
44Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Alternative zum Füllen der Tabelle V
Das Hash $val ist wie oben gesetzt.
(1) $insert = "INSERT INTO account(id,name,pw,groupID)";(2) $insert.= "VALUES(:id,:name,:pw,:groupID)";(3) $stmt= $db->prepare($insert);(4) $stmt->bindParam(':id', $id, PDO::PARAM_INT);(5) $stmt->bindParam(':name', $name, PDO::PARAM_STR);(6) $stmt->bindParam(':pw', $pw, PDO::PARAM_STR);(7) $stmt->bindParam(':groupID',$groupID,PDO::PARAM_INT);
(8) foreach($val as $row) {(9) foreach($row as $key=>$val) {(10) $$key= $val;(11) }(12) $stmt->execute();(13)}
Mit einem Doppelpunkt eingeleitet lassen sich die ? wie Variablen adressieren.
45Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Alternative zum Füllen der Tabelle VI - Erläuterung
(8) foreach($val as $row) {(9) foreach($row as $key=>$val) {(10) $$key= $val;(11) }(12) $stmt->execute();(13)}
$val[]=array('id'=>0,'name'=>'root', 'pw'=>'bitte!','groupID'=>0);$val[]=array('id'=>1,'name'=>'user', 'pw'=>'danke!','groupID'=>0);
Hier wird der Name der Variablenanhand des Wertes einer anderenVariablen bestimmt!
Auszug vom Setzen des Hash $val:
So etwas geht nur inSkriptsprachen.
46Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
SQL Injection I - Vorbereitung
(1) $LoginName= 'root'; $LoginPW= 'bitte!';(2) $update= "UPDATE account SET pw = '*balla*' WHERE id = 0";(3) $data= $db->exec($update);
id name pw groupID 0 root *balla* 0 1 user danke! 0 2 helmut sdgafg 0 3 evelyn tztrff 0
Zur Vorbereitung einer kleinen Demo für SQL-Injection wird ein Update ausprobiert.
SQL-Updates werden ganz analog zum Insert – also auch mit gebundenen Parametern - ausgeführt.
47Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
SQL Injection II
(1) $LoginPW= 'bitte!';(2) $update= "UPDATE account SET pw = '*balla*' WHERE id = 0";(3) $LoginName= "a';$update";(4) $query= "SELECT * FROM account WHERE name='$LoginName'";(5) echo $query.PHP_EOL;(6) $data= $db->query($query);(7) $rows= $data->fetch();(8) if(($rows!==false)&&($LoginPW==$rows['pw'])) {(9) echo "loggt in".PHP_EOL;(10)} else {(11) echo "NOT loggt in".PHP_EOL;(12)}
Statt eines vernünftigen Loginnamens wird wie in (3) simuliert ein Mischung aus einem Namen und einer SQL-Anweisung – hier ein Update – eingegeben. Die Zusammensetzung ist unten in 2 Zeilen dargestellt.
SELECT * FROM account WHERE name='a';UPDATE account SET pw = '*balla*' WHERE id = 0'Ausgabe(5):
Quotes
48Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
SQL Injection III - Abwehr
(1) $LoginPW= 'bitte!';(2) $update= "UPDATE account SET pw = '*balla*' WHERE id = 0";(3) $LoginName= "a';$update";(4) $LoginName= $db->quote($LoginName);(5) $query= "SELECT * FROM account WHERE name=$LoginName";(6) echo $query.PHP_EOL;
quote() fügt vor allen "gefährlichen" Zeichen ein Backslash ein, der die Funktion des Zeichens ausschaltet. Daher ist der Loginname der gesamte unterstrichene Bereich. Es wird kein weiteres SQL-Kommando ausgeführt.
Zum Glück – oder aus Absicht – ist MySQL so konfiguriert, dass auch ohne quote() nichts passiert wäre, da jeweils nur ein Kommando ausgeführt werden kann.
Es müssen trotzdem immer Formulareingaben vor dem Einsetzenin SQL-Strings geprüft werden!
SELECT * FROM account WHERE name='a\';UPDATE account SET pw = \'*balla*\' WHERE id = 0'
Ausgabe:KeineQuotes
49Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
SQL Injection IV – Beste Abwehr
(1) $LoginPW= 'bitte!';(2) $update= "UPDATE account SET pw = '*balla*' WHERE id = 0";(3) $LoginName= "a';$update";(4) $query= "SELECT name, pw FROM account WHERE name=:LoginName";(5) $stmt= $db->prepare($query);(6) $stmt->bindValue(':LoginName',$LoginName,PDO::PARAM_STR);(7) $stmt->execute();(8) $stmt->bindColumn('name',$RealName,PDO::PARAM_STR);(9) $stmt->bindColumn('pw' ,$RealPW, PDO::PARAM_STR);(10)$rows= $stmt->fetch();(11)echo $RealName." ".$RealPW.PHP_EOL;(12)if($LoginPW==$RealPW) {(13) echo "loggt in".PHP_EOL;(14)...
Die Benutzung von Prepared Statements ist am Besten, da die Formulardaten immer als Daten behandelt werden.
50Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
SQL Injection V – Erläuterungen
(4) $query= "SELECT name, pw FROM account WHERE name=:LoginName";(6) $stmt->bindValue(':LoginName',$LoginName,PDO::PARAM_STR);(8) $stmt->bindColumn('name',$RealName,PDO::PARAM_STR);(9) $stmt->bindColumn('pw' ,$RealPW, PDO::PARAM_STR);
Spaltennamen von erzeugten temporären Tabellen (durch SELECT) lassen sich auch PHP-Variablen zuordnen.
bindColumn() verbindet den Namen einer SQL-Spalte mit einer Variablen. Nach jedem fetch() werden die aktuellen Werte der gelesenen Zeile den
Variablen zugewiesen. Der Angriff via SQL-Injection funktioniert nun nicht mehr, da das SQL-
UPDATE als Datum in der WHERE-Klausel behandelt wird.
Das ändert nichts daran, alle Formulardaten zu prüfen!
51Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Transaktionen I
(1) $db->beginTransaction();(2) $update= "UPDATE account SET pw = '*balla*' WHERE id = 0";(3) $data= $db->exec($update);(4) $update= "UPDATE account SET pw = '*' WHERE id = 3";(5) $data= $db->exec($update);(6) var_dump($data);(7) $update= "UPDATE account SET pw = '??' WHERE id = 4";(8) $data= $db->exec($update);(9) var_dump($data);(10)$db->commit();(11)//$db->rollBack();
Mit beginTransaction() wird eine Transaktion eingeleitet und mit commit() positiv abgeschlossen; erst dann sind die Daten geändert sichtbar.
Mit rollBack() werden alle Änderungen verworfen. Der Rückgabewert von UPDATE ist die Anzahl der betroffenen Zeilen.
Fehlerhaftes UPDATE
52Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Transaktionen II
int(1)int(0) id name pw groupID 0 root *balla* 0 1 user danke! 0 2 helmut sdgafg 0 3 evelyn * 0
int(1)int(0) id name pw groupID 0 root bitte! 0 1 user danke! 0 2 helmut sdgafg 0 3 evelyn tztrff 0
commit()
rollBack()
Fehlerhaftes UPDATE
53Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Tipps I – Globales Definieren
Globale Parameterwerte sollten in eine Include-Datei mit der Endung ".inc.php" ausgelagert und dann als "Kopf" eingebunden werden.
Aus Sicherheitsgründen sollte die Endung immer ".php" sein (was für ".inc.php" ja gilt), da der Server diese Datei nie(?) uninterpretiert zum Browser sendet.
<?php $Param['host']= 'localhost'; $Param['uid'] = 'nobody'; $Param['pass']= 'bitte!'; $Param['db'] = 'account';
$Param['dsn'] = "mysql:host=${Param['host']}"; $Param['dsn'].= ";dbname=${Param['db']}";?>
54Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL
Tipps II – Benutzung der globalen Definitionen
Durch die Auslagerung der Parameter in eine externe Datei wird der Code noch allgemeingültiger und dadurch leichter zu warten.
Die Optionen könnten in einem weiteren Schritt auch ausgelagert werden.
(1) require('myParameter.inc.php'); (2) $DB_options = array((3) PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'(4) ); (5) try {(6) $db= new PDO($Param['dsn'],$Param['uid'],(7) $Param['pass'],$DB_options);