在迁移到PDO之前,我通过连接字符串在PHP中创建了SQL查询。如果我得到数据库语法错误,我可以回显最终的SQL查询字符串,在数据库上自己尝试,并调整它直到我修复错误,然后将其放回代码中。
准备好的PDO语句更快,更好,更安全,但有一件事困扰我:我从未看到最终查询,因为它被发送到数据库。当我在Apache日志或我的自定义日志文件中出现语法错误时(我在catch
块中记录错误),我看不到导致它们的查询。
有没有办法捕获PDO发送到数据库的完整SQL查询并将其记录到文件中?
答案 0 :(得分:95)
你这样说:
我从来没有看到最终的查询 发送到数据库
嗯,实际上,在使用预准备语句时,没有“最终查询”:
所以,回答你的问题:
有没有办法捕获完整的 PDO发送到数据库的SQL查询 并将其记录到文件中?
否:由于任何地方都没有“完整的SQL查询”,因此无法捕获它。
出于调试目的,您可以做的最好的事情是通过将值注入语句的SQL字符串中来“重构”一个“真正的”SQL查询。
在这种情况下,我通常做的是:
var_dump
(或等效的)来显示参数值这在调试方面并不是很好 - 但这是准备好的陈述的价格及其带来的好处。
答案 1 :(得分:83)
虽然 Pascal MARTIN 是正确的,PDO不会立即将完整的查询发送到数据库, ryeguy 建议使用DB的日志记录功能实际允许我可以看到由数据库组装和执行的完整查询。
以下是: (这些说明适用于Windows机器上的MySQL - 您的里程可能会有所不同)
my.ini
的{{1}}部分下,添加[mysqld]
命令,例如log
该文件将快速增长,因此请确保在完成测试后将其删除并关闭日志记录。
答案 2 :(得分:14)
您可能想要做的是使用debugDumParams() 它不会为您构建准备好的语句,但会显示您的参数。
答案 3 :(得分:14)
当然可以使用此模式进行调试{{ PDO::ATTR_ERRMODE }}
只需在查询之前添加新行,然后您将显示调试行。
$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$db->query('SELECT *******');
答案 4 :(得分:12)
旧帖子,但也许有人会觉得这很有用;
function pdo_sql_debug($sql,$placeholders){
foreach($placeholders as $k => $v){
$sql = preg_replace('/:'.$k.'/',"'".$v."'",$sql);
}
return $sql;
}
答案 5 :(得分:9)
这是一个函数,可以看看有效的SQL是什么,改编自“{3}}的”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('Y-m-d 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;
}
答案 6 :(得分:8)
没有。 PDO查询不是在客户端准备的。 PDO只是将SQL查询和参数发送到数据库服务器。 数据库是替换(?
的)。您有两种选择:
答案 7 :(得分:5)
除了检查错误日志之外,几乎没有关于错误显示的内容, 但这是一个非常有用的功能:
<?php
/* Provoke an error -- bogus SQL syntax */
$stmt = $dbh->prepare('bogus sql');
if (!$stmt) {
echo "\PDO::errorInfo():\n";
print_r($dbh->errorInfo());
}
?>
很明显,可以修改此代码以用作异常消息 或任何其他类型的错误处理
答案 8 :(得分:3)
例如,你有这个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;
答案 9 :(得分:2)
搜索互联网我发现这是一个可以接受的解决方案。使用不同的类而不是PDO,并通过魔术函数调用调用PDO函数。我不确定这会造成严重的性能问题。但它可以在PDO中添加合理的日志记录功能之前使用。
因此,根据此thread,您可以为PDO连接编写一个包装器,它可以在出现错误时记录并引发异常。
这是一个简单的例子:
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);
}
}
答案 10 :(得分:2)
要在 WAMP 中记录MySQL,您需要编辑my.ini(例如在wamp \ bin \ mysql \ mysql5.6.17 \ my.ini下)
并添加到[mysqld]
:
general_log = 1
general_log_file="c:\\tmp\\mysql.log"
答案 11 :(得分:1)
这是我用&#34;已解决&#34;返回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);
此功能不会为查询添加引号,但可以为我完成工作。
答案 12 :(得分:0)
我在这里创建了一个现代的Composer加载的项目/存储库:
找到项目的GitHub home here,查看blog post explaining it here。在composer.json中添加一行,然后就可以像这样使用它:
echo debugPDO($sql, $parameters);
$ sql是原始SQL语句,$ parameters是您的参数数组:键是占位符名称(“:user_id”)或未命名参数的编号(“?”),值为..好吧,价值。
背后的逻辑:这个脚本只是简化参数并将它们替换为提供的SQL字符串。 99%的用例非常简单但超级有效。注意:这只是一个基本的仿真,而不是真正的PDO调试(因为这是不可能的,因为PHP将原始SQL和参数分离到MySQL服务器)。
非常感谢来自StackOverflow线程Getting raw SQL query string from PDO的 bigwebguy 和 Mike ,用于编写此脚本背后的整个主要功能。大了!
答案 13 :(得分:0)
我在解决PDO豁免的解决方案中遇到的问题是它只捕获PDO豁免(duh),但没有捕获被注册为php错误的语法错误(我不知道为什么这是,但“为什么”与解决方案无关)。我所有的PDO调用来自单个表模型类,我扩展了所有与所有表的交互...当我尝试调试代码时这个复杂的事情,因为错误将注册我执行调用的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;
}
}
}
因此,上面的代码捕获了两个PDO异常和php语法错误,并以同样的方式处理它们。我的错误处理程序看起来像这样:
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);
}
}
如果有人对如何将相关信息提供给我的错误处理程序比将表模型设置为全局变量有任何更好的想法,我会很高兴听到它并编辑我的代码。
答案 14 :(得分:0)
这段代码非常适合我:
echo str_replace(array_keys($data), array_values($data), $query->queryString);
不要忘记用你的名字替换$ data和$ query
答案 15 :(得分:0)
我使用此类来调试PDO(使用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);
}
}
答案 16 :(得分:0)
TL; DR记录所有查询并尾随mysql日志。
这些说明适用于我安装的Ubuntu 14.04。发出命令lsb_release -a
以获取您的版本。您的安装可能会有所不同。
cd /etc/mysql
。您应该看到一个名为my.cnf
的文件。那是我们要改变的文件。cat my.cnf | grep general_log
确认您在正确的位置。这会为您过滤my.cnf
文件。您应该看到两个条目:#general_log_file = /var/log/mysql/mysql.log
&amp;&amp; #general_log = 1
。sudo service mysql restart
。 sudo service nginx restart
。干得好!你们都准备好了。现在您所要做的就是拖尾日志文件,以便您可以实时查看应用程序的PDO查询。
输入此cmd tail -f /var/log/mysql/mysql.log
。
您的输出将如下所示:
73 Connect xyz@localhost on your_db
73 Query SET NAMES utf8mb4
74 Connect xyz@localhost on your_db
75 Connect xyz@localhost on your_db
74 Quit
75 Prepare SELECT email FROM customer WHERE email=? LIMIT ?
75 Execute SELECT email FROM customer WHERE email='a@b.co' LIMIT 5
75 Close stmt
75 Quit
73 Quit
只要您继续拖尾日志,您的应用所做的任何新查询都会自动进入视图。要退出尾部,请点击cmd/ctrl c
。
truncate --size 0 mysql.log
。向Nathan Long’s answer above发出巨大的呼喊,要求在Ubuntu上解决这个问题。也是dikirill对Nathan的帖子的评论,它引导我找到这个解决方案。
爱你stackoverflow!
答案 17 :(得分:0)
在Debian NGINX环境中,我执行了以下操作。
如果发现/etc/mysql/mysql.conf.d
,请转到mysqld.cnf
编辑log-error = /var/log/mysql/error.log
,在其后添加以下两行。
general_log_file = /var/log/mysql/mysql.log
general_log = 1
要查看日志转到/var/log/mysql
和tail -f mysql.log
如果要在生产环境中进行调试,请记住将这些行注释掉,因为此日志文件将快速增长并且可能很大,因此请删除mysql.log
。