it-swarm.com.de

Raw-Abfragezeichenfolge aus vorbereiteten PDO-Anweisungen abrufen

Gibt es eine Möglichkeit, den unformatierten SQL-String beim Aufruf von PDOStatement :: execute () in einer vorbereiteten Anweisung auszuführen? Für Debugging-Zwecke wäre dies äußerst nützlich.

116
Wilco

Ich gehe davon aus, dass Sie die abschließende SQL-Abfrage mit den interpolierten Parameterwerten wollen. Ich verstehe, dass dies für das Debuggen hilfreich sein könnte, aber präparierte Anweisungen funktionieren nicht. Parameter werden auf der Clientseite nicht mit einer vorbereiteten Anweisung kombiniert. PDO sollte daher niemals Zugriff auf die Abfragezeichenfolge mit ihren Parametern haben.

Die SQL-Anweisung wird an den Datenbankserver gesendet, wenn Sieore () ausführen, und die Parameter werden separat gesendet, wenn Sie () ausführen. Das allgemeine Abfrageprotokoll von MySQL zeigt das letzte SQL mit Werten an, die nach der Ausführung () interpoliert werden. Nachfolgend finden Sie einen Auszug aus meinem allgemeinen Abfrageprotokoll. Ich habe die Abfragen von der mysql-CLI ausgeführt, nicht von PDO, aber das Prinzip ist dasselbe.

081016 16:51:28 2 Query       prepare s1 from 'select * from foo where i = ?'
                2 Prepare     [2] select * from foo where i = ?
081016 16:51:39 2 Query       set @a =1
081016 16:51:47 2 Query       execute s1 using @a
                2 Execute     [2] select * from foo where i = 1

Sie können auch erhalten, was Sie möchten, wenn Sie das PDO-Attribut PDO :: ATTR_EMULATE_PREPARES setzen. In diesem Modus interpolieren PDO-Parameter in die SQL-Abfrage und senden die gesamte Abfrage, wenn Sie () ausführen. Dies ist keine echte vorbereitete Abfrage. Sie umgehen die Vorteile vorbereiteter Abfragen, indem Sie vor dem Ausführen von () Variablen in den SQL-String interpolieren.


Re Kommentar von @afilina:

Nein, die textuelle SQL-Abfrage ist nicht und wird während der Ausführung mit den Parametern kombiniert. Es gibt also nichts, was die PDO Ihnen zeigen könnte.

Wenn Sie PDO :: ATTR_EMULATE_PREPARES verwenden, erstellt PDO intern eine Kopie der SQL-Abfrage und interpoliert Parameterwerte in die Abfrage, bevor Sie sie vorbereiten und ausführen. PDO macht diese modifizierte SQL-Abfrage jedoch nicht verfügbar. 

Das PDOStatement-Objekt hat eine Eigenschaft $ queryString, die jedoch nur im Konstruktor für das PDOStatement festgelegt wird. Sie wird nicht aktualisiert, wenn die Abfrage mit Parametern überschrieben wird.

Es wäre eine vernünftige Funktionsanforderung für PDO, sie um die Offenlegung der umgeschriebenen Abfrage zu bitten. Aber selbst das würde Ihnen nicht die "komplette" Abfrage geben, wenn Sie PDO :: ATTR_EMULATE_PREPARES verwenden.

Aus diesem Grund zeige ich die oben beschriebene Problemumgehung der Verwendung des allgemeinen Abfrageprotokolls des MySQL-Servers, da in diesem Fall sogar eine vorbereitete Abfrage mit Parameterplatzhaltern auf dem Server neu geschrieben wird, wobei die Parameterwerte in die Abfragezeichenfolge zurückgefüllt werden. Dies erfolgt jedoch nur während der Protokollierung, nicht während der Abfrageausführung.

100
Bill Karwin
/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public static function interpolateQuery($query, $params) {
    $keys = array();

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }
    }

    $query = preg_replace($keys, $params, $query, 1, $count);

    #trigger_error('replaced '.$count.' keys');

    return $query;
}
100
bigwebguy

