Как отлаживать запросы базы данных PDO?

прежде чем перейти к PDO, я создал SQL-запросы в PHP путем объединения строк. Если я получил синтаксическую ошибку базы данных, я мог бы просто повторить последнюю строку запроса SQL, попробовать ее сам в базе данных и настроить ее, пока не исправлю ошибку, а затем вернуть ее в код.

подготовленные операторы PDO быстрее, лучше и безопаснее, но меня беспокоит одна вещь: я никогда не вижу окончательный запрос, как он отправляется в базу данных. Когда я получаю ошибки о синтаксисе в моем журнале Apache или моем пользовательском журнале файл (я регистрирую ошибки внутри catch block), я не вижу запроса, который их вызвал.

есть ли способ захватить полный SQL-запрос, отправленный PDO в базу данных, и записать его в файл?

17 ответов


вы говорите :

Я никогда не видел окончательный запрос, как это отправлено в базу данных

ну, на самом деле, при использовании подготовленных выражений, нет такой вещи, как "окончательный запрос" :

  • во-первых, заявление отправляется в БД, и готовили там
    • база данных анализирует запрос и строит его внутреннее представление
  • и, когда вы свяжите переменные и выполните инструкцию, только переменные отправляются в базу данных
    • и база данных "вводит" значения во внутреннее представление оператора


Итак, чтобы ответить на ваш вопрос:

есть ли способ захватить полный SQL-запрос, отправленный PDO в базу данных и записать в файл?

нет : как нет "полный SQL запрос" нигде, нет никакого способа, чтобы захватить его.


Лучшее, что вы можете сделать для целей отладки,-это "перестроить" "реальный" SQL-запрос, введя значения в строку SQL инструкции.

что я обычно делаю в таких ситуациях, так это:

  • echo код SQL, соответствующий инструкции, с заполнителями
  • и использовать var_dump (или эквивалент) сразу после, чтобы отображение значений параметров
  • этого обычно достаточно, чтобы увидеть возможную ошибку, даже если у вас нет "реального" запроса, который вы можете выполнить.

это не здорово, когда дело доходит до отладки, но это цена подготовленных заявлений и преимуществ, которые они приносят.


поиск в журнале базы данных

хотя Паскаль Мартин правильно, что PDO не отправляет полный запрос в базу данных сразу,ryeguyпредложение использовать функцию ведения журнала БД фактически позволило мне увидеть полный запрос как собранный и выполненный базой данных.

вот как: (Эти инструкции для MySQL на машине Windows-ваш пробег может отличаться)

  • на my.ini, под добавьте , как log="C:\Program Files\MySQL\MySQL Server 5.1\data\mysql.log"
  • Перезапустить MySQL.
  • он начнет регистрировать каждый запрос в этом файле.

этот файл будет расти быстро, поэтому не забудьте удалить его и отключить ведение журнала, когда вы закончите тестирование.


конечно, вы можете отлаживать с помощью этого режима {{ PDO::ATTR_ERRMODE }} Просто добавьте новую строку перед запросом, тогда вы покажете строки отладки.

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

старый пост, но, возможно, кто-то найдет это полезным;

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

вероятно, то, что вы хотите сделать, это использовать debugDumParams() Он не создает подготовленный оператор для вас, но он покажет ваши параметры.


нет. Запросы PDO не готовятся на стороне клиента. PDO просто отправляет SQL-запрос и параметры на сервер базы данных. The база данных - это то, что делает подстановка (из ? ' s). У вас есть два варианта:

  • используйте функцию ведения журнала вашей БД (но даже тогда она обычно отображается как два отдельных оператора (т. е. "не окончательный"), по крайней мере, с Postgres)
  • выведите SQL-запрос и параметры и кусок его вместе себя!--7-->

вот функция, чтобы увидеть, что эффективный SQL будет, adpated из комментария "Mark" at php.net:

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

почти ничего не было сказано об ошибке отображается только проверить журналы ошибок, но есть довольно полезная функциональность:

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

(ссылка на источник)

ясно, что этот код может быть изменен для использования в качестве сообщения об исключении или любой другой вид обработки ошибок


например, у вас есть этот оператор 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;
  }

}

таким образом, вы можете использовать этот класс вместо 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); 
}

}

