Obtention d'une chaîne de requête SQL brute à partir d'instructions préparées PDO


130

Existe-t-il un moyen d'exécuter la chaîne SQL brute lors de l'appel de PDOStatement :: execute () sur une instruction préparée? À des fins de débogage, ce serait extrêmement utile.



1
Vérifiez la fonction pdo-debug sur une ligne .
Sliq du

Le moyen le plus propre que j'ai trouvé est la bibliothèque E_PDOStatement . Vous faites juste $stmt = $pdo->prepare($query); /* ... */ echo $stmt->fullQuery;. Il fonctionne en étendant la classe PDOStatement et est donc aussi élégant que l'API PDO le permet.
ComFreek

Réponses:


110

Je suppose que vous voulez dire que vous voulez la requête SQL finale, avec des valeurs de paramètres interpolées. Je comprends que cela serait utile pour le débogage, mais ce n'est pas la façon dont les instructions préparées fonctionnent. Les paramètres ne sont pas combinés avec une instruction préparée côté client, donc PDO ne doit jamais avoir accès à la chaîne de requête combinée avec ses paramètres.

L'instruction SQL est envoyée au serveur de base de données lorsque vous préparez () et les paramètres sont envoyés séparément lorsque vous exécutez (). Le journal des requêtes générales de MySQL affiche le SQL final avec des valeurs interpolées après l'exécution (). Voici un extrait de mon journal de requêtes général. J'ai exécuté les requêtes depuis la CLI mysql, pas depuis PDO, mais le principe est le même.

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

Vous pouvez également obtenir ce que vous voulez si vous définissez l'attribut PDO PDO :: ATTR_EMULATE_PREPARES. Dans ce mode, PDO interpole les paramètres dans la requête SQL et envoie la requête entière lorsque vous exécutez (). Ce n'est pas une vraie requête préparée. Vous contournerez les avantages des requêtes préparées en interpolant les variables dans la chaîne SQL avant execute ().


Re commentaire de @afilina:

Non, la requête SQL textuelle n'est pas combinée avec les paramètres lors de l'exécution. Il n'y a donc rien à vous montrer à PDO.

En interne, si vous utilisez PDO :: ATTR_EMULATE_PREPARES, PDO fait une copie de la requête SQL et y interpole les valeurs des paramètres avant d'effectuer la préparation et l'exécution. Mais PDO n'expose pas cette requête SQL modifiée.

L'objet PDOStatement a une propriété $ queryString, mais celle-ci est définie uniquement dans le constructeur du PDOStatement et n'est pas mise à jour lorsque la requête est réécrite avec des paramètres.

Ce serait une demande de fonctionnalité raisonnable pour PDO de leur demander d'exposer la requête réécrite. Mais même cela ne vous donnerait pas la requête "complète" sauf si vous utilisez PDO :: ATTR_EMULATE_PREPARES.

C'est pourquoi je montre la solution de contournement ci-dessus consistant à utiliser le journal des requêtes générales du serveur MySQL, car dans ce cas, même une requête préparée avec des espaces réservés de paramètres est réécrite sur le serveur, avec des valeurs de paramètres remplies dans la chaîne de requête. Mais cela n'est fait que pendant la journalisation, pas pendant l'exécution de la requête.


10
Et comment obtenir la requête de trou lorsque PDO :: ATTR_EMULATE_PREPARES est défini sur TRUE?
Yasen Zhelev

2
@Yasen Zhelev: Si PDO émule se prépare, alors il interpolera les valeurs des paramètres dans la requête avant de préparer la requête. Ainsi MySQL ne voit jamais la version de la requête avec des espaces réservés de paramètre. MySQL enregistre uniquement la requête complète.
Bill Karwin

2
@ Bill: «Les paramètres ne sont pas combinés avec une instruction préparée côté client» - attendez - mais sont-ils combinés côté serveur? Ou comment mysql insère-t-il des valeurs dans DB?
Stann

1
@afilina, non, vous ne pouvez pas. Voir mon explication ci-dessus.
Bill Karwin

3
Wow, un vote négatif? Veuillez ne pas tirer sur le messager. Je décris simplement comment cela fonctionne.
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;
}

6
pourquoi ne pas simplement utiliser strtr(): plus rapide, plus simple, les mêmes résultats. strtr($query, $params);
Tony Chiboucas

A quoi ça sert?

Je voulais juste m'arrêter et offrir mes remerciements également, était en dehors d'une classe supplémentaire pour cela que j'ai maintenant retirée en faveur de cela car c'est minuscule et brillant :). Tellement utile pour déboguer toutes les requêtes qu'une application fait sur chaque page en les enregistrant: D
NaughtySquid