Ich habe die Methode geändert, um die Ausgabe von Arrays für Anweisungen wie WHERE IN (?) Zu behandeln. 

UPDATE: Es wurde nur eine Überprüfung auf NULL-Wert und doppelte $ -Parameter hinzugefügt, sodass die tatsächlichen $ param-Werte nicht geändert werden. 

Großartige Arbeit bigwebguy und danke!

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_string($value))
            $values[$key] = "'" . $value . "'";

        if (is_array($value))
            $values[$key] = "'" . implode("','", $value) . "'";

        if (is_null($value))
            $values[$key] = 'NULL';
    }

    $query = preg_replace($keys, $values, $query);

    return $query;
}
27
Mike

PDOStatement hat eine öffentliche Eigenschaft $ queryString. Es sollte sein, was du willst.

Ich habe gerade bemerkt, dass PDOStatement eine undokumentierte Methode debugDumpParams () hat, die Sie vielleicht auch betrachten möchten.

8
Glass Robot

Ein wenig mehr zum Code von Mike hinzugefügt - Walken Sie die Werte, um einfache Anführungszeichen hinzuzufügen 

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_array($value))
            $values[$key] = implode(',', $value);

        if (is_null($value))
            $values[$key] = 'NULL';
    }
    // Walk the array to see if we can add single-quotes to strings
    array_walk($values, create_function('&$v, $k', 'if (!is_numeric($v) && $v!="NULL") $v = "\'".$v."\'";'));

    $query = preg_replace($keys, $values, $query, 1, $count);

    return $query;
}
7
Chris Go

Wahrscheinlich ein bisschen spät, aber jetzt gibt es PDOStatement::debugDumpParams

Die Informationen, die in einer vorbereiteten Anweisung enthalten sind, werden direkt auf .__ ausgegeben. die Ausgabe. Es liefert die verwendete SQL-Abfrage, die Nummer von verwendete Parameter (Params), die Liste der Parameter mit ihrem Namen type (paramtype) als Ganzzahl, deren Schlüsselname oder -position und die Position in der Abfrage (wenn dies vom PDO-Treiber unterstützt wird, Andernfalls wird es -1).

Weitere Informationen finden Sie in den offiziellen PHP-Dokumenten

Beispiel:

<?php
/* Execute a prepared statement by binding PHP variables */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour');
$sth->bindParam(':calories', $calories, PDO::PARAM_INT);
$sth->bindValue(':colour', $colour, PDO::PARAM_STR, 12);
$sth->execute();

$sth->debugDumpParams();

?>
6
Jimmy Kane

Sie können die PDOStatement-Klasse erweitern, um die gebundenen Variablen zu erfassen und zur späteren Verwendung zu speichern. Dann können 2 Methoden hinzugefügt werden, eine für die Bereinigung von Variablen (debugBindedVariables) und eine für den Ausdruck der Abfrage mit diesen Variablen (debugQuery):

class DebugPDOStatement extends \PDOStatement{
  private $bound_variables=array();
  protected $pdo;

  protected function __construct($pdo) {
    $this->pdo = $pdo;
  }

  public function bindValue($parameter, $value, $data_type=\PDO::PARAM_STR){
    $this->bound_variables[$parameter] = (object) array('type'=>$data_type, 'value'=>$value);
    return parent::bindValue($parameter, $value, $data_type);
  }

  public function bindParam($parameter, &$variable, $data_type=\PDO::PARAM_STR, $length=NULL , $driver_options=NULL){
    $this->bound_variables[$parameter] = (object) array('type'=>$data_type, 'value'=>&$variable);
    return parent::bindParam($parameter, $variable, $data_type, $length, $driver_options);
  }

