Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/variables/2.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数据库查询?_Php_Sql_Pdo - Fatal编程技术网

Php 如何调试PDO数据库查询?

Php 如何调试PDO数据库查询?,php,sql,pdo,Php,Sql,Pdo,在转到PDO之前,我通过连接字符串在PHP中创建了SQL查询。如果出现数据库语法错误,我可以回显最终的SQL查询字符串,自己在数据库中尝试,并调整它,直到修复错误,然后将其放回代码中 准备好的PDO语句更快、更好、更安全,但有一件事困扰着我:在将最终查询发送到数据库时,我从未看到它。当我在Apache日志或自定义日志文件中发现语法错误时,我会在catch块中记录错误,但我看不到导致这些错误的查询 是否有方法捕获PDO发送到数据库的完整SQL查询并将其记录到文件中?否。PDO查询不在客户端准备。P

在转到PDO之前,我通过连接字符串在PHP中创建了SQL查询。如果出现数据库语法错误,我可以回显最终的SQL查询字符串,自己在数据库中尝试,并调整它,直到修复错误,然后将其放回代码中

准备好的PDO语句更快、更好、更安全,但有一件事困扰着我:在将最终查询发送到数据库时,我从未看到它。当我在Apache日志或自定义日志文件中发现语法错误时,我会在catch块中记录错误,但我看不到导致这些错误的查询


是否有方法捕获PDO发送到数据库的完整SQL查询并将其记录到文件中?

否。PDO查询不在客户端准备。PDO只是将SQL查询和参数发送到数据库服务器。数据库是什么做的替换?'s。您有两个选择:

使用数据库的日志功能,但即使这样,它通常也会显示为两个单独的语句,即至少在Postgres中不是final 输出SQL查询和 参数并将其拼接在一起 你自己 你这样说:

我从来没有看到最终的查询,因为它是 发送到数据库

实际上,当使用准备好的语句时,没有最终查询:

首先,一条语句被发送到DB,并在那里准备好 数据库解析查询,并构建查询的内部表示 并且,当绑定变量并执行语句时,只有变量被发送到数据库 数据库将这些值注入到语句的内部表示中 所以,要回答你的问题:

有没有办法捕捉到完整的 PDO向数据库发送的SQL查询 并将其记录到一个文件中

否:由于任何地方都没有完整的SQL查询,因此无法捕获它

出于调试目的,您可以做的最好的事情是通过将值注入语句的SQL字符串来重新构造一个真正的SQL查询

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

使用占位符回显与语句对应的SQL代码 并在后面使用var_dump或等效值来显示参数的值 这通常足以看到可能的错误,即使您没有任何可以执行的实际查询。 当涉及到调试时,这不是一件好事,但这是准备好的语句的代价和它们带来的好处。

查看数据库日志 虽然Pascal MARTIN认为PDO不会一次将完整的查询发送到数据库是正确的,但ryeguy建议使用DB的日志功能实际上允许我看到数据库组装和执行的完整查询

以下是方法: 这些说明适用于Windows计算机上的MySQL-您的里程可能会有所不同

在my.ini的[mysqld]部分下,添加一个log命令,如log=C:\Program Files\MySQL\MySQL Server 5.1\data\MySQL.log 重新启动MySQL。 它将开始记录该文件中的每个查询。
该文件将快速增长,因此请确保在完成测试时将其删除并关闭日志记录。

您可能要做的是在语句句柄上使用它。您可以在将值绑定到准备好的查询后随时运行该语句,而无需执行该语句


它不会为您构建准备好的语句,但会显示您的参数。

一篇旧文章,但可能有人会发现这很有用

function pdo_sql_debug($sql,$placeholders){
    foreach($placeholders as $k => $v){
        $sql = preg_replace('/:'.$k.'/',"'".$v."'",$sql);
    }
    return $sql;
}
$values = array(1, 'SomeUsername');
$smth->execute($values);

确实可以使用此模式进行调试{{PDO::ATTR_ERRMODE}} 只需在查询之前添加新行,即可显示调试行

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

例如,您有以下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;

搜索互联网,我发现这是一个可以接受的解决方案。使用不同的类代替PDO,PDO函数通过神奇的函数调用进行调用。我不确定这会造成严重的性能问题。但在PDO中添加一个合理的日志功能之前,它是可以使用的

因此,您可以为您的PDO连接编写一个包装器,它可以记录日志,并在出现错误时抛出异常

