如何debuggingPDO数据库查询?

在转移到PDO之前,我通过连接string在PHP中创build了SQL查询。 如果我得到数据库语法错误,我可以回声最后的SQL查询string,在数据库上自己尝试,并调整它,直到我修复错误,然后把它放回到代码。

准备好的PDO语句更快,更好,更安全,但有一件事情让我困扰:我发送到数据库的时候从来没有看到最终的查询。 当我在我的Apache日志或我的自定义日志文件(我logging在catch块内的错误)的语法错误,我看不到造成他们的查询。

有没有办法捕捉由PDO发送到数据库的完整SQL查询并将其logging到文件?

你这样说:

发送到数据库时,我从来没有看到最终的查询

那么,实际上,在使用准备好的语句时,不存在“ 最终查询 ”这样的事情

  • 首先,一个声明被发送到DB,并在那里准备
    • 数据库parsing查询,并build立它的内部表示
  • 而且,当你绑定variables并执行语句时,只有variables被发送到数据库
    • 数据库将这些价值“注入”到其声明的内部表示中

所以,要回答你的问题:

有没有办法捕捉由PDO发送到数据库的完整SQL查询并将其logging到文件?

不:因为在任何地方都没有“ 完整的SQL查询 ”,所以无法捕获它。

出于debugging目的,您可以做的最好的事情是通过将值注入到语句的SQLstring中来“重新构build”“真实”SQL查询。

我通常在这种情况下做的是:

  • 使用占位符回显对应于语句的SQL代码
  • 并在之后使用var_dump (或等价物)来显示参数的值
  • 即使您没有任何可以执行的“真实”查询,通常这也足以查看可能的错误。

这在debugging方面并不是很好,但这是准备好的报表的价格和它带来的好处。

查看数据库日志

尽pipePascal MARTIN是正确的,PDO并没有将完整的查询同时发送到数据库, 但是ryeguybuild议使用数据库的日志loggingfunction,实际上允许我看到由数据库组装和执行的完整查询。

这里是如何:(这些说明是在Windows机器上的MySQL – 你的里程可能会有所不同)

  • my.ini[mysqld]部分,添加一个log命令,如log="C:\Program Files\MySQL\MySQL Server 5.1\data\mysql.log"
  • 重新启动MySQL。
  • 它将开始logging该文件中的每个查询。

该文件将快速增长,所以一定要删除它,并在testing完成后closures日志logging。

一个旧的职位,但也许有人会觉得这有用;

 function pdo_sql_debug($sql,$placeholders){ foreach($placeholders as $k => $v){ $sql = preg_replace('/:'.$k.'/',"'".$v."'",$sql); } return $sql; } 

可能你想要做的是使用debugdumparams()它不会为你build立准备好的语句,但它会显示你的参数。

当然你可以使用这种模式进行debugging{{ PDO::ATTR_ERRMODE }}只需在查询前添加新行,然后你将显示debugging行。

 $db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING ); $db->query('SELECT *******'); 

没有在客户端准备PDO查询。 PDO只是将SQL查询和参数发送到数据库服务器。 数据库是什么替代(的? )的。 你有两个select:

  • 使用你的数据库的日志function(但即使如此,它通常至less与Postgres显示为两个单独的语句(即“不是最终”))
  • 输出SQL查询和参数,并将其拼接在一起

