PDOStatement -> bindParam() generieren

Matze202

Erfahrenes Mitglied
Hiho @all,

ich müsste zur Fehleranalyse den SQL-Code mir ausgeben lassen, der bei dem ausführen von dem execute(), an die Datenbank gesendet wird.

Ich hoffe, dass dies möglich ist, weil ich bisher nichts dazu gefunden habe.

Ich bräuchte dies, weil ich sicherstellen möchte, dass die bindParam(), welche ich generiert habe, auch richtig funktionieren, denn trotz der Eingabe von Daten, welche in der Datenbank vorhanden sind, bekomme ich kein Ergebnis, aber auch keine Fehlermeldung, sondern nur ein false, dass er nichts gefunden hat.

Gruß Matze202.

EDIT: Schon oft und jetzt auch wieder Stundenlange Suche und nix gefunden, aber nach dem Posten, bin ich auf den Beitrag von @Yaslaw gestoßen. Was ich erstmal austesten werde, aber wenn jemand noch andere Lösungen dazu kennt, wäre ich auch Dankbar, diese hier zu erfahren. ;)
Link zum Beitrag von Yaslaw
EDIT3: Dies habe ich mir erstmal aus dem Kopf geschlagen:

EDIT2: Irgndwie gelingt es mir leider nicht, dieses in mein Datenbank-Objekt zu integrieren, dass die Objekt-Funktionen, welche direkt mit der Datenbank kommunizieren, den SQL-Code ausgeben.

Ich bin mir aber inzwischen fast sicher, dass irgendwas bei dem generieren der Platzhalter nicht korrekt ist, daher sende ich euch hier mal ein paar Funktionen.

Diese Funktion, soll den SQL-Code zusammensetzen und die dazugehörigen bindParam()-Funktionen generieren, sowie das absenden des SQL-Codes und ausgeben des Ergebnis.
PHP:
  function load_string($table, $cell, $where = array('', '', 0, 0, 0)){
    $sql = "SELECT :sc FROM :st WHERE ";
    $sql .= self::check_where_for_sql($where);
    $stmt = self::$_db->prepare($sql);
    $table = self::$_prefix.$table;
    $stmt->bindParam(":sc", $cell, self::pdo_param(0), 20);
    $stmt->bindParam(":st", $table, self::pdo_param(0), 20);
    $ar = self::check_bind_param($where);
    foreach($ar as $v){
      if(is_array($v)){
        $a = $v[0];
        $b = $v[1];
        $c = $v[2];
        $d = $v[3];
        $e = $v[4];
        $f = $v[5];
        $stmt->bindParam($a, $b, self::pdo_param(0), 20);
        if($f != 0){
          $stmt->bindParam($c, $d, self::pdo_param($e), $f);
        }else{
          $stmt->bindParam($c, $d, self::pdo_param($e));
        }
      }
    }
    $stmt->execute();
    $result = $stmt->fetch(PDO::FETCH_ASSOC);
    return $result[$cell];
  }

var_dump($sql);
SELECT :sc FROM :st WHERE :c = :w OR :c_ccccc = :w_wwwww
var_dump($v);
array (size=6)
0 => string ':c' (length=2)
1 => string 'key' (length=3)
2 => string ':w' (length=2)
3 => string 'title' (length=5)
4 => int 0
5 => int 5

array (size=6)
0 => string ':c_ccccc' (length=8)
1 => string 'id' (length=2)
2 => string ':w_wwwww' (length=8)
3 => int 1
4 => int 1
5 => int 11