下面是一个简单的例子:

class LoggedPDOSTatement extends PDOStatement    {

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

}
因此,您可以使用该类而不是PDO语句:

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

}

除了检查错误日志外,几乎没有提到错误显示, 但有一个相当有用的功能:

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

很明显,可以修改此代码以用作异常消息
或者任何其他类型的错误处理

这里有一个函数,用于查看有效的SQL是什么,根据Mark在以下位置的注释:


我在这里创建了一个现代作曲家加载的项目/存储库:

pdo调试 找到项目的,请参阅。在composer.json中添加一行,然后您可以这样使用它:

/**
 * 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;
        }            
    }
}
echo str_replace(array_keys($data), array_values($data), $query->queryString);
general_log_file        = /var/log/mysql/mysql.log
general_log             = 1
$sql是原始sql语句,$parameters是参数数组:键是占位符名称:user_id还是未命名参数的编号? ,值为。。嗯,价值

背后的逻辑:此脚本将简单地对参数进行分级,并将其替换为提供的SQL字符串。超级简单,但对99%的使用案例超级有效。注意:这只是一个基本的模拟,不是真正的PDO调试,因为这是不可能的,因为PHP将原始SQL和参数发送到MySQL服务器


非常感谢StackOverflow线程中的bigwebguy和Mike编写了这个脚本背后的整个主要功能。大块头

为了调试而捕获PDO豁免的解决方案存在的问题是,它只捕获PDO豁免duh,但没有捕获注册为php错误的语法错误。我不确定这是为什么,但为什么与解决方案无关。我所有的PDO调用都来自一个表模型类,我为所有与表的交互扩展了这个类。。。当我试图调试代码时,这会使事情变得复杂,因为错误会注册调用execute调用的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);
    }
}
因此,上面的代码捕获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;
        }            
    }
}
echo str_replace(array_keys($data), array_values($data), $query->queryString);
general_log_file        = /var/log/mysql/mysql.log
general_log             = 1

如果有人对如何向我的错误处理程序获取相关信息有比将表模型设置为全局变量更好的想法,我很乐意听到并编辑我的代码。

这段代码对我来说非常有用:

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

别忘了用您的名字替换$data和$query

我使用这个类调试PDO

要在WAMP中登录MySQL,您需要编辑my.ini,例如在WAMP\bin\MySQL\mysql5.6.17\my.ini下

并添加到[mysqld]:


下面是我用来返回带有已解析参数的SQL查询的函数

$values = array(1, 'SomeUsername');
$smth->execute($values);
假设你这样执行

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 
此函数不在查询中添加引号,但为我完成了这项工作。

如何在Ubuntu中调试PDO mysql数据库查询 TL;DR记录所有查询并跟踪mysql日志

这些说明适用于我的Ubuntu 14.04安装。发出命令lsb_release-a以获取您的版本。您的安装可能不同

在mysql中打开日志记录 转到dev服务器cmd行 更改目录cd/etc/mysql。您应该看到一个名为my.cnf的文件。这就是我们要更改的文件。 通过键入cat my.cnf | grep general_log验证您是否在正确的位置。这将为您过滤my.cnf文件。您应该看到两个条目:general\u log\u file=/var/log/mysql/mysql.log&&general\u log=1。 取消对这两行的注释并通过您选择的编辑器保存。 重启mysql:sudo服务mysql重启。 您可能还需要重新启动Web服务器。我记不起我用过的顺序了。对于我的安装,这是nginx:sudo服务nginx restart。 干得好!你都准备好了。现在你所要做的就是跟踪日志文件,这样你就可以看到你的应用程序实时进行的PDO查询

跟踪日志以查看您的查询 输入cmd tail-f/var/log/mysql/mysql.log

您的输出将如下所示:

/**
 * 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;
        }            
    }
}
echo str_replace(array_keys($data), array_values($data), $query->queryString);
general_log_file        = /var/log/mysql/mysql.log
general_log             = 1
只要您继续跟踪日志,应用程序发出的任何新查询都会自动弹出到视图中。要退出尾部,请按cmd/ctrl c

笔记 小心:这个日志文件可能会变得很大。我只在我的开发服务器上运行这个。 日志文件变得太大?截断它。这意味着文件将保留,但内容将被删除。truncate-大小为0.mysql.log。 很酷,日志文件列出了mysql连接。我知道其中一个来自我正在转换的遗留mysqli代码。第三个来自我的新PDO连接。然而,不确定第二个是从哪里来的。如果你知道一个快速找到它的方法,让我知道。 信用与感谢 巨大的呐喊声让inspo在Ubuntu上解决这个问题。同时也感谢他对Nathan帖子的评论,这篇文章将我引向了这个解决方案


我爱你

在Debian NGINX环境中,我执行了以下操作

Goto/etc/mysql/mysql.conf.d编辑mysqld.cnf如果您发现log error=/var/log/mysql/error.log,请在下面添加以下两行

要查看日志,请转到/var/log/mysql和tail-f mysql.log


如果您在生产环境中进行调试,请记住在完成调试后将这些行注释掉。请删除mysql.log,因为此日志文件将快速增长并可能非常庞大。

非常好的解释-谢谢。很明显,我对这是如何工作的只有模糊的想法。我假设在准备语句时,生成的对象包含一个散列或数字ID,可以将其与要插入的参数一起发送回数据库;;;我不知道这是如何实现的细节,但我想它是这样的-结果是完全一样的,无论如何;;;这是预处理语句的优点之一:如果您必须多次执行同一查询,它将只发送到DB并准备一次:对于每次执行,只发送数据。更新:Aaron Pa

Terterson在Railsconf 2011上提到,他在Rails中添加了更多准备好的语句,但PostgreSQL的好处要比MySQL大得多。他说这是因为MySQL在您执行准备好的查询之前不会真正创建查询计划。我从未想过要检查数据库的日志。我在MySQL目录中四处搜索,没有看到任何日志文件,但也许日志是我必须在某个地方打开的选项。是的,你必须打开它。我不知道具体细节,但默认情况下它不会记录每个查询。它记录在一个文件中:/var/log/mysql/*。PDO绑定的参数不会导致语法错误,因此您只需要准备好SQL查询。请参阅中的代码,而不是接受的答案中的代码。并不是说发布了一些更新。通过Composer简单的一句话:Xeoncross的回答帮助了我。下面是一篇文章,解释如何打开此功能。在许多服务器安装中,它都是默认设置。尝试使用var_dump$pdo_instance->debugDumpParamsJust a note-我必须避开my.ini中的斜杠。因此,我的条目看起来类似于log=C:\\temp\\MySQL\\MySQL.log。这可能取决于PDO::ATTR\u EMULATE\u的设置。更多信息请参见此答案:我讨厌PDO,因为它。@webbiedave-哦,哇!您的链接答案意味着,我的答案仅在PDO不能最佳工作时有效,而是发送整个查询以向后兼容旧版本的MySQL或旧驱动程序。有趣。在MySQL 5.5+中,您需要的是常规日志而不是日志。请看,唯一的问题是它输出调试,而不是在内部存储调试,而不“回显”调试。我不能用这种方式记录。你可以使用输出缓冲ob\u start。。。存储输出并记录它。修正在7.1你可以看到的价值观:有点晚,但它的php这是错误的方式。PDO足够聪明,可以使这段代码毫无用处。只要告诉它对错误抛出异常。PHP将完成剩下的工作,比这个有限的函数要好得多。另外,请学习不要将所有错误直接打印到浏览器中。还有更好的方法。这是官方文档,当然没有人会在生产中打印错误,这也是官方网站php.net的一个示例,请参见代码示例下面的链接。当然,更好的方法是在PDO实例化中使用额外的参数$db->setAttributePDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION,但不幸的是,对于同样可以处理数值参数的类似函数,您无法访问该代码,感谢php.net上的一位评论者。为什么马克在str_replace中的$k之前使用冒号:$k。。。。?关联索引在$params数组中已经有它。这是一个好问题…这可能解释了它:。就我个人而言,我使用这个函数来调试条令查询,我认为条令使用的是编号参数而不是命名参数,所以我没有注意到这个问题。我更新了该函数,以便它现在可以使用或不使用前导冒号。注意,此解决方案将:name_long替换为:name。至少如果:name在:name\u long之前。MySQL准备的语句可以正确处理这一点,所以不要让它迷惑你。为我工作。您在第二个代码示例中有错误:;应该是;只有一个圆括号。但在使用准备好的语句时,您不会调用->查询?谢谢,这对我帮助很大!:非常感谢!我添加了以下内容:if是_字符串$value{$value='.$value.;}