  public function debugBindedVariables(){
    $vars=array();

    foreach($this->bound_variables as $key=>$val){
      $vars[$key] = $val->value;

      if($vars[$key]===NULL)
        continue;

      switch($val->type){
        case \PDO::PARAM_STR: $type = 'string'; break;
        case \PDO::PARAM_BOOL: $type = 'boolean'; break;
        case \PDO::PARAM_INT: $type = 'integer'; break;
        case \PDO::PARAM_NULL: $type = 'null'; break;
        default: $type = FALSE;
      }

      if($type !== FALSE)
        settype($vars[$key], $type);
    }

    if(is_numeric(key($vars)))
      ksort($vars);

    return $vars;
  }

  public function debugQuery(){
    $queryString = $this->queryString;

    $vars=$this->debugBindedVariables();
    $params_are_numeric=is_numeric(key($vars));

    foreach($vars as $key=>&$var){
      switch(gettype($var)){
        case 'string': $var = "'{$var}'"; break;
        case 'integer': $var = "{$var}"; break;
        case 'boolean': $var = $var ? 'TRUE' : 'FALSE'; break;
        case 'NULL': $var = 'NULL';
        default:
      }
    }

    if($params_are_numeric){
      $queryString = preg_replace_callback( '/\?/', function($match) use( &$vars) { return array_shift($vars); }, $queryString);
    }else{
      $queryString = strtr($queryString, $vars);
    }

    echo $queryString.PHP_EOL;
  }
}


class DebugPDO extends \PDO{
  public function __construct($dsn, $username="", $password="", $driver_options=array()) {
    $driver_options[\PDO::ATTR_STATEMENT_CLASS] = array('DebugPDOStatement', array($this));
    $driver_options[\PDO::ATTR_PERSISTENT] = FALSE;
    parent::__construct($dsn,$username,$password, $driver_options);
  }
}

Und dann können Sie diese geerbte Klasse zum Debuggen von Zwecken verwenden.

$dbh = new DebugPDO('mysql:Host=localhost;dbname=test;','user','pass');

$var='user_test';
$sql=$dbh->prepare("SELECT user FROM users WHERE user = :test");
$sql->bindValue(':test', $var, PDO::PARAM_STR);
$sql->execute();

$sql->debugQuery();
print_r($sql->debugBindedVariables());

Ergebend

SELECT user FROM Benutzer WHERE user = 'user_test' 

Array ( [: Test] => user_test )

4
Otamay

Ich habe viel Zeit damit verbracht, diese Situation für meine eigenen Bedürfnisse zu untersuchen. Dieser und einige andere SO - Threads haben mir sehr geholfen, deshalb wollte ich mitteilen, was ich mir vorgestellt habe.

Der Zugriff auf die interpolierte Abfragezeichenfolge ist zwar ein erheblicher Vorteil bei der Fehlerbehebung, wir wollten jedoch nur ein Protokoll bestimmter Abfragen protokollieren (daher war die Verwendung der Datenbankprotokolle für diesen Zweck nicht ideal). Wir wollten auch die Protokolle verwenden können, um jederzeit den Zustand der Tabellen neu zu erstellen. Daher mussten wir sicherstellen, dass die interpolierten Zeichenfolgen ordnungsgemäß maskiert wurden. Schließlich wollten wir diese Funktionalität auf unsere gesamte Codebasis ausdehnen und so wenig wie möglich neu schreiben (Fristen, Marketing usw.); Sie wissen, wie es ist.

Meine Lösung bestand darin, die Funktionalität des PDOStatement-Standardobjekts zu erweitern, um die parametrisierten Werte (oder Referenzen) zwischenzuspeichern. Wenn die Anweisung ausgeführt wird, können Sie die Funktionalität des PDO-Objekts verwenden, um die Parameter ordnungsgemäß zu sichern, wenn sie wieder in die Abfrage eingefügt werden Schnur Wir könnten dann die Methode des Anweisungsobjekts ausführen und die tatsächlich ausgeführte Abfrage protokollieren (oder zumindest einer Wiedergabe so genau wie möglich)}.

