Как отлаживать запросы базы данных 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);
}
}