Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/247.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 从PDO准备的语句中获取原始SQL查询字符串_Php_Sql_Mysql_Pdo_Pdostatement - Fatal编程技术网

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

Php 从PDO准备的语句中获取原始SQL查询字符串,php,sql,mysql,pdo,pdostatement,Php,Sql,Mysql,Pdo,Pdostatement,在对准备好的语句调用PDOStatement::execute()时,是否有方法执行原始SQL字符串?出于调试目的,这将非常有用。PDO语句有一个公共属性$queryString。这应该是你想要的 我刚刚注意到PDO语句有一个未记录的方法debugDumpParams(),您可能还想看看。提到的$queryString属性可能只返回传入的查询,而不将参数替换为它们的值。在.Net中,我让查询执行器的catch部分对参数进行简单的搜索,并将其替换为提供的值,以便错误日志可以显示用于查询的实际值。您

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

PDO语句有一个公共属性$queryString。这应该是你想要的


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

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

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

SQL语句在执行prepare()时发送到数据库服务器,而参数在执行()时单独发送。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查询并在执行prepare和execute之前将参数值插入其中。但是PDO不公开这个修改过的SQL查询

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

对于PDO来说,要求他们公开重写的查询是一个合理的特性请求。但即使这样,也不会给出“完整”的查询,除非您使用PDO::ATTR_SIMULATE_


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

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

/**
 * 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;
}
更新:刚刚添加了空值检查和重复的$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;
}

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

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

尽管在故障排除时访问插值查询字符串是一个显著的好处,但我们希望能够维护仅包含某些查询的日志(因此,为此目的使用数据库日志并不理想)。我们还希望能够使用日志在任何给定时间重新创建表的条件,因此,我们需要确保正确转义插值字符串。最后,我们希望将此功能扩展到我们的整个代码库,而只需重新编写尽可能少的代码(截止日期、市场营销等等;您知道它是如何实现的)

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

正如我所说,我们不想修改整个代码库来添加此功能,因此我们覆盖PDOStatement对象的默认
bindParam()
bindValue()
方法,缓存绑定数据,然后调用
parent::bindParam()
或parent::
bindValue()
。这允许我们现有的代码库继续正常工作

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

github上提供了扩展以及安装和配置说明:

免责声明

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

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

将准备好的语句中包含的信息直接转储到 输出。它将提供正在使用的SQL查询的 使用的参数(Params),参数列表及其na
<?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();

?>
$pdo = $this->getDataSource()->getConnection();
$results = $this->find('all', array(
    'conditions' => array(
        'Model.name LIKE ' . $pdo->quote("%{$keyword1}%"),
        'Model.name LIKE ' . $pdo->quote("%{$keyword2}%"),
    ),
);
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)
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;
}
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;
/**
 * 
 * @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);
}
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());
//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();
}
/**
 * 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;
}
    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;
    }