J'ai vu cette fonction et cela m'a fait très plaisir, bien que, quelque chose que je ne comprends pas, pourquoi vérifiez-vous le $keypour être un stringet non $value? Est-ce que je manque quelque chose? La raison pour laquelle je pose cette question est à cause de cette sortie, le deuxième paramètre n'est pas vu comme une chaîne:string(115) "INSERT INTO tokens (token_type, token_hash, user_id) VALUES ('resetpassword', hzFs5RLMpKwTeShTjP9AkTA2jtxXls86, 1);"
Kerwin Sneijders

1
C'est un bon début, mais cela échoue si la valeur d'un $ param elle-même inclut un point d'interrogation ("?").
chickenchilli

32

J'ai modifié la méthode pour inclure la gestion de la sortie des tableaux pour des instructions telles que WHERE IN (?).

MISE À JOUR: Je viens d'ajouter une vérification de la valeur NULL et des $ param en double pour que les valeurs réelles de $ param ne soient pas modifiées.

Excellent travail bigwebguy et merci!

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

2
Je pense que vous devez faire $values = $params;au lieu de $values = array().
test le

Un autre petit morceau qui manque ici est les cordes. Afin de les capturer, mettez ceci au-dessus de la is_arrayvérification:if (is_string($value)) $values[$key] = "'" . $value . "'";
treeface

Il s'agit d'une valeur de liaison limitée à une seule fois dans preg_replace. ajoutez cette ligne après avoir $values = $params; $values_limit = []; $words_repeated = array_count_values(str_word_count($sql, 1, ':_')); ajouté ceci à l'intérieur en premier si dans foreach $values_limit[$key] = (isset($words_repeated[':'.$key]) ? intval($words_repeated[':'.$key]) : 1);et ceci en premier dans foreach, $values_limit = [];utilisez à nouveau foreach loop $ values ​​à preg_replace avecisset($values_limit[$key])
vee

par exemple loop $ values. 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

Un peu tard probablement mais maintenant il y a PDOStatement::debugDumpParams

Vide les informations contenues dans une instruction préparée directement sur la sortie. Il fournira la requête SQL utilisée, le nombre de paramètres utilisés (Params), la liste des paramètres, avec leur nom, type (paramtype) sous forme d'entier, leur nom ou position de clé et la position dans la requête (si cela est supporté par le pilote PDO, sinon, il sera -1).

Vous pouvez en savoir plus sur la documentation php officielle

Exemple:

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

?>


et pour une meilleure lisibilité:echo '<pre>'; $sth->debugDumpParams(); echo '</pre>';
SandroMarques

10

Une solution est de mettre volontairement une erreur dans la requête et d'imprimer le message d'erreur:

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

Sortie standard:

SQLSTATE [42000]: Erreur de syntaxe ou violation d'accès: [...] près de 'ELECT * FROM Person WHERE age = 18' à la ligne 1

Il est important de noter qu'il n'imprime que les 80 premiers caractères de la requête.


Je ne sais pas pourquoi cela a été rejeté. C'est simple et ça marche. Ça marche vite. Beaucoup plus rapide que d'activer le journal, de rechercher la bonne ligne dans le journal, puis de désactiver le journal, puis de nettoyer les fichiers journaux.
Bojan Hrnkas

@BojanHrnkas la longueur de l'échantillon d'erreur est très limitée. Pour une requête aussi simple, il est plus facile de remplacer manuellement un espace réservé par une variable. Et cette méthode ne fonctionne que si vous activez l'émulation.
Votre bon sens

9

Ajout d'un peu plus au code par Mike - parcourez les valeurs pour ajouter des guillemets simples

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

1
Très utilement, j'ai fait quelques modifications pour remplacer la fonction bindParam de la classe PDOStatement et valider si la valeur est une chaîne ou un entier avec les valeurs PDO: PARAMS .
Sergio Flores

1
où pouvons-nous voir cela?
Mawg dit de réintégrer Monica le


5

Vous pouvez étendre la classe PDOStatement pour capturer les variables limitées et les stocker pour une utilisation ultérieure. Ensuite, 2 méthodes peuvent être ajoutées, une pour le nettoyage des variables (debugBindedVariables) et une autre pour imprimer la requête avec ces variables (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);
  }
}

Et puis vous pouvez utiliser cette classe héritée pour le débogage des purpouses.

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

Résultant en

SELECT utilisateur FROM utilisateurs WHERE user = 'user_test'

Tableau ([: test] => test_utilisateur)


4

J'ai passé beaucoup de temps à étudier cette situation pour mes propres besoins. Cela et plusieurs autres fils SO m'ont beaucoup aidé, alors je voulais partager ce que j'avais trouvé.