Hier wird der Where-Teil des SQL-Code mit Platzhaltern zusammengestellt, die Funktion check_and_or() tut nur entsprechend das AND bzw. OR einfügen, daher gehe ich nicht auf diese Funktion ein, weil diese meiner Meinung nach, nichts mit dem Problem zu tun hat. Die Zeilen 7-16 dieser Funktion, sollen die Platzhalter generieren, wo ich denke, dass darin das Problem steckt. Ich habe die Platzhalter erst mit der Art :c0, :c5 etc. probiert, da ich bei meiner Recherche feststellte, dass diese sonst nie mit Zahlen versehen waren, habe ich es versucht, diese mit einem Unterstrich zu unterscheiden um besser eindeutig darzustellen, dass es keine doppelten geben kann, auch wenn aus endlos-tiefen Array´s generiert werden. Die Platzhalter mit dem Unterstrich und ohne Zahlen werden zwar sehr lang und sind genauso eindeutig, wie die mit Zahlen, aber es werden immer noch keine Einträge der Datenbank gefunden.
PHP:
  private function check_where_for_sql($data, $c = 'c', $w = 'w'){
    $sql = null;
    for($i = 0; $i < count($data); $i++){
      if(is_array($data[$i])){
        $sql .= '('.self::check_where_for_sql($data, $c.'c', $w.'w').')';
      }else{
        $c_p = ':'.$c;
        $w_p = ':'.$w;
        if($i != 0){
          $c_p .= '_';
          $w_p .= '_';
          for($a = 0; $a < $i; $a++){
            $c_p .= 'c';
            $w_p .= 'w';
          }
        }
        $sql .= $c_p.' = '.$w_p;
        $i = $i + 4;
        if(isset($data[$i])){
          $sql .= self::check_and_or($data[$i]);
        }
      }
    }
    return $sql;
  }

Diese Funktion soll ebenso, wie die vorherige, die Platzhalter generieren, damit diese dann aber nicht wie bei der vorherigen in den SQL-Code, sondern in die bindParam()-Funktion eingefügt werden können.
PHP:
  private function check_bind_param($data, $c = 'c', $w = 'w'){
    $ar = array();
    for($i = 0; $i < count($data); $i++){
      if(is_array($data[$i])){
        $ar[] = self::check_bind_param($data, $c.'c', $w.'w');
      }else{
        $c_p = ':'.$c;
        $w_p = ':'.$w;
        if($i != 0){
          $c_p .= '_';
          $w_p .= '_';
          for($a = 0; $a < $i; $a++){
            $c_p .= 'c';
            $w_p .= 'w';
          }
        }
        $ar[] = array($c_p, $data[$i], $w_p, $data[$i+1], $data[$i+2], $data[$i+3]);
        $i = $i+4;
      }
    }
    return $ar;
  }

Diese Funktion, soll später eine komplette Liste, aller möglichen PDO:: PARAM... Vorgaben enthalten.
PHP:
  private function pdo_param($i = 0){
    switch($i){
      case 0:
        return PDO::PARAM_STR;
      case 1:
        return PDO::PARAM_INT;
    }
  }

Abfragen tu ich dies mit folgender Zeile zum testen:
PHP:
echo $db->load_string('_lang', 'value', array('key', 'title', 0, 5, 2, 'id', 1, 1, 11, 0));
Der Eingabewert '_lang wird von einem Prefix und besagt die Tabelle, 'value' beinhaltet das gesuchte Ergebnis, in dem Array befinden sich alle Werte, die für das WHERE benötigt werden, 5 Werte ergeben eine Abfrage. Die 1. Stelle der WHERE-Teils ist die Spalte der Datenbank-Tabelle, danach kommt der Inhalt, welche in dieser sich befinden soll, danach kommt der Wert für die pdo_param()-Funktion, dann wieviele Stellen dieser Eingabewert haben darf und danach eine 0, wenn kein Wert mehr folgt und wenn doch einer folgt, ob dieser mit einem AND (Wert: 1) oder OR (Wert: 2) verbunden werden soll. Ungetestet ist noch, aber was meines erachtens funktionieren sollte, ist dass man beliebig viele Array mit je 5 Wert-Blocks dieser Werte, darin verschachten kann und diese sollten dann alle funktionell in den SQL-Code mit Platzhaltern eingebaut werden. Aber wesentlicher ich mir erstmal, dass die Datenbank überhaupt einen Wert, über dieses Script ausspuckt.

Mir ist zwar klar, dass ich damit den SQL-Code stark eingrenze, aber ich finde diese Benutzung sehr handlich und bevor ich die Hintergründe weiter entwickel, werde ich natürlich noch weitere Funktionen, dieser Datenbankanbindung optimieren und erweitern.

Also es gibt momentan keine Fehlermeldungen, an die ich mich halten kann, ich habe aber die Platzhalter unter verdacht, dass die bindParam()-Funktion keinen Platz findet, wo die Daten eingefügt werden sollen, obwohl diese meiner Meinung nach eindeutig sind.

Ich hoffe, dass mir jetzt jemand helfen kann.
 