下面是一个函数,用来查看有效的SQL是什么,从php.net上的 “Mark”注释开始:

 function sql_debug($sql_string, array $params = null) { if (!empty($params)) { $indexed = $params == array_values($params); foreach($params as $k=>$v) { if (is_object($v)) { if ($v instanceof \DateTime) $v = $v->format('Ymd H:i:s'); else continue; } elseif (is_string($v)) $v="'$v'"; elseif ($v === null) $v='NULL'; elseif (is_array($v)) $v = implode(',', $v); if ($indexed) { $sql_string = preg_replace('/\?/', $v, $sql_string, 1); } else { if ($k[0] != ':') $k = ':'.$k; //add leading colon if it was left out $sql_string = str_replace($k,$v,$sql_string); } } } return $sql_string; } 

几乎没有什么关于错误显示除了检查错误日志,但有一个相当有用的function:

 <?php /* Provoke an error -- bogus SQL syntax */ $stmt = $dbh->prepare('bogus sql'); if (!$stmt) { echo "\PDO::errorInfo():\n"; print_r($dbh->errorInfo()); } ?> 

( 来源链接 )

很明显,这个代码可以被修改为用作exception消息或任何其他types的error handling

例如你有这个pdo声明:

 $query="insert into tblTest (field1, field2, field3) values (:val1, :val2, :val3)"; $res=$db->prepare($query); $res->execute(array( ':val1'=>$val1, ':val2'=>$val2, ':val3'=>$val3, )); 

现在你可以通过定义一个这样的数组来获得执行的查询:

 $assoc=array( ':val1'=>$val1, ':val2'=>$val2, ':val3'=>$val3, ); $exQuery=str_replace(array_keys($assoc), array_values($assoc), $query); echo $exQuery; 

search互联网我发现这是一个可以接受的解 使用不同的类而不是PDO,并通过魔术函数调用来调用PDO函数。 我不确定这会造成严重的性能问题。 但是它可以使用,直到一个明智的日志function被添加到PDO。

所以按照这个线程 ,你可以为你的PDO连接编写一个包装器,当你得到一个错误时它可以logging并引发一个exception。

这是一个简单的例子:

 class LoggedPDOSTatement extends PDOStatement { function execute ($array) { parent::execute ($array); $errors = parent::errorInfo(); if ($errors[0] != '00000'): throw new Exception ($errors[2]); endif; } } 

所以你可以使用这个类而不是PDOStatement:

 $this->db->setAttribute (PDO::ATTR_STATEMENT_CLASS, array ('LoggedPDOStatement', array())); 

这里提到的PDO装饰器实现:

 class LoggedPDOStatement { function __construct ($stmt) { $this->stmt = $stmt; } function execute ($params = null) { $result = $this->stmt->execute ($params); if ($this->stmt->errorCode() != PDO::ERR_NONE): $errors = $this->stmt->errorInfo(); $this->paint ($errors[2]); endif; return $result; } function bindValue ($key, $value) { $this->values[$key] = $value; return $this->stmt->bindValue ($key, $value); } function paint ($message = false) { echo '<pre>'; echo '<table cellpadding="5px">'; echo '<tr><td colspan="2">Message: ' . $message . '</td></tr>'; echo '<tr><td colspan="2">Query: ' . $this->stmt->queryString . '</td></tr>'; if (count ($this->values) > 0): foreach ($this->values as $key => $value): echo '<tr><th align="left" style="background-color: #ccc;">' . $key . '</th><td>' . $value . '</td></tr>'; endforeach; endif; echo '</table>'; echo '</pre>'; } function __call ($method, $params) { return call_user_func_array (array ($this->stmt, $method), $params); } } 

要在WAMP中loggingMySQL,您需要编辑my.ini(例如,在\ bin \ mysql \ mysql5.6.17 \ my.ini下)

并添加到[mysqld]

 general_log = 1 general_log_file="c:\\tmp\\mysql.log" 

我用这个解决scheme捕获PDO免除以用于debugging目的的问题是,它只捕获了PDO免除(duh),但没有捕获被注册为php错误的语法错误(我不知道为什么会这样,但是“为什么“与解决scheme无关)。 我所有的PDO调用都来自单个表模型类,我扩展了所有与所有表的交互…这些复杂的事情,当我试图debugging代码,因为错误会注册的PHP代码行我的执行调用叫,但没有告诉我,这个电话实际上是从哪里来的。 我用下面的代码来解决这个问题:

 /** * Executes a line of sql with PDO. * * @param string $sql * @param array $params */ class TableModel{ var $_db; //PDO connection var $_query; //PDO query function execute($sql, $params) { //we're saving this as a global, so it's available to the error handler global $_tm; //setting these so they're available to the error handler as well $this->_sql = $sql; $this->_paramArray = $params; $this->_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->_query = $this->_db->prepare($sql); try { //set a custom error handler for pdo to catch any php errors set_error_handler('pdoErrorHandler'); //save the table model object to make it available to the pdoErrorHandler $_tm = $this; $this->_query->execute($params); //now we restore the normal error handler restore_error_handler(); } catch (Exception $ex) { pdoErrorHandler(); return false; } } } 

所以,上面的代码捕获PDOexception和PHP语法错误,并以相同的方式对待它们。 我的error handling程序看起来像这样:

 function pdoErrorHandler() { //get all the stuff that we set in the table model global $_tm; $sql = $_tm->_sql; $params = $_tm->_params; $query = $tm->_query; $message = 'PDO error: ' . $sql . ' (' . implode(', ', $params) . ") \n"; //get trace info, so we can know where the sql call originated from ob_start(); debug_backtrace(); //I have a custom method here that parses debug backtrace, but this will work as well $trace = ob_get_clean(); //log the error in a civilized manner error_log($message); if(admin(){ //print error to screen based on your environment, logged in credentials, etc. print_r($message); } } 

如果任何人有更好的想法如何获得相关的信息到我的error handling程序比设置表模型作为一个全局variables,我会很高兴听到它,并编辑我的代码。

这段代码对我很好用:

 echo str_replace(array_keys($data), array_values($data), $query->queryString); 

不要忘记用你的名字来replace$ data和$ query

我使用这个类来debuggingPDO(与Log4PHP )

 <?php /** * Extends PDO and logs all queries that are executed and how long * they take, including queries issued via prepared statements */ class LoggedPDO extends PDO { public static $log = array(); public function __construct($dsn, $username = null, $password = null, $options = null) { parent::__construct($dsn, $username, $password, $options); } public function query($query) { $result = parent::query($query); return $result; } /** * @return LoggedPDOStatement */ public function prepare($statement, $options = NULL) { if (!$options) { $options = array(); } return new \LoggedPDOStatement(parent::prepare($statement, $options)); } } /** * PDOStatement decorator that logs when a PDOStatement is * executed, and the time it took to run * @see LoggedPDO */ class LoggedPDOStatement { /** * The PDOStatement we decorate */ private $statement; protected $_debugValues = null; public function __construct(PDOStatement $statement) { $this->statement = $statement; } public function getLogger() { return \Logger::getLogger('PDO sql'); } /** * When execute is called record the time it takes and * then log the query * @return PDO result set */ public function execute(array $params = array()) { $start = microtime(true); if (empty($params)) { $result = $this->statement->execute(); } else { foreach ($params as $key => $value) { $this->_debugValues[$key] = $value; } $result = $this->statement->execute($params); } $this->getLogger()->debug($this->_debugQuery()); $time = microtime(true) - $start; $ar = (int) $this->statement->rowCount(); $this->getLogger()->debug('Affected rows: ' . $ar . ' Query took: ' . round($time * 1000, 3) . ' ms'); return $result; } public function bindValue($parameter, $value, $data_type = false) { $this->_debugValues[$parameter] = $value; return $this->statement->bindValue($parameter, $value, $data_type); } public function _debugQuery($replaced = true) { $q = $this->statement->queryString; if (!$replaced) { return $q; } return preg_replace_callback('/:([0-9a-z_]+)/i', array($this, '_debugReplace'), $q); } protected function _debugReplace($m) { $v = $this->_debugValues[$m[0]]; if ($v === null) { return "NULL"; } if (!is_numeric($v)) { $v = str_replace("'", "''", $v); } return "'" . $v . "'"; } /** * Other than execute pass all other calls to the PDOStatement object * @param string $function_name * @param array $parameters arguments */ public function __call($function_name, $parameters) { return call_user_func_array(array($this->statement, $function_name), $parameters); } } 

我已经在这里创build了一个现代的Composer加载的项目/存储库:

PDOdebugging

在这里find项目的GitHub主页 ,看看这里的博客文章解释它 。 一行添加到你的composer.json,然后你可以像这样使用它:

 echo debugPDO($sql, $parameters); 

$ sql是原始的SQL语句,$ parameters是你的参数数组:键是占位符名(“:user_id”)或未命名参数(“?”)的数字,值是..好吧,值。

背后的逻辑:这个脚本将简单地修改参数并将其replace为提供的SQLstring。 超级简单,但对于99%的用例超级有效。 注意:这只是一个基本的模拟,不是一个真正的PDOdebugging(因为这是不可能的,因为PHP将原始SQL和参数发送到MySQL服务器分离)。

非常感谢来自StackOverflow线程的bigwebguyMike从PDO获取原始SQL查询string,以便基本上编写此脚本背后的整个主函数。 大起来!

这是我用返回一个带有“已解决”参数的SQL查询的函数。

 function paramToString($query, $parameters) { if(!empty($parameters)) { foreach($parameters as $key => $value) { preg_match('/(\?(?!=))/i', $query, $match, PREG_OFFSET_CAPTURE); $query = substr_replace($query, $value, $match[0][1], 1); } } return $query; $query = "SELECT email FROM table WHERE id = ? AND username = ?"; $values = [1, 'Super']; echo paramToString($query, $values); 

假设你像这样执行

 $values = array(1, 'SomeUsername'); $smth->execute($values); 

这个函数不会为查询添加引号,但为我做这个工作。