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

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

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

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

Ответ 1

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

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

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

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

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

Ответ 2

Вы говорите это:

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

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

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


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

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

Нет: поскольку "полного SQL-запроса" нет, нет возможности его захватить.


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

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

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

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

Здесь функция, чтобы увидеть, что будет эффективным 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;
}

Ответ 8

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

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

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

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

Ответ 9

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

Ответ 10

Поиск в Интернете Я нашел это приемлемым решением. Вместо 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); 
}

}

Ответ 11

Чтобы зарегистрировать MySQL в WAMP, вам нужно будет изменить my.ini(например, в разделе wamp\bin\mysql\mysql5.6.17\my.ini)

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

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

Ответ 12

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

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

Ответ 13

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

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

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

Включить ведение журнала в mysql

  • Перейдите на страницу cmd-сервера dev-сервера.
  • Изменить каталоги cd /etc/mysql. Вы должны увидеть файл с именем my.cnf. Это файл изменится.
  • Убедитесь, что вы в нужном месте, набрав cat my.cnf | grep general_log. Это фильтрует файл my.cnf для вас. Вы должны увидеть две записи: #general_log_file = /var/log/mysql/mysql.log && #general_log = 1.
  • Раскомментируйте эти две строки и сохраните их с помощью своего редактора.
  • Перезагрузите mysql: sudo service mysql restart.
  • Возможно, вам также потребуется перезагрузить веб-сервер. (Я не могу вспомнить последовательность, которую я использовал). Для моей установки, thats nginx: sudo service nginx restart.

Хорошая работа! Ты все настроен. Теперь все, что вам нужно сделать, это вывести файл журнала, чтобы вы могли видеть запросы PDO, которые делает ваше приложение в режиме реального времени.

Хвост журнала для просмотра ваших запросов

Введите этот cmd tail -f /var/log/mysql/mysql.log.

Ваш результат будет выглядеть примерно так:

73 Connect  [email protected] on your_db
73 Query    SET NAMES utf8mb4
74 Connect  [email protected] on your_db
75 Connect  [email protected] on your_db
74 Quit 
75 Prepare  SELECT email FROM customer WHERE email=? LIMIT ?
75 Execute  SELECT email FROM customer WHERE email='[email protected]' LIMIT 5
75 Close stmt   
75 Quit 
73 Quit 

Любые новые запросы, созданные вашим приложением, автоматически появятся в представлении, если вы продолжите обработку журнала. Чтобы выйти из хвоста, нажмите cmd/ctrl c.

Примечания

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

Кредит и благодарности

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

Полюбите вас stackoverflow!

Ответ 14

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

PDO-отладки

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

echo debugPDO($sql, $parameters);

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

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

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

Ответ 15

Проблема, с которой я столкнулась с решением уловить исключения 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 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;
        }            
    }
}

Таким образом, приведенный выше код улавливает ошибки BOTH 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);
    }
}

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

Ответ 16

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

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

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

Ответ 17

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