55
24.10.17 1 Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL Webtechnologien Teil 8: PDO/MySQL

Webtechnologien Teil 8: PDO/MySQL - Wirtschaftsinformatikwi.f4.htw-berlin.de/.../Folien/WT-08/08-WT-PHP-III-PDO-1.pdf · Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL 3 Einführung

  • Upload
    lycong

  • View
    214

  • Download
    0

Embed Size (px)

Citation preview

24.10.17 1Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL

Webtechnologien

Teil 8: PDO/MySQL

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.

16Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL

Einrichten einer Datenbank I

17Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL

Einrichten einer Datenbank II

Es hat geklappt!

Festlegen derKodierung

18Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL

Einrichten einer Datenbank III

19Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL

Einrichten einer Datenbank III

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);

55Webtechnologien – WS 2017/18 - Teil 8/PHP und MySQL

Nach dieser Anstrengung etwas Entspannung...