Abrufen einer SQL-Abfragezeichenfolge aus PDO-vorbereiteten Anweisungen

130

Gibt es eine Möglichkeit, die unformatierte SQL-Zeichenfolge auszuführen, wenn PDOStatement :: execute () für eine vorbereitete Anweisung aufgerufen wird? Für Debugging-Zwecke wäre dies äußerst nützlich.

Wilco
quelle
1
Für PHP> = 5.1 werfen Sie
Mawg sagt, Monica am
1
Überprüfen Sie die einzeilige Funktion pdo-debug .
Sliq
Der sauberste Weg, den ich gefunden habe, ist die E_PDOStatement- Bibliothek. Du tust es einfach $stmt = $pdo->prepare($query); /* ... */ echo $stmt->fullQuery;. Es erweitert die PDOStatement-Klasse und ist daher so elegant, wie es die PDO-API zulässt.
ComFreek

Antworten:

110

Ich nehme an, Sie meinen, Sie möchten die endgültige SQL-Abfrage mit darin interpolierten Parameterwerten. Ich verstehe, dass dies für das Debuggen nützlich wäre, aber es ist nicht die Art und Weise, wie vorbereitete Anweisungen funktionieren. Parameter werden auf der Clientseite nicht mit einer vorbereiteten Anweisung kombiniert, daher sollte PDO niemals Zugriff auf die Abfragezeichenfolge haben, die mit ihren Parametern kombiniert ist.

Die SQL-Anweisung wird beim Vorbereiten () an den Datenbankserver gesendet, und die Parameter werden beim Ausführen () separat gesendet. Das allgemeine Abfrageprotokoll von MySQL zeigt das endgültige SQL mit Werten an, die nach der Ausführung von () interpoliert wurden. Unten finden Sie einen Auszug aus meinem allgemeinen Abfrageprotokoll. Ich habe die Abfragen über die MySQL-CLI ausgeführt, nicht über 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 das erhalten, was Sie möchten, wenn Sie das PDO-Attribut PDO :: ATTR_EMULATE_PREPARES festlegen. In diesem Modus interpoliert PDO Parameter in die SQL-Abfrage und sendet die gesamte Abfrage, wenn Sie () ausführen. Dies ist keine wirklich vorbereitete Abfrage. Sie umgehen die Vorteile vorbereiteter Abfragen, indem Sie vor der Ausführung von Variablen Variablen in die SQL-Zeichenfolge interpolieren.


Kommentar von @afilina:

Nein, die textuelle SQL-Abfrage wird während der Ausführung nicht mit den Parametern kombiniert. PDO kann Ihnen also nichts zeigen.

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

Das PDOStatement-Objekt hat die Eigenschaft $ queryString, diese wird jedoch nur im Konstruktor für das PDOStatement festgelegt und nicht aktualisiert, wenn die Abfrage mit Parametern neu geschrieben wird.

Es wäre eine vernünftige Funktionsanforderung für PDO, sie zu bitten, die umgeschriebene Abfrage offenzulegen. Aber selbst das würde Ihnen nicht die "vollständige" Abfrage geben, wenn Sie nicht PDO :: ATTR_EMULATE_PREPARES verwenden.

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

