从 PDO 准备好的语句中获取原始 SQL 查询字符串

Posted

技术标签:

【中文标题】从 PDO 准备好的语句中获取原始 SQL 查询字符串【英文标题】:Getting raw SQL query string from PDO prepared statements 【发布时间】:2010-09-17 15:44:44 【问题描述】:

有没有办法在准备好的语句上调用 PDOStatement::execute() 时执行原始 SQL 字符串?出于调试目的,这将非常有用。

【问题讨论】:

对于 php >= 5.1,看看php.net/manual/en/pdostatement.debugdumpparams.php 检查单行函数pdo-debug. 我找到的最干净的方法是E_PDOStatement 库。你只需做$stmt = $pdo->prepare($query); /* ... */ echo $stmt->fullQuery;。它由 extending the PDOStatement class 工作,因此与 PDO API 允许的一样优雅。 【参考方案1】:

我假设您的意思是您想要最终的 SQL 查询,并在其中插入参数值。我知道这对调试很有用,但这不是准备好的语句的工作方式。参数不与客户端的预准备语句组合,因此 PDO 永远不应访问与其参数组合的查询字符串。

执行prepare()时将SQL语句发送到数据库服务器,执行execute()时单独发送参数。 mysql 的一般查询日志确实显示了最终的 SQL,其中包含在您执行 () 之后插入的值。以下是我的一般查询日志的摘录。我从 mysql CLI 而不是 PDO 运行查询,但原理是一样的。

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

如果你设置了 PDO 属性 PDO::ATTR_EMULATE_PREPARES,你也可以得到你想要的。在这种模式下,PDO 将参数插入到 SQL 查询中,并在您执行()时发送整个查询。 这不是一个真正的准备好的查询。您将通过在 execute() 之前将变量插入到 SQL 字符串中来规避准备好的查询的好处。


来自@afilina 的回复:

不,文本 SQL 查询在执行期间与参数结合。所以 PDO 没有什么可以显示给你的。

在内部,如果您使用 PDO::ATTR_EMULATE_PREPARES,PDO 会复制 SQL 查询并将参数值插入其中,然后再进行准备和执行。但是 PDO 不会公开这个修改后的 SQL 查询。

PDOStatement 对象有一个属性 $queryString,但这仅在 PDOStatement 的构造函数中设置,并且在使用参数重写查询时不会更新。

PDO 要求他们公开重写的查询是一个合理的功能请求。但即使这样也不会给你“完整”的查询,除非你使用 PDO::ATTR_EMULATE_PREPARES。

这就是为什么我在上面展示了使用 MySQL 服务器的一般查询日志的解决方法,因为在这种情况下,即使是带有参数占位符的准备好的查询也会在服务器上重写,并将参数值回填到查询字符串中。但这仅在记录期间完成,而不是在查询执行期间完成。

【讨论】:

当 PDO::ATTR_EMULATE_PREPARES 设置为 TRUE 时,如何获取孔查询? @Yasen Zhelev:如果 PDO 正在模拟准备,那么它会在准备查询之前将参数值插入到查询中。所以 MySQL 永远不会看到带有参数占位符的查询版本。 MySQL 只记录完整的查询。 @Bill:'参数没有与客户端的准备好的语句结合'-等等-但是它们在服务器端结合了吗?或者mysql如何将值插入DB? @afilina,不,你不能。见我上面的解释。 哇,投反对票?请不要向信使开枪。我只是在描述它是如何工作的。【参考方案2】:
/**
 * 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;

【讨论】:

为什么不直接使用strtr():更快、更简单、结果相同。 strtr($query, $params); 这个有什么用? 只是想停下来并表示感谢,因为它很小而且很聪明,所以我现在已经删除了整个额外的课程以支持它:)。通过记录它们来调试应用程序在每个页面上执行的所有查询非常有用:D 看到这个功能,我很高兴,虽然,我不明白,为什么你检查$keystring而不是$value?我错过了什么吗?我问这个的原因是因为这个输出,第二个参数不是字符串:string(115) "INSERT INTO tokens (token_type, token_hash, user_id) VALUES ('resetpassword', hzFs5RLMpKwTeShTjP9AkTA2jtxXls86, 1);" 这是一个好的开始,但如果 $param 的值本身包含问号(“?”),它就会失败。【参考方案3】:

我修改了该方法以包括处理 WHERE IN (?) 等语句的数组输出。

更新:刚刚添加了对 NULL 值和重复 $params 的检查,因此不会修改实际的 $param 值。

伟大的工作 bigwebguy,谢谢!

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

【讨论】:

我认为你必须使用$values = $params; 而不是$values = array() 这里遗漏的另一个小部分是字符串。为了捕捉这些,把它放在is_array检查上方:if (is_string($value)) $values[$key] = "'" . $value . "'"; 这只是限制绑定值在 preg_replace 中只有一次。在 $values = $params; $values_limit = []; $words_repeated = array_count_values(str_word_count($sql, 1, ':_')); 之后添加这一行 if in foreach $values_limit[$key] = (isset($words_repeated[':'.$key]) ? intval($words_repeated[':'.$key]) : 1); 和 this in first else in foreach $values_limit = []; 再次使用 foreach 循环 $values 到 preg_replace 与 isset($values_limit[$key]) 例如循环 $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); 这到处都是。【参考方案4】:

一种解决方案是自愿在查询中输入错误并打印错误消息:

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

标准输出:

SQLSTATE[42000]:语法错误或访问冲突:[...] 在第 1 行的 'ELECT * FROM Person WHERE age=18' 附近

需要注意的是,它只打印查询的前 80 个字符。

【讨论】:

我不知道为什么这被否决了。这很简单,而且很有效。它工作得很快。比打开日志,在日志中搜索正确的行,然后禁用日志,然后清理日志文件要快得多。 @BojanHrnkas 错误样本的长度非常有限。对于这样一个简单的查询,只需手动将占位符替换为变量会更容易。而且此方法仅在您启用仿真时才有效。【参考方案5】:

可能有点晚了,但现在有PDOStatement::debugDumpParams

将准备好的语句包含的信息直接转储到 输出。它将提供正在使用的 SQL 查询,数量 使用的参数(Params),参数列表及其名称, type (paramtype) 作为一个整数,它们的键名或位置,以及 查询中的位置(如果 PDO 驱动程序支持, 否则,它将是 -1)。

您可以在official php docs 上找到更多信息

例子:

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

?>

【讨论】:

注意PHP Bug #52384: PDOStatement::debugDumpParams does not emit the bind parameter value. 为了更好的可读性:echo '&lt;pre&gt;'; $sth-&gt;debugDumpParams(); echo '&lt;/pre&gt;';【参考方案6】:

Mike 在代码中添加了更多内容 - 遍历值以添加单引号

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

【讨论】:

非常有用,我做了一些修改来覆盖 PDOStatement 类的 bindParam 函数,并使用PDO:PARAMS 值。 我们在哪里可以看到?【参考方案7】:

PDOStatement 有一个公共属性 $queryString。应该是你想要的。

我刚刚注意到 PDOStatement 有一个未记录的方法 debugDumpParams(),您可能还想查看它。

【讨论】:

没有记录 debugDumpParams php.net/manual/en/pdostatement.debugdumpparams.php 不。 $queryString 不显示包含的参数值。【参考方案8】:

为了自己的需要,我花了很多时间研究这种情况。这个和其他几个 SO 线程对我帮助很大,所以我想分享我的想法。

虽然在故障排除时可以访问插入的查询字符串是一个显着的好处,但我们希望能够仅维护某些查询的日志(因此,为此目的使用数据库日志并不理想)。我们还希望能够使用日志在任何给定时间重新创建表的条件,因此,我们需要确保正确转义了插值字符串。最后,我们希望将此功能扩展到我们的整个代码库,必须尽可能少地重写它(截止日期、营销等;你知道它是怎么回事)。

我的解决方案是扩展默认 PDOStatement 对象的功能以缓存参数化的值(或引用),并且在执行语句时,使用 PDO 对象的功能在将参数重新注入时正确转义参数到查询字符串。然后我们可以绑定到语句对象的执行方法并记录当时执行的实际查询(或至少尽可能忠实地再现)

正如我所说,我们不想修改整个代码库来添加这个功能,所以我们覆盖了 PDOStatement 对象的默认 bindParam()bindValue() 方法,对绑定的数据进行缓存,然后致电parent::bindParam() 或父母::bindValue()。这使我们现有的代码库能够继续正常运行。

最后,当调用execute() 方法时,我们执行插值并将结果字符串作为新属性E_PDOStatement-&gt;fullQuery 提供。这可以输出以查看查询,例如,写入日志文件。

该扩展以及安装和配置说明可在 github 上获得:

https://github.com/noahheck/E_PDOStatement

免责声明: 显然,正如我所提到的,我编写了这个扩展。因为它是在许多线程的帮助下开发的,所以我想在这里发布我的解决方案,以防其他人遇到这些线程,就像我一样。

【讨论】:

感谢分享。没有投票,因为答案太长,代码太少【参考方案9】:

您可以扩展 PDOStatement 类来捕获有界变量并将它们存储起来以备后用。然后可以添加 2 种方法,一种用于变量清理 (debugBindedVariables),另一种用于使用这些变量打印查询 (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);
  

然后你可以使用这个继承的类进行调试。

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

导致

从用户中选择用户 WHERE user = 'user_test'

数组 ( [:test] => user_test )

【讨论】:

【参考方案10】:

你可以使用sprintf(str_replace('?', '"%s"', $sql), ...$params);

这是一个例子:

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

请注意,这只适用于 PHP >= 5.6

【讨论】:

【参考方案11】:

提到的 $queryString 属性可能只会返回传入的查询,而不会将参数替换为其值。在 .Net 中,我的查询执行器的 catch 部分使用提供的值对参数进行简单的搜索替换,以便错误日志可以显示用于查询的实际值。您应该能够枚举 PHP 中的参数,并将参数替换为它们的赋值。

【讨论】:

【参考方案12】:

我知道这个问题有点老了,但是,我很久以前就在使用这个代码(我使用了来自@chris-go 的响应),现在,这些代码在 PHP 7.2 中已经过时了

我将发布这些代码的更新版本(主要代码来自@bigwebguy、@mike和@chris-go,它们都是这个问题的答案):

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

请注意,代码上的更改是在 array_walk() 函数上,将 create_function 替换为匿名函数。这使得这些优秀的代码具有功能性并与 PHP 7.2 兼容(希望未来的版本也如此)。

【讨论】:

我很困惑,这似乎根本不像它应该做的那样?它添加了\' 而不是'。此外,它似乎没有处理 ' inside 值,从而为 SQL-injection 敞开大门?【参考方案13】:

现有的答案似乎都不完整或不安全,所以我想出了这个功能,它有以下改进:

适用于未命名 (?) 和命名 (:foo) 参数。

使用PDO::quote() 正确转义不是NULLintfloatbool 的值。

正确处理包含"?"":foo" 的字符串值,而不会将它们误认为占位符。

    function interpolateSQL(PDO $pdo, string $query, array $params) : string 
        $s = chr(2); // Escape sequence for start of placeholder
        $e = chr(3); // Escape sequence for end of placeholder
        $keys = [];
        $values = [];

        // Make sure we use escape sequences that are not present in any value
        // to escape the placeholders.
        foreach ($params as $key => $value) 
            while( mb_stripos($value, $s) !== false ) $s .= $s;
            while( mb_stripos($value, $e) !== false ) $e .= $e;
        
        
        
        foreach ($params as $key => $value) 
            // Build a regular expression for each parameter
            $keys[] = is_string($key) ? "/$s:$key$e/" : "/$s\?$e/";

            // Treat each value depending on what type it is. 
            // While PDO::quote() has a second parameter for type hinting, 
            // it doesn't seem reliable (at least for the SQLite driver).
            if( is_null($value) )
                $values[$key] = 'NULL';
            
            elseif( is_int($value) || is_float($value) )
                $values[$key] = $value;
            
            elseif( is_bool($value) )
                $values[$key] = $value ? 'true' : 'false';
            
            else
                $value = str_replace('\\', '\\\\', $value);
                $values[$key] = $pdo->quote($value);
            
        

        // Surround placehodlers with escape sequence, so we don't accidentally match
        // "?" or ":foo" inside any of the values.
        $query = preg_replace(['/\?/', '/(:[a-zA-Z0-9_]+)/'], ["$s?$e", "$s$1$e"], $query);

        // Replace placeholders with actual values
        $query = preg_replace($keys, $values, $query, 1, $count);

        // Verify that we replaced exactly as many placeholders as there are keys and values
        if( $count !== count($keys) || $count !== count($values) )
            throw new \Exception('Number of replacements not same as number of keys and/or values');
        

        return $query;
    

我相信它可以进一步改进。

在我的例子中,我最终只记录了实际的“未准备查询”(即包含占位符的 SQL)以及 JSON 编码的参数。但是,此代码可能会在您确实需要插入最终 SQL 查询的某些用例中使用。

【讨论】:

为什么,为什么你们从来没有想过引用数据,使您的“插值”查询容易出错和各种注入?为什么有一些 PDO 不支持的 is_array 案例?为什么在 PDO 相关函数中使用 SQLite3 扩展?当 PDO 具有引用适用于任何驱动程序的数据的函数时,为什么有人会使用已失效的 mysql_real_escape_string()?这个答案的重点是非常令人困惑,因为最后一段基本上说您自己不使用此代码 @YourCommonSense 随意编辑或添加更好的答案。 这要好得多。虽然我真的不明白这段代码如何“正确处理包含“?”和“:foo”的字符串值,而不会将它们误认为占位符”,但对我来说似乎没有。 @YourCommonSense 它用chr(2)chr(3) 包围查询中的原始占位符。因此,只要您的值不包含&lt;ASCII 2&gt;?&lt;ASCII 3&gt;,它就会起作用。如果您希望值包含转义字符,请相应地修改代码。 谢谢,我明白了【参考方案14】:

preg_replace 对我不起作用,当 binding_ 超过 9 时,binding_1 和 binding_10 被替换为 str_replace(留下 0),所以我向后进行了替换:

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;

希望有人觉得它有用。

【讨论】:

【参考方案15】:

我需要在绑定参数之后记录完整的查询字符串,所以这是我的代码中的一部分。希望对遇到相同问题的每个人都有用。

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

【讨论】:

【参考方案16】:

有点相关...如果您只是想清理特定变量,您可以使用PDO::quote。例如,如果您遇到像 CakePHP 这样的有限框架,要搜索多个部分 LIKE 条件:

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

【讨论】:

【参考方案17】:

Mike's answer 在您使用“重用”绑定值之前工作良好。 例如:

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)

迈克的答案只能替换第一个 :search 而不能替换第二个。 因此,我重写了他的答案以使用可以正确重复使用的多个参数。

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;

【讨论】:

以上是关于从 PDO 准备好的语句中获取原始 SQL 查询字符串的主要内容,如果未能解决你的问题,请参考以下文章

从 PDO 准备语句中检索(或模拟)完整查询

从 PDO 准备好的语句中获取查询 [重复]

带有位域的 PDO 准备好的语句

如何查看准备好的 PDO SQL 语句 [重复]

使用 PDO 准备好的语句 MySQL 错误

在查询中使用 PDO 准备好的语句和 LIMIT 时出错 [重复]