Frage

Gibt es eine Möglichkeit, die rohen SQL-Zeichenfolge ausgeführt zu bekommen, wenn PDOStatement Aufruf :: () auf eine vorbereitete Anweisung ausführen? Für Debug-Zwecke wäre dies sehr nützlich sein.

War es hilfreich?

Lösung

Ich nehme an, Sie meinen, dass Sie die letzte SQL-Abfrage möchten, mit Parameterwerten in sie interpoliert. Ich verstehe, dass dies für die Fehlersuche nützlich sein würde, aber es ist nicht die Art und Weise vorbereitete Anweisungen arbeiten. Die Parameter sind nicht mit einer vorbereiteten Erklärung auf der Client-Seite kombiniert, so PDO sollte nie mit seinen Parametern kombiniert den Zugriff auf die Query-String haben.

Die SQL-Anweisung wird an den Datenbankserver gesendet, wenn Sie () bereiten, und die Parameter werden separat gesendet, wenn Sie nicht ausführen (). MySQL allgemeine Abfrageprotokoll die letzte SQL mit Werten tut zeigen interpoliert, nachdem Sie execute (). Im Folgenden ein Auszug aus meinem allgemeinen Abfrageprotokoll. Ich lief die Abfragen aus dem mysql CLI, nicht von PDO, aber das Prinzip ist das gleiche.

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 das bekommen, was Sie wollen, wenn Sie die PDO-Attribut PDO :: ATTR_EMULATE_PREPARES gesetzt. In diesem Modus Interpolation PDO Parameter in die SQL-Abfrage und sendet die gesamte Abfrage, wenn Sie () ausführen. Das ist keine echte vorbereitete Abfrage. Sie werden die Vorteile der präparierten Anfragen umgehen, indem Variablen in die SQL-Zeichenfolge vor execute () interpoliert werden.


Re Kommentar von @afilina:

Nein, die textlichen SQL-Abfrage ist nicht in Kombination mit den Parametern während der Ausführung. Also gibt es nichts für PDO Sie zu zeigen.

Intern, wenn Sie PDO :: ATTR_EMULATE_PREPARES verwenden, macht PDO eine Kopie der SQL-Abfrage und interpoliert Parameterwerte in sie, bevor die Vorbereitung und Durchführung zu tun. Aber PDO nicht aussetzen diese geänderte SQL-Abfrage.

Das PDOStatement Objekt verfügt über eine Eigenschaft $ querystring, aber dies nur im Konstruktor für die PDOStatement festgelegt ist, und es wird nicht aktualisiert, wenn die Abfrage mit Parametern neu geschrieben wird.

Es wäre ein sinnvolles Feature Anfrage sein für PDO sie zu bitten, die umgeschriebene Abfrage zu belichten. Aber selbst das würde Sie nicht die „vollständige“ Abfrage, wenn Sie PDO :: ATTR_EMULATE_PREPARES verwenden.

Das ist, warum ich die Abhilfe zeige über der Verwendung des allgemeine Abfrageprotokolls des MySQL-Servers, da in diesem Fall auch eine vorbereitete Abfrage mit Parametern Platzhalter auf dem Server neu geschrieben wird, mit Parameterwerten in den Query-String verfüllt. Aber dies ist nur während der Aufzeichnung durchgeführt, nicht während der Ausführung der Abfrage.

Andere Tipps

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

ich die Methode geändert Umgang Ausgabe von Arrays für Aussagen enthalten wie WHERE IN (?).

UPDATE: Just-Check für NULL-Wert hinzugefügt und dupliziert $ params so tatsächlichen $ param Werte werden nicht geändert.

Gute Arbeit bigwebguy und Dank!

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

PDOStatement hat eine öffentliche Eigenschaft $ querystring. Es sollte sein, was Sie wollen.

Ich habe gerade bemerkt, dass PDOStatement ein nicht dokumentiertes Verfahren debugDumpParams hat (), die Sie auch sehen möchten.

Ein bisschen spät wahrscheinlich aber jetzt gibt es PDOStatement::debugDumpParams

  