Zuletzt bearbeitet:
hiho @all,

weil ich leider auf folgendes:
PDOStatement -> bindParam() generieren
noch keine Antwort habe, wollte ich mir erstmal wie folgt behelfen.

Teile der mysql (PDO) Klasse:
PHP:
class mysql{
  ...
  private static $_db;
  ...
PHP:
  function __construct(){
    try{
      self::$_db = new PDO("mysql:host=".self::$_db_host.";dbname=".self::$_db_name, self::$_db_user,self::$_db_pass);
    } catch (PDOException $e){
      echo "Datenbankeverbindung gescheitert";
      die();
    }
  }

Teil der index.php, wo die Objekte angelegt werden:
PHP:
$db = new mysql();
$main = new main($db);

Teile der main Klasse:
PHP:
class main{
  private static $db;
  ...
 
  public function __construct($d){
    self::$db = $d;
  }
PHP:
  function load_default_language(){
// Auf die nächste Zeile, wird der Fehler angezeigt:
    $stmt = self::$db->prepare("SELECT id, lang, staat, slang FROM ".self::$db->prefix()."_language WHERE default = 1");
    $stmt->execute();
    $result = $stmt->fetch_assoc();
    var_dump($result);
  }

Der Grund, warum ich die Datenbankklasse, nicht in die main Klasse vererben wollte, ist dass man ja nur direkt zu einander gehörende Klassen vererben sollte, so wie ich es verstanden habe.

Aber die Main soll die Grundeinstellungen der Website verwalten und die DB soll sich um die Datenbank kümmern.

Wo ist hier mein Denkfehler, dass ich den Fehler bekomme und nicht die PDO-Funktionen übernehmen kann?

Gruß Matze202.
 
Ich nehme jetzt mal nur Bezug auf deinen zweiten Post hier. Wenn ich das richtig sehe, ist die Abhängigkeitskette folgende:

$main->$db->$_db->PDO , du versuchst aber direkt auf $db zu arbeiten. Das funktioniert natürlich nicht, da deine mysql-Klasse ja nicht von PDO abgeleitet ist oder die Methoden sonst irgendwie delegiert, sondern selber nur ein Datenbankhandle ($_db) beinhaltet. Mir persönliche wäre für einfache Anwendungen die Abstraktion, die PDO macht, schon genug... Was willst du noch mehr? PDO stellt eine reine OO-API zum Aufbauen/Trennen der Verbindung, sicheren Abfragen (Prepared Statements), Exceptionbasierte Fehlerbbehandlung, etc zur Verfügung.

Das nächste was du dann bauen willst, ist ein QueryBuilder (SQLAbfragenErsteller), sodass du nur noch PHP-Code schreiben musst und nicht mehr SQL-Code. Ein Beispiel wäre: $query = new QueryBuilder($pdo)->table('users')->select('name')->where('name', 'Hans')->execute(); Diesem musst du dann natürlich dein Datenbankhandle (PDO) übergeben und optimalerweise auch in einer Klassenvariable ablegen, damit du den QueryBuilder mehrmals benutzen kannst (mehrere Abfragen.) Um dir mal ein Bild von sowas zu machen, akknst du dir Beispielsweise folgendes ansehen: http://laravel.com/docs/4.2/queries

Noch ein bisschen weiter gedacht und abstrahiert wirst du wohl auch irgendwann an den Punkt kommen, einen ORM zu implementieren oder einen fertigen zu benutzen. (http://de.wikipedia.org/wiki/Objektrelationale_Abbildung) Auch dazu sei dir mal die Laravel-Dokumentation ans Herz gelegt, da bekommt man nen schönen Eindurck davon, was so alles möglich ist. Die Implementierung , und an der hapert es ja anscheinend grade bei dir, ist natürlich noch ne ganz andere Sache.
 
Danke für die umfangreichen Info´s, der etwas mehr als nur den QueryBuilder hatte ich mit der mysql-Klasse, aus dem ersten Post vor.

$main->$db->$_db->PDO
Leider bekomme ich bei diesem Weg, auch nur Fehlermeldungen:

$mysql gibt es nicht, das Objekt $db füge ich über die __construct()-Funktion, in die Variable $db der main-Klasse ein. Daher dachte ich, dass dieses funktionieren könnte, wenn ich $_db aus der mysql-Klasse, dass private entferne.

Dies sah dann wie folgt aus:
PHP:
$stmt = self::$db->$_db->prepare("SELECT id, lang, staat FROM ".self::$db->$_db->prefix()."_language WHERE default = 1");

Aber es funktioniert leider auch nicht.
 
Ja, genau, private ist natürlich auch ein Problem. Außerdem würde ich mal das ganze static Zeug erstmal weg lassen. Um das Ganze zu Debuggen kannst du die Kette ja mal nach und nach auflösen: Was ergibt var_dump(self::$db)? wenn das das richtige beinhaltet, gehst du weiter: var_dump(self::$db->$_db); ($_db ist zB auch ein static, warum hier anderer Operator?) Was ist überhaupt die genaue Fehlermeldung - normalerweise solltest du danach gut gehen können und den Fehler leichter identifizieren ;)
 
Weil ich auf den Thread aufmerksam gemacht wurde: Könntest du die aktuelle Version der Klasse vielleicht zusammenhängend posten mit einem Beispiel, wie man sie benutzt?

Könntest du vielleicht auch noch mal in einem Satz sagen, was sie tun soll? Ich finde es zu anstrengend, mir aus den Auszügen aus zwei oder drei Posts, die teilweise Änderungen enthalten, ein Gesamtbild zusammenzubauen.

Wenn es nur darum geht, für Debug-Zwecke die Query-Templates von Prepared Statements mit den entsprechenden Daten zu füllen: Dazu findet man mit kurzer Suche erst mal viel einfachere Funktionen:

- http://php.net/manual/en/pdostatement.debugdumpparams.php#113400 (Ob die immer unter allen Umständen gültiges SQL erzeugen, sei aber dahingestellt.)
 
Sorry, dass ich solange hier drum mich nicht kümmern konnte, aber ich habe mit meinem eigenen Forum bissel was zu tun gehabt, daher kam ich nicht hierzu. Nun hab ich mich wieder ran gesetzt, paar tutorials mir zu gemüte gezogen, die ich noch fand und jetzt soweit es selbst hinbekommen, dass ich das PDO ohne vererben, in anderen klassen verwenden kann.

Hier nun meine jetzige Code´s (PDOTEST.class.php), wo ich schon im ersten paar Fragen habe:
PHP:
class PDOTEST{ 
  ...
  public static $_db;
  ...
Dieses $_db mußte ich auf public setzen, damit ich damit ich auf die anderen zugreifen konnte, wenn darin das PDO agiert, könnte das aber Sicherheitsrisiken mal mit sich bringen. Aber ohne diesem public bekam ich es leider nicht zum laufen. Gibt es da vielleicht noch ne bessere Lösung?
Zu dem habe ich diese Klasse umbenannt, damit es nicht irgendwo vielleicht Verwechselungen oder andere Probleme gibt.

Hier wird ja das PDO gestartet:
PHP:
  function __construct(){
    try{
      self::$_db = new PDO("mysql:host=".self::$_db_host.";dbname=".self::$_db_name, self::$_db_user,self::$_db_pass);
    } catch (PDOException $e){
      echo "Datenbankeverbindung gescheitert";
      die();
    }
  }

Hier werden die Objekte in der index.php erstellt:
PHP:
$db = new PDOTEST();
$main = new main();
da konnte ich nun die Klammern leer lassen.

Hier der Auszug aus der main.class.php:
PHP:
class main{
  private $db;
  ...
  public function __construct(){
    $this->db = PDOTEST::$_db;
  }

Und hier die Funktion, welche das PDO aus der mysql-Klasse nutzt:
PHP:
  public function check_user(){
    $stmt = $this->db->prepare("SELECT email FROM users WHERE user = 'Matze202'");
    $stmt->execute();
    $result = $stmt->fetch(PDO::FETCH_ASSOC);
    var_dump($result);
  }

$_db ist zB auch ein static, warum hier anderer Operator?
@alxy Dies klappte ja aber meines wissens nur, weil ich die $_db in der PDOTEST-Klasse auf static gesetzt hatte. Aber soweit funktioniert es ja nun. In der main-Klasse konnte ich das weg lassen, oder meintest du dies?

Ich habe einfach nur noch 2 Fragen zu dem Script:
  1. Kann es Sicherheitsrisiken mit dem public der $_db geben?
  2. Gibt es eventuell eine elegantere Lösung?
Ich möchte einfach nur, die in der PDOTEST-Klasse aufgebaute Datenbankverbindung, in anderen Klassen verwenden können.

Das andere aus dem ersten Beitrag, habe ich mir erstmal aus dem Kopf geschlagen, da es doch ne Nummer zu groß ist für mich.

Gruß Matze202.
 
@mermshaus Danke erstmal für deine Antwort ;)
Was meinst du mit Getter?
PHP:
class PDOTEST{
  private static $_db;
Ja hier habe ich $_db ein static verpasst, dass ich auch außerhalb der Klasse darauf zugreifen kann. Aber ich habe ja noch das Aufbauen der Datenbankverbindung, welches im __construct() steckt, welches doch erst mit der Instanziierung aufgerufen wird oder habe ich da was falsch aufgerufen? Denn nach meinem Wissen, ist doch die $_db vor der Instanziierung leer.
PHP:
  public static function getDatabase()
  {
      return self::$_db;
  }
Was soll dieses return, der Variable in der static-Methode bewirken?
Wieso eine Instanz, wenn die Klasse statisch angelegt ist?
Ich habe doch nicht die ganze Klasse statisch gemacht, oder doch??? ;)

Sorry, ich muss noch viel lernen.
 
Was meinst du mit Getter?

Siehe das Codebeispiel darunter. Das verhindert, dass irgendwo einfach per PDOTEST::$_db = whatever die Klassenvariable überschrieben werden kann.

Das ist ein kontrollierterer Zugriff.

- http://en.wikipedia.org/wiki/Mutator_method

Ja hier habe ich $_db ein static verpasst, dass ich auch außerhalb der Klasse darauf zugreifen kann.

Dazu muss das nicht statisch sein. Statisch besagt einfach, dass keine Instanz benötigt wird, um auf den Wert zuzugreifen, weil er an den Klassennamen gebunden im globalen Geltungsbereich existiert.

Du kannst aber auch mit Instanzen von außerhalb der Instanz auf einen Wert zugreifen:

PHP:
<?php

class A
{
    public $foo = 'bar';
}

$a = new A();
var_dump($a->foo); // string(3) "bar"

Aber ich habe ja noch das Aufbauen der Datenbankverbindung, welches im __construct() steckt, welches doch erst mit der Instanziierung aufgerufen wird oder habe ich da was falsch aufgerufen? Denn nach meinem Wissen, ist doch die $_db vor der Instanziierung leer.

Das stimmt technisch gesehen schon. Es ist aber zumindest „unüblich“, in so einem Fall Klassenvariablen (static) über eine Instanz zu steuern. Man würde da eher eine ebenfalls statische init-Methode verwenden oder lazy initialization (Initialisierung bei erster Nutzung) oder einen Singleton-Ansatz.

Was soll dieses return, der Variable in der static-Methode bewirken?

Das dient der erwähnten Zugriffssteuerung, die das Überschreiben des Variableninhalts mit einem unsinnigen Wert verhindert.

Ich habe doch nicht die ganze Klasse statisch gemacht, oder doch??? ;)

Du hast es aktuell irgendwie so halb und halb gemacht. Die Frage wäre, warum du überhaupt etwas auf statisch gesetzt hast? Die Idee dahinter wird wohl gewesen sein, dass du die einmal konfigurierte PDO-Instanz überall im Code verfügbar haben möchtest. Das schreibst du ja auch so ähnlich.

Wenn du dir dazu mit Klassenvariablen behilfst, nutzt du im Grunde den globalen Geltungsbereich (global state). Das ist zumeist unvorteilhaft, weil du Abhängigkeiten von einzelnen Teilen deines Codes damit verdeckst. Das ist das Gleiche wie bei globalen Variablen. Die kannst du einfach irgendwo mitten im Code aus der Luft fischen. Das ist sehr unübersichtlich, weil du den gesamten Code lesen musst, um zu wissen, welche Abhängigkeiten eine Funktion oder Methode hat.

Es ist in der Regel sinnvoller, alle Abhängigkeiten in der Signatur anzugeben und bei Aufruf zu übergeben (Dependency Injection).

Dann hast du Instanzen und benötigst keine statischen Klassenvariablen mehr.
 
Zuletzt bearbeitet:
Zurück