einfache Datenbankklasse ( PHP Data Objects-Erweiterung (PDO) )

  • Ziel:
    eine einfache Datenbankklasse erstellen, über die so einfach wie möglich Anfragen (Queries) an den Datenbankserver geschickt werden können.
    Dabei sollen Platzhalter verwendet werden können, um SQL-Injections vermeiden zu können.


    Verwendete Techniken:
    PHP, PDO (siehe: http://php.net/manual/de/book.pdo.php)


    Schwierigkeitsgrad:
    für Anfänger geeignet, Grundkenntnisse über die Funktionsweisen von PHP, OOP und SQL vorausgesetzt


    Fehlerbehandlung:
    die Klasse wirft Exceptions, welche mit einem try/catch -Block abgefangen werden sollten.


    Was soll die Klasse können?
    Einfaches Verbinden zum Datenbankserver und Ausführen der Sql-Abfragen.


    - Erweiterung "Persistente Verbindung" und Erweiterung "Initial Commands"
    (wird evtl erweitert)


    Struktur
    Da wir die DB-Klasse ständig verfügbar haben wollen,
    ohne jedes Mal eine Instanz z.B. als Funktionsargument übergeben zu müssen,
    sind die Attribute (Variablen) und Methoden (Funktionen) unserer Klasse statisch (Schlüsselwort static).


    Die Klasse und ihre Methoden (zur Übersicht erstmal ohne Inhalt):


    Die methode ::connect()


    Die methode ::close()

    PHP
    1. public static function close()
    2. {
    3. self::$dbh = null;
    4. // wir setzen $dbh auf den wert NULL,
    5. // wodurch der __destruct() der in PHP vordefinierten klasse PDO ausgeführt wird
    6. }


    Die methode ::exe()


    Die methode ::lastInsertId()

    PHP
    1. public static function lastInsertId()
    2. {
    3. // hier gibts nicht viel zu tun.
    4. // die in PHP vordefinierte klasse PDO stellt uns diese methode zur verfügung:
    5. return self::$dbh->lastInsertId();
    6. }


    Das wars. Hier die ganze Klasse nocheinmal ohne Kommentare:


    Beispiele:


    einen Fehler beim Verbindungsaufbau provozieren und fangen (catch-block)

    PHP
    1. try{
    2. Sql::connect('localhost', 'root', 'einFalschesPasswort', 'datenbank_name');
    3. }catch(Exception $e){
    4. echo $e->getMessage();
    5. }


    Ausgabe:

    Code
    1. SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: YES)




    einen Fehler bei Ausführen einer Query provozieren und fangen (catch-block)
    (Vorraussetzung natürlich eine gültige Verbindung)

    PHP
    1. try{
    2. Sql::connect('localhost', 'benutzername', 'password');
    3. $sql = "SELECT irgendwas falsches;";
    4. $rows = Sql::exe($sql);
    5. var_dump($rows);
    6. }catch(Exception $e){
    7. echo $e->getMessage();
    8. }


    Ausgabe:

    Code
    1. (SQLSTATE: 42S22) eMessage: Unknown column 'irgendwas' in 'field list' (eCode: 1054)query: SELECT irgendwas falsches; para:




    Bsp für eine Query ohne Parameter:

    PHP
    1. try{
    2. Sql::connect('localhost', 'benutzername', 'password');
    3. $sql = "SELECT `table_schema` AS `Datenbankname` FROM `information_schema`.`tables` GROUP BY `table_schema`;";
    4. $rows = Sql::exe($sql);
    5. echo "<pre>";
    6. var_dump($rows);
    7. }catch(Exception $e){
    8. echo $e->getMessage();
    9. }


    Ausgabe (Bsp):




    Bsp für eine Query mit Parameter:


    Ausgabe (Bsp):




    Und zuletzt nich ein unkommentiertes Beispiel:

    Dieser Beitrag wurde bereits 4 Mal editiert, zuletzt von cottton () aus folgendem Grund: ERGÄNZUNG 18.03.2015 ->bindParam und floats

  • Hatte die halbe Einleitung von lauras geklaut :p
    Es gibt ja Anfänger und Anfänger. Ich würde sagen, wenn jemand mit Sql Queries klar kommt, dann wird er auch schon durch sehen.
    EDIT: btw - hier ist noch nix mit "persistent connection". Wollte ich erstmal außen vor lassen. Mach ich evtl morgen.
    Erweiterung "Persistente Verbindung"
    Was ist das?


    (Details siehe: http://php.net/manual/de/pdo.connections.php#example-869 (Beispiel #4 Persistente Verbindungen))


    Was bringt mir das?
    Kurz und einfach:
    Schnellere Reaktionszeiten der Webseite.
    Denn ist beim Seitenaufruf der Verbindungsaufbau zum Sql-Server "schneller", ist das Script eher abgearbeitet.


    Ändern der Sql::connect() Methode für Persistente Verbindungen:
    Als erstes brauchen wir einen neuen parameter $pers_con
    Dieser ist standardmäßig false, da es sich hier um eine zusätzliche Option handelt.

    PHP
    1. public static function connect($host, $username, $password, $database=null, $pers_con=false)


    In der Methode ::connect() geben wir nun dem __construct der in PHP vordefinierten klasse PDO
    (siehe: http://php.net/manual/de/pdo.construct.php)
    einen vierten Parameter mit.
    Genauer genommen übergeben wir ein Array (array $options) über die wir gleich mehrere Parameter übergeben können.


    Das Array:

    PHP
    1. array(
    2. PDO::ATTR_PERSISTENT => $pers_con
    3. )


    Was wir hier übergeben ist die vordefinierte Konstante PDO::ATTR_PERSISTENT als Schlüssel des Arrays,
    und die Variable $pers_con als Wert, die wir über unseren fünften, neuen Parameter bekommen.
    (Eine Liste der Vordefinierten Konstanten gibt es hier: http://php.net/manual/de/pdo.constants.php,
    und speziell für Mysql-Funktionen hier: http://php.net/manual/de/ref.pdo-mysql.php)


    Einfügen/Anhängen der neuen Option beim Verbindungsaufbau:
    Die Kommentare "NEU: array $options" können natürlich weggelassen werden


    Das wars.
    Jetzt muss nur noch beim Verbindungsaufbau der neue Parameter (ein boolean true) mitgegeben werden,
    falls wir eine Persistente Verbindung wünschen:

    PHP
    1. try{
    2. Sql::connect('localhost', 'benutzername', 'password', 'database', true);
    3. }catch(Exception $e){
    4. die('Verbindung konnte nicht hergestellt werden. Fehler: ' . $e->getMessage());
    5. }


    Wäre dann nur noch der Fall, dass man beim Verbindungsaufbau keine Datenbank angeben möchte.
    Dann gibt man momentan einfach den Wert NULL (oder false) an:

    PHP
    1. try{
    2. Sql::connect('localhost', 'benutzername', 'password', null, true);
    3. }catch(Exception $e){
    4. die('Verbindung konnte nicht hergestellt werden. Fehler: ' . $e->getMessage());
    5. }


    Es ist aber auch möglich die Reihenfolge der Parameterübernahme in der Methode ::connect() zu ändern:

    PHP
    1. // vertauschen der parameter $database <==> $pers_con
    2. public static function connect($host, $username, $password, $pers_con=false, $database=null)


    Der Verbindungsaufbau sieht dann so aus:

    PHP
    1. try{
    2. Sql::connect('localhost', 'benutzername', 'password', true);
    3. }catch(Exception $e){
    4. die('Verbindung konnte nicht hergestellt werden. Fehler: ' . $e->getMessage());
    5. }



    _________________________________________________________________________


    Erweiterung "Initial Commands"
    Was ist das?

    http://php.net/manual/de/ref.pdo-mysql.php schrieb:


    Command to execute when connecting to the MySQL server. Will automatically be re-executed when reconnecting.


    Note, this constant can only be used in the driver_options array when constructing a new database handle.


    Frei übersetzt heißt das: beim Verbindungsaufbau (und nur dann) können wir Befehle mitgeben.


    Was bringt mir das?
    Kurz und einfach:
    Schnellere Reaktionszeiten der Webseite.
    Warum?
    Würden wir die Befehle nach dem Verbindungsaufbau als einzellne Queries an den MySQL-Server senden,
    dann hätten wir für jede dieser Queries:
    - php prüft und sendet die Query (Packet) an den MySQL-Server
    - MySQL-Server empfängt und prüft die Query (Packet), führt sie aus und sendet das Ergebnis zurück
    - php empfängt und prüft das Ergebnis (Packet)



    Ändern der Sql::connect() Methode für Initial Commands:
    Als erstes brauchen wir einen neuen parameter $init_cmd
    Dieser ist standardmäßig null, da es sich hier um eine zusätzliche Option handelt.
    null anstatt false:
    der Parameter $pers_con stellte einen Zustand (ein/aus) dar,
    der Parameter $init_cmd kann aber kann Inhalte transportieren, oder nichts (also null)

    PHP
    1. public static function connect($host, $username, $password, $database=null, $pers_con=false, $init_cmd=null)


    In der Methode ::connect() geben wir nun dem __construct der in PHP vordefinierten klasse PDO
    (siehe: http://php.net/manual/de/pdo.construct.php)
    dem vierten Parameter (dem Array (array $options)) eine weitere Ebene ("Schlüssel => Wert" Paar) mit:

    Das Array:

    PHP
    1. array(
    2. PDO::ATTR_PERSISTENT => $pers_con // bekannt von: Erweiterung "Persistente Verbindung",
    3. PDO::MYSQL_ATTR_INIT_COMMAND => $init_cmd // hier unsere Erweiterung für Initial Commands
    4. )


    Was wir hier übergeben ist die vordefinierte Konstante PDO::MYSQL_ATTR_INIT_COMMAND als Schlüssel des Arrays,
    und die Variable $init_cmd als Wert, die wir über unseren sechsten, neuen Parameter bekommen.


    Einfügen/Anhängen der neuen Option beim Verbindungsaufbau:
    Die Kommentare "NEU: $init_cmd" können natürlich weggelassen werden


    Das wars.
    Jetzt muss nur noch beim Verbindungsaufbau der neue Parameter (ein String) mitgegeben werden,
    falls wir Initial Commands nutzen möchten:

    PHP
    1. try{
    2. Sql::connect('localhost', 'benutzername', 'password', 'database', true, "sql_mode = 'STRICT_ALL_TABLES';");
    3. }catch(Exception $e){
    4. die('Verbindung konnte nicht hergestellt werden. Fehler: ' . $e->getMessage());
    5. }


    Wie bei Erweiterung "Persistente Verbindung" kann man die Reihenfolge der Parameter anpassen.

    Was ist "sql_mode = 'STRICT_ALL_TABLES'"; ?
    sql_mode ist eine Variable des MySQL-Servers.
    Hier setzen wir also die Variable sql_mode auf den Wert STRICT_ALL_TABLES.
    Was passiert dann?

    http://dev.mysql.com/doc/refman/5.1/en/sql-mode.html#sqlmode_strict_all_tables schrieb:


    Enable strict mode for all storage engines. Invalid data values are rejected. Additional details follow.


    Falsch Daten werden also zurückgewiesen.
    Was sind Falsche Daten?
    Bsp: (Voraussetzung: wir haben sql_mode auf 'STRICT_ALL_TABLES' gesetzt)
    Wir erstellen eine Tabelle `test`.
    In dieser Tabelle erstellen wir eine Spalte `zahl` vom Typ int().
    Sollten wir nun "ausversehen" eine Query an den MySQL-Server senden, die einen Buchstaben "a" als Wert für `zahl` setzt,
    bekommen wir einen Fehler: "Error Code: 1366. Incorrect integer value: 'a' for column 'zahl' at row 1".
    Der MySQL-Server hindert uns jetzt also daran einen falschen Wert einzutragen (und das ist auch gut so!).


    Was passiert ohne 'STRICT_ALL_TABLES'?
    Wir würden dem Feld `zahl` einen Buchstaben aufdrücken, was aber nicht funktioniert.
    Die Zeile würde eingefügt werden, allerdings mit dem Wert (Typ) NULL.
    Der MySQL-Server würde nicht meckern, gibt ein ~"ok, hab fertig", und wir wissen nicht, was wir falsch gemacht haben.


    Auch zu lange Werte werden abgewiesen. Wenn zB ein Feld `buchstabe` ein Wert "abc" zugewiesen bekommen soll.
    Dann meldet sich er MySQL-Server mit der Fehlermeldung: "Error Code: 1406. Data too long for column 'buchstabe' at row 1".


    Es mach also Sinn, und das Fehler finden einfacher.
    genaueres zu sql_mode: http://dev.mysql.com/doc/refma…bles.html#sysvar_sql_mode



    Empfehlung:


    Was sind das für Anweisungen?
    sql_mode = 'STRICT_ALL_TABLES': bereits darüber beschrieben
    NAMES 'utf8': (siehe: http://dev.mysql.com/doc/refma…n/charset-connection.html)
    SET CHARSET 'utf8': (siehe: http://dev.mysql.com/doc/refma…n/charset-connection.html)

    Dieser Beitrag wurde bereits 2 Mal editiert, zuletzt von cottton () aus folgendem Grund: ERGÄNZUNG 18.03.2015 - MYSQL_ATTR_INIT_COMMAND

  • Hey,
    ich hätte grundsätzlich das $options Array der persistenten Verbindung mitgegeben:

    PHP
    1. $options = array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8');

    Und dann halt ggf. für die persistente Verbindung die Option für Persistenz eingefügt

    PHP
    1. $options[PDO::ATTR_PERSISTENT] = $pers_con;