Gibt die von einer vorbereiteten Erklärung enthaltenen Informationen direkt auf   die Ausgabe. Es wird die SQL-Abfrage in Gebrauch bereitzustellen, die Anzahl der   Parameter verwendet (Param), die Liste der Parameter, mit ihrem Namen,   Typ (ParamType) als eine ganze Zahl, deren Schlüsselnamen oder die Position und die   Position in der Abfrage (wenn dies von dem PDO-Treiber unterstützt wird,   andernfalls wird es sein, -1).

Sie können mehr über die offiziellen php docs

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();

?>

ein bisschen mehr, um den Code von Mike hinzugefügt - die Werte gehen Apostrophe

hinzufü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;
}

Ich verbrachte viel Zeit, diese Situation für meine eigenen Bedürfnisse zu erforschen. Diese und einige andere SO Fäden half mir sehr viel, also wollte ich teilen, was ich kam mit.

Während Zugriff auf das interpolierte Query-String ein signifikanter Vorteil ist bei der Fehlersuche, wir wollten ein Protokoll nur bestimmter Abfragen halten können (also mit Hilfe der Datenbankprotokolle für diesen Zweck war nicht ideal). Wir wollten auch die Protokolle zu verwenden, um die Lage sein, den Zustand der Tabellen zu einem bestimmten Zeitpunkt zu erstellen, damit wir richtig zu machen benötigt wurden, sicher, dass die interpolierten Saiten entkommen. Schließlich wollten wir diese Funktionalität unserer gesamten Codebasis neu zu schreiben, so wenig wie möglich davon mit verlängern (Termine, Marketing, und so, Sie wissen, wie es ist).

Meine Lösung war, die Funktionalität des Standard PDOStatement Objekts zu erweitern, die parametrisierte Werte (oder Referenzen) zwischenzuspeichern, und wenn die Anweisung ausgeführt wird, um die Funktionalität des PDO-Objekts verwenden, um richtig die Parameter zu entkommen, wenn sie injiziert werden wieder in auf den Query-String. Wir könnten dann Methode des Statement-Objekts auszuführen binden und melden Sie sich die eigentliche Abfrage, die zu dieser Zeit ( oder zumindest so treu eine Reproduktion wie möglich) ausgeführt wurde.

Wie gesagt, wir wollten nicht die gesamte Code-Basis modifizieren, um diese Funktionalität hinzuzufügen, so dass wir überschreiben die Standard bindParam() und bindValue() Methoden des PDOStatement Objekt, tun unser Caching der gebundenen Daten, dann rufen parent::bindParam() oder Eltern :: bindValue(). Dies erlaubt unseren Code-Basis bestehenden weiterhin normal funktionieren zu können.

Wenn schließlich die execute() Methode aufgerufen wird, führen wir unsere Interpolation und die resultierende Zeichenfolge als eine neue Eigenschaft E_PDOStatement->fullQuery bieten. Dies kann auszugebenden die Abfrage oder zum Beispiel zu sehen, in eine Protokolldatei geschrieben.

Die Erweiterung sowie Installations- und Konfigurationsanweisungen, sind auf GitHub:

https://github.com/noahheck/E_PDOStatement

HAFTUNGSAUSSCHLUSS :
Offensichtlich wie ich bereits erwähnt, habe ich diese Erweiterung. Weil es mit Hilfe von vielen Threads hier entwickelt wurde, wollte ich meine Lösung hier, falls jemand Anderes über diese Themen kommt, so wie ich.

Sie können PDOStatement Klasse erweitern die beschränkten Variablen zu erfassen und speichern sie für die spätere Verwendung. Dann wurden 2 Methoden hinzugefügt werden können, eine für variable Hygienisierung (debugBindedVariables) und einem anderen die Abfrage mit diesen Variablen (debugQuery) drucken:

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 vererbten Klasse verwenden purpouses für das Debuggen.

$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());

Was zu

  

SELECT Benutzer FROM users WHERE user = 'user_test'

     

Array (       [: Test] => user_test   )

Eine Lösung ist auf freiwillige Basis einen Fehler in der Abfrage zu setzen und die Fehler der Nachricht 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();
}

Standardausgang:

  

