Как отлаживать запросы к базе данных PDO? - PullRequest
130 голосов
/ 09 марта 2010

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

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

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

Ответы [ 18 ]

2 голосов
/ 19 марта 2013

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

<code>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 '
'; } function __call ($ method, $ params) { return call_user_func_array (массив ($ this-> stmt, $ method), $ params); } }
1 голос
/ 22 июля 2017

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

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

0 голосов
/ 22 апреля 2019

В среде Debian NGINX я сделал следующее.

Перейти /etc/mysql/mysql.conf.d изменить mysqld.cnf, если вы найдете log-error = /var/log/mysql/error.log, добавить следующие 2 строки ниже.

general_log_file        = /var/log/mysql/mysql.log
general_log             = 1

Для просмотра логов перейдите на /var/log/mysql и tail -f mysql.log

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

0 голосов
/ 21 ноября 2017

Как отлаживать запросы к базе данных MySQL PDO в 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. Осторожно: этот файл журнала может стать огромным. Я запускаю это только на моем сервере разработки.
  2. Файл журнала становится слишком большим? Обрежь это. Это означает, что файл остается, но содержимое удаляется. truncate --size 0 mysql.log.
  3. Здорово, что в файле журнала перечислены подключения mysql. Я знаю, что один из них взят из моего старого кода MySQL, из которого я перехожу. Третий из моего нового соединения PDO. Однако, не уверен, откуда приходит второй. Если вы знаете быстрый способ найти его, дайте мне знать.

Кредит и спасибо

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

Люблю тебя переполнение стека!

0 голосов
/ 20 апреля 2015

я использую этот класс для отладки 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);
    }
}
0 голосов
/ 23 октября 2014

этот код прекрасно работает для меня:

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

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

0 голосов
/ 13 августа 2014

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

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

0 голосов
/ 11 июля 2014

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

PDO-отладка

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

echo debugPDO($sql, $parameters);

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...