для входа MySQL в ПУВР, вам нужно будет отредактировать my.ini (например, под wamp\bin\mysql\mysql5.6.17\my.ini)

и добавить к [mysqld]:

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

вот функция, которую я сделал, чтобы вернуть 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);

эта функция не добавляет кавычки в запросы, но выполняет работу за меня.


как отлаживать PDO mysql database queries в Ubuntu

TL; DR войти все ваши запросы и хвост журнала mysql.

эти направления предназначены для моей установки Ubuntu 14.04. Выполните команду lsb_release -a чтобы получить свою версию. Ваша установка может отличаться.

включить вход в mysql

  1. перейдите в строку cmd сервера dev
  2. каталог cd /etc/mysql. Вы должны увидеть файл под названием my.cnf. Это файл, который мы собираемся менять.
  3. убедитесь, что вы находитесь в нужном месте, введя cat my.cnf | grep general_log. Это фильтрует my.cnf файл для вас. Вы должны увидеть две строки: #general_log_file = /var/log/mysql/mysql.log &&#general_log = 1.
  4. раскомментируйте эти две строки и сохраните их в выбранном редакторе.
  5. перезапустить mysql:sudo service mysql restart.
  6. возможно, Вам также потребуется перезагрузить веб-сервер. (Я не могу вспомнить последовательность, которую я использовал). Для моей установки это nginx: 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.

Примечания

  1. осторожно: этот файл журнала может стать огромным. Я запускаю это только на своем dev-сервере.
  2. файл журнала становится слишком большим? Усечь его. Это означает, что файл остается, но содержимое удаляется. truncate --size 0 mysql.log.
  3. круто, что файл журнала перечисляет соединения mysql. Я знаю, что один из них-из моего унаследованного кода mysqli, из которого я перехожу. Третий-от моего нового PDO-соединения. Однако, не уверен, где второй идет от. Если вы знаете быстрый способ найти его, дайте мне знать.

кредитные и спасибо

огромный крик к ответ Натана Лонга выше для inspo, чтобы выяснить это на Ubuntu. Также к dikirill за его комментарий к сообщению Натана, который привел меня к этому решению.

люблю тебя stackoverflow!


проблема, которую я имел с решением поймать исключения PDO для целей отладки, заключается в том, что он поймал только исключения PDO (duh), но не поймал синтаксические ошибки, которые были зарегистрированы как ошибки php (я не уверен, почему это так, но "почему" не имеет отношения к решению). Все мои вызовы PDO поступают из одного класса модели таблицы, который я расширил для всех моих взаимодействий со всеми таблицами... это усложняло вещи, когда я пытался отладить код, потому что ошибка регистрировала бы строку кода php где был вызван мой вызов execute, но не сказал мне, откуда на самом деле был сделан вызов. Я использовал следующий код для решения этой проблемы:

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

Если у кого-то есть лучшие идеи о том, как получить соответствующую информацию для моего обработчика ошибок, чем установка модели таблицы в качестве глобальной переменной, я был бы рад слушайте и редактируйте мой код.


этот код отлично работает для меня:

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

Не забудьте заменить $data и $query своими именами


Я использую этот класс для отладки 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);
    }
}

Я создал современный композитор-загруженный проект / репозиторий именно для этого здесь:

pdo-debug

найти проект GitHub дома здесь, увидели сообщение в блоге, объясняя это здесь. Одна строка для добавления в ваш композитор.json, а затем вы можете использовать его следующим образом:

echo debugPDO($sql, $parameters);

$sql-это оператор raw SQL, $parameters-это массив ваших параметров: ключ-это имя заполнителя (": user_id") или номер неназванного параметр ("?") значение .. ну, ценность.

логика позади: этот скрипт просто градуирует параметры и заменяет их в предоставленную строку SQL. Супер-простой, но супер-эффективный для 99% ваших случаев использования. Примечание: это просто базовая эмуляция, а не реальная отладка PDO (поскольку это невозможно, поскольку PHP отправляет raw SQL и параметры на сервер MySQL отдельно).

большое спасибо to bigwebguy и Майк из потока StackOverflow получение строки запроса raw SQL из PDO для написания в основном всей основной функции за этим скриптом. Большой вверх!