Man kennt das Problem. Mit PDO kann man sehr schön mit bindParam() oder bindValue() arbeiten. Jedoch zum debugen ist das nicht gerade der Luxus, da man sich das SQL-Statement nicht ausgeben lassen kann (siehe [PHP][MySQLi] Debug Queries). Wenn man also ein test-SQL haben will, dann muss man das schon selber parsen. Dazu kann man die PDOStatement Klasse erweitern. Hier ist mal mein Versuch. Die Beschreibungen befinden sich im Code.
Um zu zeigen was das Ding macht, beginne ich mit Anwendungsbeispielen. Der Code selber befindet sich am Schluss der Seite.
Erstellen des Statements ist eigentlcih wie normal. Einfach vorher dem PDO-Objekt die eigene Statement-Klasse zuordnen
<?php //Connection erstellen. $pdo ist das PDO-Objekt require_once 'connectPDO.php'; //YPDOStmt einbinden require_once 'YPDOStmt.php'; // ! WICHTIG ! PDO-Statementklasse durch die YPDOStmt-Klasse ersetzen $pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('YPDOStmt', array($pdo))); //SQL-Statement mit ? Parametern $sql_indexed = <<<SQL SELECT t.ID_tipp, t.username, t.ID_partien FROM tipps_1213 t WHERE FIND_IN_SET(t.ID_partien, ?) AND t.punkte=? SQL; //Variablen vorbereiten $ids = array(319,320,321); $idsS = implode($ids, ','); $id = 1;
Indexiert und mit einzelner Parameterbindung. Ausgebenes SQL: letzter Lauf
try{ $stmt = $pdo->prepare($sql_indexed); $stmt->bindValue(1, $idsS, PDO::PARAM_STR); //Als Value $stmt->bindParam(2, $id, PDO::PARAM_INT); //und als Bind Variable $stmt->execute(); //Das ausgeführte SQL anzeigen echo $stmt->executedSql; } catch(Exception $e){ echo $e->getMessage(); echo nl2br($e->getTraceAsString()); }
SELECT t.ID_tipp, t.username, t.ID_partien, '319,320,321' FROM tipps_1213 t WHERE FIND_IN_SET(t.ID_partien, '319,320,321') AND t.punkte='1'
Nach der Asuführung des ersten Beispiels, beide Variablen mal ändern. Nur die Bind-Variable ($id) wird neu übernommen. Ausgebenes SQL: letzter Lauf
try{ $idsS = 'A,B,C'; $id = 5; $stmt->execute(); //Das ausgeführte SQL anzeigen echo $stmt->executedSql; } catch(Exception $e){ echo $e->getMessage(); echo nl2br($e->getTraceAsString()); }
SELECT t.ID_tipp, t.username, t.ID_partien FROM tipps_1213 t WHERE FIND_IN_SET(t.ID_partien, '319,320,321') AND t.punkte='5'
Bind-Variable nochmal anpassen und nur das SQL ausgeben ohne auszuführen
try{ $id = 999; //Das SQL anzeigen ohne auszuführen echo $stmt->getSql(); } catch(Exception $e){ echo $e->getMessage(); echo nl2br($e->getTraceAsString()); }
SELECT t.ID_tipp, t.username, t.ID_partien FROM tipps_1213 t WHERE FIND_IN_SET(t.ID_partien, '319,320,321') AND t.punkte='999'
//Connection erstellen. $pdo ist das PDO-Objekt require_once 'connectPDO.php'; //YPDOStmt einbinden require_once 'YPDOStmt.php'; // ! WICHTIG ! PDO-Statementklasse durch die YPDOStmt-Klasse ersetzen $pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('YPDOStmt', array($pdo))); //SQL-Statement mit benamsten Parametern $sql_named = <<<SQL SELECT t.ID_tipp, t.username, t.ID_partien, :ids FROM tipps_1213 t WHERE FIND_IN_SET(t.ID_partien, :ids) AND t.punkte=:id SQL; //Variablen vorbereiten $ids = array(319,320,321); $idsS = implode($ids, ','); $id = 1;
Benannte Variablen, Valueübergabe im execute(). Ausgebenes SQL: letzter Lauf
try{ $stmt = $pdo->prepare($sql_named); $stmt->execute(array(':ids'=>$idsS, ':id'=>$id)); //Das ausgeführte SQL anzeigen echo $stmt->executedSql; } catch(Exception $e){ echo $e->getMessage(); echo nl2br($e->getTraceAsString()); }
SELECT t.ID_tipp, t.username, t.ID_partien, '319,320,321' FROM tipps_1213 t WHERE FIND_IN_SET(t.ID_partien, '319,320,321') AND t.punkte='1'
Hier baue ich ein Fehler ins SQL-Statement ein. Zum Debuggen möchte ich das generierte SQL-Statement haben. Das Error-Object aus YPDOStmt beinhaltet das bereits im Fehlertext
<?php //Connection erstellen. $pdo ist das PDO-Objekt require_once 'connectPDO.php'; //YPDOStmt einbinden require_once 'YPDOStmt.php'; // ! WICHTIG ! PDO-Statementklasse durch die YPDOStmt-Klasse ersetzen $pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('YPDOStmt', array($pdo))); //SQL-Statement mit Fehler. vor der letzten Spalte fehlt ein Komma $sql_named = <<<SQL SELECT t.ID_tipp, t.username t.ID_partien FROM tipps_1213 t WHERE FIND_IN_SET(t.ID_partien, :ids) AND t.punkte=:id SQL; //Variablen vorbereiten $ids = array(319,320,321); $idsS = implode($ids, ','); $id = 1;
Ich führe das Statement extra ohn echo aus, damit die Ausgabe wirklich nur aus dem Error-Object stammt.
try{ $stmt = $pdo->prepare($sql_named); $stmt->execute(array(':ids'=>$idsS, ':id'=>$id)); } catch(Exception $e){ echo $e->getMessage(); echo nl2br($e->getTraceAsString()); }
Und die Fehlerausgabe. Das SQL kann man analog zu [PHP][MySQL] Debug Queries in phpMyAdmin oder HeidiSQL testen.
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.ID_partien FROM tipps_1213 t WHERE FIND_IN_SET(t.ID_partien, '319,320,321') ' at line 1 parsed SQL: SELECT t.ID_tipp, t.username t.ID_partien FROM tipps_1213 t WHERE FIND_IN_SET(t.ID_partien, '319,320,321') AND t.punkte='1' #0 C:\xampp\htdocs\test\test4.php(41): YPDOStmt->execute(Array) #1 {main}
Der COde der Klassen
<?php /** * mpl by ERB software * @author stefan.erb(at)erb-software.com * @version 1.0.0 30.09.2013 */ /** * Erweiterung von PDOStatement um das SQL-Statement zu ermitteln * @example * require_once 'connectPDO.php'; * require_once 'YPDOStmt.php'; * //PDO-Statementklasse durch die eigene ersetzen * $pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('YPDOStmt', array($pdo))); */ class YPDOStmt extends PDOStatement{ /** * Error-Format(printf). Parameter 1 => PDO-Fehlermeldung, Parameter 2 => SQL-Statement * @var String */ const C_DEFAULT_ERROR_FORMAT = '%1$s<br/>parsed SQL:<br/><hr /><pre>%2$s</pre><hr />'; public $error_format = self::C_DEFAULT_ERROR_FORMAT; /** * zuletzt ausgeführtes SQL * @var String */ public $executedSql; protected $params = array(); protected $pdo; /** * @see PDOStatement::__construct() */ protected function __construct($pdo) { $this->pdo = $pdo; } /** * bindParam überschreiben, damit der Parameter gespeichert wird. * @see PDOStatement::bindParam() */ public function bindParam($parameter, &$variable, $data_type = null , $length = null, $driver_options = null){ parent::bindParam($parameter, $variable, $data_type, $length, $driver_options); if(is_numeric($parameter)) $parameter = $parameter-1; $this->params[$parameter] = new YPDOParam($this->pdo, $parameter, $variable, $data_type, $length, $driver_options); } /** * bindValue überschreiben, damit der Parameter gespeichert wird. * @see PDOStatement::bindValue() */ public function bindValue($parameter, $value, $data_type = null){ parent::bindValue($parameter, $value, $data_type); if(is_numeric($parameter)) $parameter = $parameter-1; $this->params[$parameter] = new YPDOParam($this->pdo, $parameter, $value, $data_type); } /** * execute überschreiben, damit das SQL-Statement mit den Aktuellen Variableninhalten gespeichert wird * Die PDOException wird um das SQL-Staement erweitert * @see PDOStatement::execute() */ public function execute($input_parameters = null){ //Es wurden Parameter mitgegeben. Diese also noch erfassen if(is_array($input_parameters)){ $this->params = array(); foreach(array_keys($input_parameters) as $key){ //$input_parameters[$key] anstelle von $value aus einer normalen foreach() übergeben, //da das YPDOParam eine Refernz erstellt. Ansonsten hätten am Ende alle Parameter den Wert des letzten Values $this->params[$key] = new YPDOParam($this->pdo, $key , $input_parameters[$key]); } } //SQL genereiren und speichern $this->executedSql = $this->getSql(); //parent->execute ausführen try{ parent::execute($input_parameters); }catch (PDOException $e){ //Den Fehler um das SQL-Statement erweitern $msg = sprintf($this->error_format, $e->getMessage(), $this->executedSql); throw new PDOException($msg, (int)$e->getCode(), $e); } } /** * SQL-Statement mit den aktuellen Variabelninhalten erstellen * @return String */ public function getSql(){ $sql = $this->queryString; if(count($this->params) > 0){ reset($this->params); //Der Query-String hat ? als Platzhalter if(is_numeric(key($this->params))){ foreach($this->params as $key => &$param){ $sql = preg_replace('/\?/', $param->getValue(), $sql, 1); } //Der Query-String hat Variablen als Platzhalter }else{ foreach($this->params as $key => $param){ $sql = preg_replace("/(?<!\w)({$key})(?!\w)/is", $param->getValue(), $sql); } } } return $sql; } /** * Gibt alle Parameters zurück * @return Array<YPDOParam> */ public function getParams(){ return $this->params; } /** * gibt einen einzelnen Parameter zurück * @param Key oder Index * @return YPDOParam */ public function getParam($iKey){ return $this->params[$iKey]; } } /** * Parameter-Klasse mit allen Informationen zu PDO-Bind Params * @author Stefan Erb */ class YPDOParam{ protected $pdo; /** * @param PDO PDO-DB-Connection * @see PDOStatement::bindParam() */ public function __construct(&$pdo, $parameter, &$variable, $data_type = null , $length = null, $driver_options = null){ $this->pdo = &$pdo; $this->parameter = $parameter; $this->variable = &$variable; $this->data_type = $data_type; $this->length = $length; $this->driver_options = $driver_options; } /** * @return <String> Query-String mit PDO geparst. Nimmt die Aktuellen Werte der referenzierten Variable */ public function getValue(){ return $this->pdo->quote($this->variable, $this->data_type); } } ?>