SQLSTATE [42000]: Syntaxfehler oder Zugriffsverletzung: [...] in der Nähe von 'ELECT * FROM Person WHERE Alter = 18' in Zeile 1

Es ist wichtig zu beachten, dass es nur die ersten 80 Zeichen der Abfrage druckt.

Das $ querystring Eigenschaft erwähnt wird wahrscheinlich nur die Abfrage zurückgeben, die in vergangen, ohne dass die mit ihren Werten ersetzt Parameter. In .Net, habe ich die Klinke Teil meiner Abfrage executer auf die Parameter mit ihren Werten eine einfache Suche tun ersetzen, die geliefert wurde, so dass das Fehlerprotokoll Istwerten zeigen können, die für die Abfrage verwendet wurden. Sie sollten die Parameter in PHP aufzählen können, und die Parameter mit ihrem zugewiesenen Wert ersetzen.

Etwas ähnliche ... wenn Sie nur eine bestimmte Variable zu sanieren versuchen, können Sie verwenden, PDO :: quote . Zum Beispiel kann für mehr Teil-LIKE-Bedingungen zu suchen, wenn Sie mit einem begrenzten Rahmen wie CakePHP stecken:

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

Ich brauche volle Abfrage-String nach bind param loggt so ist dies ein Stück in meinem Code. Hoffnung, ist es sinnvoll, für jeden Hut hat das gleiche Problem.

/**
 * 
 * @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);
}

Ich weiß, diese Frage ist ein bisschen alt, aber ich diesen Code bin mit da vieler Zeit vor (I Antwort von @ chris-go verwendet habe), und jetzt, dieser Code ist veraltet mit PHP 7.2

Ich werde eine aktualisierte Version dieser Code (Kredit für den Hauptcode veröffentlichen sind von @bigwebguy , @ Mike und @ chris-go < alle von ihnen Antworten auf diese Frage / a>):

/**
 * 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 die Änderung des Codes sind auf array_walk () Funktion, create_function durch eine anonyme Funktion zu ersetzen. Dies macht dieses gute Stück Code funktional und kompatibel mit PHP 7.2 (und hofft, dass auch zukünftige Versionen).

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

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 das funktioniert nur für PHP> = 5.6

Mikes Antwort funktioniert gut, bis Sie die „Wiederverwendung“ bind Wert verwenden.
Zum Beispiel:

SELECT * FROM `an_modules` AS `m` LEFT JOIN `an_module_sites` AS `ms` ON m.module_id = ms.module_id WHERE 1 AND `module_enable` = :module_enable AND `site_id` = :site_id AND (`module_system_name` LIKE :search OR `module_version` LIKE :search)

Die Antwort des Mike kann nur ersetzen zuerst: suchen, aber nicht den zweiten
. Also, ich umschreiben seine Antwort mit mehreren Parametern zu arbeiten, die richtig wiederverwendet werden.

public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;
    $values_limit = [];

    $words_repeated = array_count_values(str_word_count($query, 1, ':_'));

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

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

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

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

    if (is_array($values)) {
        foreach ($values as $key => $val) {
            if (isset($values_limit[$key])) {
                $query = preg_replace(['/:'.$key.'/'], [$val], $query, $values_limit[$key], $count);
            } else {
                $query = preg_replace(['/:'.$key.'/'], [$val], $query, 1, $count);
            }
        }
        unset($key, $val);
    } else {
        $query = preg_replace($keys, $values, $query, 1, $count);
    }
    unset($keys, $values, $values_limit, $words_repeated);

    return $query;
}

preg_replace nicht für mich arbeiten und wenn binding_ über 9 war, binding_1 und binding_10 wurde mit str_replace ersetzt (die 0 zurücklassend), so machte ich die Ersetzungen nach hinten:

public function interpolateQuery($query, $params) {
$keys = array();
    $length = count($params)-1;
    for ($i = $length; $i >=0; $i--) {
            $query  = str_replace(':binding_'.(string)$i, '\''.$params[$i]['val'].'\'', $query);
           }
        // $query  = str_replace('SQL_CALC_FOUND_ROWS', '', $query, $count);
        return $query;

}

Hope jemand findet es nützlich.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top