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


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

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

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

17 127

17 ответов:

вы говорите :

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

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

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


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

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

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


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

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

  • Эхо код 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-запрос и параметры на сервер базы данных. Элемент база данных это то, что делает подстановка (из ? ' s). У вас есть два варианта:

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

вот функция, чтобы увидеть, что эффективные SQL будет, по мере из комментария на "Марк" в 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 не будет добавлена разумная функция ведения журнала.

Так как в этот 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 decorator:

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 в WAMP, вам нужно будет отредактировать мой.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 в 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-это необработанный оператор SQL, $parameters-это массив ваших параметров: ключ-это имя заполнителя (": user_id") или номер неназванного параметр ("?") значение .. ну и ценность.

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

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