Bien que l'accès à la chaîne de requête interpolée soit un avantage significatif lors du dépannage, nous voulions pouvoir maintenir un journal de certaines requêtes uniquement (par conséquent, l'utilisation des journaux de la base de données à cette fin n'était pas idéale). Nous voulions également pouvoir utiliser les journaux pour recréer l'état des tables à un moment donné, par conséquent, nous devions nous assurer que les chaînes interpolées étaient correctement échappées. Enfin, nous voulions étendre cette fonctionnalité à l'ensemble de notre base de code en en réécrivant le moins possible (délais, marketing, etc., vous savez comment c'est).

Ma solution était d'étendre la fonctionnalité de l'objet PDOStatement par défaut pour mettre en cache les valeurs paramétrées (ou références), et lorsque l'instruction est exécutée, utiliser la fonctionnalité de l'objet PDO pour échapper correctement aux paramètres lorsqu'ils sont réinjectés dans la requête chaîne. Nous pourrions alors nous connecter pour exécuter la méthode de l'objet instruction et enregistrer la requête réelle qui a été exécutée à ce moment-là ( ou au moins aussi fidèle que possible d'une reproduction) .

Comme je l'ai dit, nous ne voulions pas modifier la base de code entière pour ajouter cette fonctionnalité, nous écrasons donc la valeur par défaut bindParam()et les bindValue()méthodes de l'objet PDOStatement, faisons notre mise en cache des données liées, puis appelons parent::bindParam()ou parent :: bindValue(). Cela a permis à notre base de code existante de continuer à fonctionner normalement.

Enfin, lorsque la execute()méthode est appelée, nous effectuons notre interpolation et fournissons la chaîne résultante en tant que nouvelle propriétéE_PDOStatement->fullQuery . Cela peut être généré pour afficher la requête ou, par exemple, écrit dans un fichier journal.

L'extension, ainsi que les instructions d'installation et de configuration, sont disponibles sur github:

https://github.com/noahheck/E_PDOStatement

AVERTISSEMENT :
Évidemment, comme je l'ai mentionné, j'ai écrit cette extension. Parce qu'il a été développé avec l'aide de nombreux threads ici, je voulais publier ma solution ici au cas où quelqu'un d'autre rencontrerait ces fils, tout comme je l'ai fait.


Merci d'avoir partagé. Pas de vote favorable car réponse trop longue avec trop peu de code
T30

1

La propriété $ queryString mentionnée ne retournera probablement que la requête transmise, sans que les paramètres soient remplacés par leurs valeurs. Dans .Net, je demande à la partie catch de mon exécuteur de requête d'effectuer une recherche simple et de remplacer les paramètres par leurs valeurs qui ont été fournies afin que le journal des erreurs puisse afficher les valeurs réelles utilisées pour la requête. Vous devriez être capable d'énumérer les paramètres en PHP et de remplacer les paramètres par leur valeur assignée.


1

Vous pouvez utiliser sprintf(str_replace('?', '"%s"', $sql), ...$params);

Voici un exemple:

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";
}

Notez que cela ne fonctionne que pour PHP> = 5.6


0

Je sais que cette question est un peu ancienne, mais j'utilise ce code depuis il y a très longtemps (j'ai utilisé la réponse de @ chris-go), et maintenant, ce code est obsolète avec PHP 7.2

Je publierai une version mise à jour de ce code (le crédit pour le code principal provient de @bigwebguy , @mike et @ chris-go , toutes les réponses à cette question):

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

Notez que le changement de code concerne la fonction array_walk (), en remplaçant create_function par une fonction anonyme. Cela rend ce bon morceau de code fonctionnel et compatible avec PHP 7.2 (et j'espère aussi les versions futures).


-1

Un peu lié ... si vous essayez simplement de nettoyer une variable particulière, vous pouvez utiliser PDO :: quote . Par exemple, pour rechercher plusieurs conditions LIKE partielles si vous êtes coincé avec un framework limité comme CakePHP:

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

-1

La réponse de Mike fonctionne bien jusqu'à ce que vous utilisiez la valeur de liaison "réutiliser".
Par exemple:

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)

La réponse de Mike ne peut remplacer que la première: la recherche mais pas la seconde.
Donc, je réécris sa réponse pour travailler avec plusieurs paramètres qui peuvent être réutilisés correctement.

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

-1

preg_replace ne fonctionnait pas pour moi et lorsque binding_ était supérieur à 9, binding_1 et binding_10 étaient remplacés par str_replace (laissant le 0 derrière), j'ai donc effectué les remplacements à l'envers:

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;

}

J'espère que quelqu'un le trouvera utile.


-1

J'ai besoin de journaliser la chaîne de requête complète après le paramètre de liaison, c'est donc un élément de mon code. J'espère que c'est utile pour tout le monde qui a le même problème.

/**
 * 
 * @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);
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.