User Tools

Site Tools


php:libraries:ypdostmt

[PHP] YPDOStmt: Erweiterung der PDOStatement Klasse

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.

Anwendungsbeispiele

Um zu zeigen was das Ding macht, beginne ich mit Anwendungsbeispielen. Der Code selber befindet sich am Schluss der Seite.

Mit ? Parametern indexieren

Initializieren

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;

Beispiele des indexierten SQLs

Beispiel 1) Indexierte Paramter

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'
Beispiel 2) Bind Variable

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'
Beispiel 3) SQLgeparstes SQL ausgeben ohne es auszuführen

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'

Mit benannten Parametern

Initializieren

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

Beispiel

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'

Mit Fehler im SQL-Statement

Initializieren

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;

Beispiel

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}

Code

Der COde der Klassen

YPDOStmt.php
<?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);
	}
 
}
?>
php/libraries/ypdostmt.txt · Last modified: 22.03.2017 13:02:33 by yaslaw