Wie gesagt, wir wollten nicht die gesamte Codebasis modifizieren, um diese Funktionalität hinzuzufügen. Daher überschreiben wir die Standardmethoden bindParam() und bindValue() des PDOStatement-Objekts, führen das Zwischenspeichern der gebundenen Daten durch und rufen dann parent::bindParam() oder parent :: bindValue() auf. . Dadurch konnte unsere vorhandene Codebasis weiterhin normal funktionieren.

Wenn die execute()-Methode aufgerufen wird, führen wir schließlich unsere Interpolation durch und stellen die resultierende Zeichenfolge als neue Eigenschaft E_PDOStatement->fullQuery zur Verfügung. Dies kann ausgegeben werden, um die Abfrage anzuzeigen oder beispielsweise in eine Protokolldatei geschrieben zu werden.

Die Erweiterung sowie Installations- und Konfigurationsanweisungen sind auf github verfügbar:

https://github.com/noahheck/E_PDOStatement

HAFTUNGSAUSSCHLUSS:
Natürlich habe ich, wie gesagt, diese Erweiterung geschrieben. Da es mit Hilfe vieler Threads hier entwickelt wurde, wollte ich meine Lösung hier posten, falls jemand anderes auf diese Threads stößt, genau wie ich.

3
myesain

Eine Lösung besteht darin, freiwillig einen Fehler in die Abfrage einzufügen und die Fehlermeldung zu drucken:

//Connection to the database
$co = new PDO('mysql:dbname=myDB;Host=localhost','root','');
//We allow to print the errors whenever there is one
$co->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

//We create our prepared statement
$stmt = $co->prepare("ELECT * FROM Person WHERE age=:age"); //I removed the 'S' of 'SELECT'
$stmt->bindValue(':age','18',PDO::PARAM_STR);
try {
    $stmt->execute();
} catch (PDOException $e) {
    echo $e->getMessage();
}

Standardausgabe:

SQLSTATE [42000]: Syntaxfehler oder Zugriffsverletzung: [...] nahe 'ELECT * FROM Person WHERE = 18' in Zeile 1

Beachten Sie, dass nur die ersten 80 Zeichen der Abfrage gedruckt werden.

2
JacopoStanchi

Die erwähnte $ queryString-Eigenschaft gibt wahrscheinlich nur die übergebene Abfrage zurück, ohne dass die Parameter durch ihre Werte ersetzt werden. In .Net habe ich den catch-Teil meines Abfrageausführers, der eine einfache Suche durchführt, um die Parameter mit ihren angegebenen Werten zu ersetzen, sodass das Fehlerprotokoll die tatsächlichen Werte anzeigen kann, die für die Abfrage verwendet wurden. Sie sollten in der Lage sein, die Parameter in PHP aufzulisten und durch den zugewiesenen Wert zu ersetzen.

1
Kibbee

Sie können sprintf(str_replace('?', '"%s"', $sql), ...$params); verwenden

Hier ist ein Beispiel:

function mysqli_prepared_query($link, $sql, $types='', $params=array()) {
    echo sprintf(str_replace('?', '"%s"', $sql), ...$params);
    //prepare, bind, execute
}

$link = new mysqli($server, $dbusername, $dbpassword, $database);
$sql = "SELECT firstname, lastname FROM users WHERE userage >= ? AND favecolor = ?";
$types = "is"; //integer and string
$params = array(20, "Brown");

if(!$qry = mysqli_prepared_query($link, $sql, $types, $params)){
    echo "Failed";
} else {
    echo "Success";
}

Beachten Sie, dass dies nur für PHP> = 5.6 funktioniert

0
kurdtpage

Ich weiß, dass diese Frage ein bisschen alt ist, aber ich verwende diesen Code seit langer Zeit (ich habe die Antwort von @ chris-go verwendet), und jetzt ist dieser Code mit PHP 7.2 veraltet