Bill Karwin
quelle
10
Und wie erhalten Sie die Lochabfrage, wenn PDO :: ATTR_EMULATE_PREPARES auf TRUE gesetzt ist?
Yasen Zhelev
2
@Yasen Zhelev: Wenn PDO Vorbereitungen emuliert, werden Parameterwerte in die Abfrage interpoliert, bevor die Abfrage vorbereitet wird. Daher sieht MySQL niemals die Version der Abfrage mit Parameterplatzhaltern. MySQL protokolliert nur die vollständige Abfrage.
Bill Karwin
2
@ Bill: 'Parameter werden auf der Clientseite nicht mit einer vorbereiteten Anweisung kombiniert' - warte - aber werden sie auf der Serverseite kombiniert? Oder wie fügt MySQL Werte in die Datenbank ein?
Stann
1
@afilina, nein, das kannst du nicht. Siehe meine Erklärung oben.
Bill Karwin
3
Wow, eine Gegenstimme? Bitte schießen Sie nicht auf den Boten. Ich beschreibe nur, wie es funktioniert.
Bill Karwin
107
/**
 * 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;
}
bigwebguy
quelle
6
Warum nicht einfach verwenden strtr(): schneller, einfacher, gleiche Ergebnisse. strtr($query, $params);
Tony Chiboucas
Was ist die Verwendung dafür?
Ich wollte nur vorbeischauen und mich auch bedanken, war außerhalb einer ganzen Extraklasse dafür, die ich jetzt dafür entfernt habe, da es winzig und brillant ist :). So verdammt nützlich, um alle Abfragen zu debuggen, die eine Anwendung auf jeder Seite durch Protokollieren
ausführt
Ich habe diese Funktion gesehen und es hat mich sehr gefreut, obwohl ich etwas nicht verstehe, warum prüfst du, ob $keyes eine ist stringund nicht $value? Vermisse ich etwas Der Grund, warum ich dies frage, ist wegen dieser Ausgabe, der zweite Parameter wird nicht als Zeichenfolge angesehen:string(115) "INSERT INTO tokens (token_type, token_hash, user_id) VALUES ('resetpassword', hzFs5RLMpKwTeShTjP9AkTA2jtxXls86, 1);"
Kerwin Sneijders
1
Dies ist ein guter Anfang, schlägt jedoch fehl, wenn der Wert eines $ param selbst ein Fragezeichen ("?") Enthält.
Chickenchilli
32

Ich habe die Methode dahingehend geändert, dass die Ausgabe von Arrays für Anweisungen wie WHERE IN (?) Behandelt wird.

UPDATE: Nur hinzugefügt, um nach NULL-Werten zu suchen und $ params zu duplizieren, damit die tatsächlichen $ param-Werte nicht geändert werden.

Tolle 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;
}
Mike
quelle
2
Ich denke du musst $values = $params;stattdessen tun $values = array().
Testen
Ein weiteres kleines Stück, das hier fehlt, sind Streicher. Um diese zu erfassen, setzen Sie dies über das is_arrayHäkchen:if (is_string($value)) $values[$key] = "'" . $value . "'";
Baumgesicht
Dieser Bindungswert ist in preg_replace nur auf einmal beschränkt. Fügen Sie diese Zeile hinzu, nachdem $values = $params; $values_limit = []; $words_repeated = array_count_values(str_word_count($sql, 1, ':_')); Sie diese zuerst in foreach $values_limit[$key] = (isset($words_repeated[':'.$key]) ? intval($words_repeated[':'.$key]) : 1);und zuerst in foreach hinzugefügt haben. $values_limit = [];Verwenden Sie foreach loop $ -Werte erneut, um preg_replace withisset($values_limit[$key])
vee
Zum Beispiel Schleife $ -Werte. if (is_array($values)) { foreach ($values as $key => $val) { if (isset($values_limit[$key])) { $sql = preg_replace(['/:'.$key.'/'], [$val], $sql, $values_limit[$key], $count); } } unset($key, $val); } else { $sql = preg_replace($keys, $values, $sql, 1, $count); }
Vee
12

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

Gibt die in einer vorbereiteten Anweisung enthaltenen Informationen direkt in der Ausgabe aus. Es enthält die verwendete SQL-Abfrage, die Anzahl der verwendeten Parameter (Params), die Liste der Parameter mit ihrem Namen, Typ (Paramtyp) als Ganzzahl, ihren Schlüsselnamen oder ihre Position und die Position in der Abfrage (falls dies der Fall ist) wird vom PDO-Treiber unterstützt, andernfalls ist es -1).

Mehr finden Sie auf der Informationen 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();

?>
Jimmy Kane
quelle
und für eine bessere Lesbarkeit:echo '<pre>'; $sth->debugDumpParams(); echo '</pre>';
SandroMarques
10

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

//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: [...] in der Nähe von 'ELECT * FROM Person WHERE age = 18' in Zeile 1

Es ist wichtig zu beachten, dass nur die ersten 80 Zeichen der Abfrage gedruckt werden.

JacopoStanchi
quelle
Ich weiß nicht, warum dies abgelehnt wurde. Es ist einfach und es funktioniert. Es funktioniert schnell. Viel schneller als das Protokoll einzuschalten, nach der richtigen Zeile im Protokoll zu suchen, das Protokoll zu deaktivieren und dann die Protokolldateien zu bereinigen.
Bojan Hrnkas
@BojanHrnkas Die Länge des Fehlerbeispiels ist sehr begrenzt. Bei einer so einfachen Abfrage ist es einfacher, einen Platzhalter manuell durch eine Variable zu ersetzen. Und diese Methode funktioniert nur, wenn Sie die Emulation aktivieren.
Ihr
9

Ein bisschen mehr zum Code von Mike hinzugefügt - gehen Sie die Werte durch, 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;
}
Chris Go
quelle
1
Sehr nützlich, ich habe einige Änderungen vorgenommen, um die bindParam- Funktion der PDOStatement- Klasse zu überschreiben und zu überprüfen, ob der Wert eine Zeichenfolge oder eine Ganzzahl mit den PDO: PARAMS- Werten ist.
Sergio Flores
wo können wir das sehen
Mawg sagt, Monica am
8

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

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

Glasroboter
quelle
1
Die debugDumpParams ist nicht dokumentiert php.net/manual/en/pdostatement.debugdumpparams.php
mloskot
Nee. $ queryString zeigt die enthaltenen Parameterwerte nicht an.
Andreas
5

Sie können die PDOStatement-Klasse erweitern, um die begrenzten Variablen zu erfassen und zur späteren Verwendung zu speichern. Dann können zwei Methoden hinzugefügt werden, eine für die Bereinigung von Variablen (debugBindedVariables) und eine andere zum Drucken 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 users WHERE user = 'user_test'

Array ([: test] => user_test)

Otamay
quelle
4

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

Während der Zugriff auf die interpolierte Abfragezeichenfolge bei der Fehlerbehebung ein wesentlicher Vorteil ist, wollten wir in der Lage sein, nur bestimmte Abfragen zu protokollieren (daher war die Verwendung der Datenbankprotokolle für diesen Zweck nicht ideal). Wir wollten auch in der Lage sein, die Protokolle zu verwenden, um den Zustand der Tabellen zu einem bestimmten Zeitpunkt wiederherzustellen. Daher mussten wir sicherstellen, dass die interpolierten Zeichenfolgen ordnungsgemäß maskiert wurden. Schließlich wollten wir diese Funktionalität auf unsere gesamte Codebasis ausweiten 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 Standard-PDOStatement-Objekts zu erweitern, um die parametrisierten Werte (oder Referenzen) zwischenzuspeichern. Wenn die Anweisung ausgeführt wird, verwenden Sie die Funktionalität des PDO-Objekts, um die Parameter ordnungsgemäß zu umgehen, wenn sie wieder in die Abfrage eingefügt werden Zeichenfolge. Wir könnten uns dann anschließen, um die Methode des Anweisungsobjekts auszuführen und die tatsächliche Abfrage zu protokollieren, die zu diesem Zeitpunkt ausgeführt wurde ( oder zumindest einer Reproduktion so treu wie möglich). .

Wie gesagt, wir wollten nicht die gesamte Codebasis ändern, um diese Funktionalität hinzuzufügen. Deshalb überschreiben wir die Standardeinstellungen bindParam()und bindValue()Methoden des PDOStatement-Objekts, speichern die gebundenen Daten zwischen und rufen dann parent::bindParam()oder parent :: aufbindValue() . 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 bereitE_PDOStatement->fullQuery . Dies kann ausgegeben werden, um die Abfrage anzuzeigen, oder beispielsweise in eine Protokolldatei geschrieben werden.

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

https://github.com/noahheck/E_PDOStatement

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

myesain
quelle
Danke für das Teilen. Keine positive Bewertung, da zu lange Antwort mit zu wenig Code
T30
1

Die erwähnte Eigenschaft $ queryString gibt wahrscheinlich nur die übergebene Abfrage zurück, ohne dass die Parameter durch ihre Werte ersetzt werden. In .Net muss der catch-Teil meines Abfrageausführers eine einfache Suche durchführen, um die Parameter durch ihre angegebenen Werte zu ersetzen, damit 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 die Parameter durch den zugewiesenen Wert zu ersetzen.

Kibbee
quelle
1

Sie können verwenden 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, dass dies nur für PHP> = 5.6 funktioniert

kurdtpage
quelle
0

Ich weiß, dass diese Frage etwas 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 veröffentlichen (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 auf die Funktion array_walk () beziehen und create_function durch eine anonyme Funktion ersetzen. Dies macht diesen guten Code funktionsfähig und kompatibel mit PHP 7.2 (und hofft auch auf zukünftige Versionen).

Sakura Kinomoto
quelle
-1

Etwas verwandt ... Wenn Sie nur versuchen, eine bestimmte Variable zu bereinigen, können Sie PDO :: quote verwenden . So suchen Sie beispielsweise nach mehreren partiellen LIKE-Bedingungen, wenn Sie mit einem eingeschränkten Framework wie CakePHP nicht weiterkommen:

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

Mikes Antwort funktioniert gut, bis Sie den Bindungswert "Wiederverwendung" verwenden.
Beispielsweise:

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 von Mike kann nur die erste ersetzen: Suche, aber nicht die zweite.
Also schreibe ich seine Antwort neu, um mit mehreren Parametern zu arbeiten, die richtig wiederverwendet werden können.

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;
}
vee
quelle
-1

preg_replace hat bei mir nicht funktioniert und als binding_ über 9 war, wurden binding_1 und binding_10 durch str_replace ersetzt (wobei die 0 zurückgelassen wurde), also habe ich die Ersetzungen rückwärts vorgenommen:

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;

}}

Hoffe, jemand findet es nützlich.

Markos F.
quelle
-1

Ich muss die vollständige Abfragezeichenfolge nach dem Bindungsparameter protokollieren, damit dies ein Teil meines Codes ist. 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);
}
ducminh1903
quelle