Ich werde eine aktualisierte Version dieses Codes posten (Die Gutschrift für den Hauptcode stammt von @bigwebguy , @mike und @ chris-go , alle Antworten auf diese Frage):

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_array($value))
            $values[$key] = implode(',', $value);

        if (is_null($value))
            $values[$key] = 'NULL';
    }
    // Walk the array to see if we can add single-quotes to strings
    array_walk($values, function(&$v, $k) { if (!is_numeric($v) && $v != "NULL") $v = "\'" . $v . "\'"; });

    $query = preg_replace($keys, $values, $query, 1, $count);

    return $query;
}

Beachten Sie, dass sich die Änderungen am Code in der Funktion array_walk () befinden und create_function durch eine anonyme Funktion ersetzt werden. Dadurch ist dieser gute Code funktional und kompatibel mit PHP 7.2 (und hoffentlich auch zukünftige Versionen).

0
Sakura Kinomoto

Etwas verwandt ... Wenn Sie nur versuchen, eine bestimmte Variable zu bereinigen, können Sie PDO :: quote verwenden. Um beispielsweise nach mehreren partiellen LIKE-Bedingungen zu suchen, wenn Sie sich in einem begrenzten Rahmen wie CakePHP befinden:

$pdo = $this->getDataSource()->getConnection();
$results = $this->find('all', array(
    'conditions' => array(
        'Model.name LIKE ' . $pdo->quote("%{$keyword1}%"),
        'Model.name LIKE ' . $pdo->quote("%{$keyword2}%"),
    ),
);
0
Synexis

Ich muss die vollständige Abfragezeichenfolge nach dem Bind-Parameter protokollieren, sodass dies ein Teil meines Codes ist. Ich hoffe, es ist nützlich für alle, die das gleiche Problem haben.

/**
 * 
 * @param string $str
 * @return string
 */
public function quote($str) {
    if (!is_array($str)) {
        return $this->pdo->quote($str);
    } else {
        $str = implode(',', array_map(function($v) {
                    return $this->quote($v);
                }, $str));

        if (empty($str)) {
            return 'NULL';
        }

        return $str;
    }
}

/**
 * 
 * @param string $query
 * @param array $params
 * @return string
 * @throws Exception
 */
public function interpolateQuery($query, $params) {
    $ps = preg_split("/'/is", $query);
    $pieces = [];
    $prev = null;
    foreach ($ps as $p) {
        $lastChar = substr($p, strlen($p) - 1);

        if ($lastChar != "\\") {
            if ($prev === null) {
                $pieces[] = $p;
            } else {
                $pieces[] = $prev . "'" . $p;
                $prev = null;
            }
        } else {
            $prev .= ($prev === null ? '' : "'") . $p;
        }
    }

    $arr = [];
    $indexQuestionMark = -1;
    $matches = [];

    for ($i = 0; $i < count($pieces); $i++) {
        if ($i % 2 !== 0) {
            $arr[] = "'" . $pieces[$i] . "'";
        } else {
            $st = '';
            $s = $pieces[$i];
            while (!empty($s)) {
                if (preg_match("/(\?|:[A-Z0-9_\-]+)/is", $s, $matches, PREG_OFFSET_CAPTURE)) {
                    $index = $matches[0][1];
                    $st .= substr($s, 0, $index);
                    $key = $matches[0][0];
                    $s = substr($s, $index + strlen($key));

                    if ($key == '?') {
                        $indexQuestionMark++;
                        if (array_key_exists($indexQuestionMark, $params)) {
                            $st .= $this->quote($params[$indexQuestionMark]);
                        } else {
                            throw new Exception('Wrong params in query at ' . $index);
                        }
                    } else {
                        if (array_key_exists($key, $params)) {
                            $st .= $this->quote($params[$key]);
                        } else {
                            throw new Exception('Wrong params in query with key ' . $key);
                        }
                    }
                } else {
                    $st .= $s;
                    $s = null;
                }
            }
            $arr[] = $st;
        }
    }

    return implode('', $arr);
}
0
ducminh1903