PDO 데이터베이스 쿼리를 디버깅하는 방법?
PDO로 이동하기 전에 문자열을 연결하여 PHP에서 SQL 쿼리를 작성했습니다. 데이터베이스 구문 오류가 발생하면 최종 SQL 쿼리 문자열을 에코하고 데이터베이스에서 직접 시도하고 오류를 수정할 때까지 조정 한 다음 코드에 다시 넣을 수 있습니다.
준비된 PDO 문은 더 빠르고 안전하며 안전하지만 한 가지가 나를 귀찮게합니다. 데이터베이스로 전송되는 최종 쿼리는 결코 보지 못합니다. Apache 로그 또는 사용자 정의 로그 파일의 구문에 대한 오류가 발생하면 ( catch
블록 내에 오류를 기록함 ) 오류 를 일으킨 쿼리를 볼 수 없습니다.
PDO가 데이터베이스로 보낸 전체 SQL 쿼리를 캡처하여 파일에 기록하는 방법이 있습니까?
당신은 이것을 말합니다 :
데이터베이스로 전송되는 최종 쿼리를 보지 못했습니다.
실제로, 준비된 명령문을 사용할 때 " final query " 와 같은 것은 없습니다 .
- 먼저, 성명서를 DB로 보내어 준비한다
- 데이터베이스는 쿼리를 구문 분석하고 쿼리의 내부 표현을 작성합니다.
- 그리고 변수를 바인드하고 명령문을 실행할 때 변수 만 데이터베이스로 전송됩니다.
- 데이터베이스는 값을 명령문의 내부 표현에 "주입"합니다.
따라서 귀하의 질문에 대답하려면 :
PDO가 데이터베이스로 보낸 전체 SQL 쿼리를 캡처하여 파일에 기록하는 방법이 있습니까?
아니요 : " 전체 SQL 쿼리 "가 어디에도 없으므로이를 캡처 할 방법이 없습니다.
디버깅 목적으로 수행 할 수있는 최선의 방법은 값을 명령문의 SQL 문자열에 삽입하여 "실제"SQL 쿼리를 "재구성"하는 것입니다.
이런 종류의 상황에서 내가 일반적으로하는 일은 다음과 같습니다.
- 자리 표시 자와 함께 명령문에 해당하는 SQL 코드를 에코합니다.
- 매개 변수의 값을 표시하기 위해 직후에
var_dump
(또는 이에 상응하는) 사용 - 실행할 수있는 "실제"쿼리가없는 경우에도 일반적으로 가능한 오류를 볼 수 있습니다.
이것은 디버깅과 관련하여 훌륭하지는 않지만 준비된 명령문의 가격과 장점입니다.
데이터베이스 로그를 보면
하지만 파스칼 마틴은 PDO 번에 모든 데이터베이스의 전체 쿼리를 전송하지 않습니다 정확한지, ryeguy DB를의 로깅 기능을 사용하기의 제안은 실제로 조립 및 데이터베이스에 의해 실행 나를는 전체 쿼리를 볼 수있었습니다.
방법은 다음과 같습니다 (이 지침은 Windows 컴퓨터의 MySQL에 대한 것입니다-마일리지는 다를 수 있음)
- 에서는
my.ini
의 아래[mysqld]
절의 경우, 추가log
등의 명령을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 *******');
아마 당신이하고 싶은 것은 debugDumParams () 사용 하는 것입니다. 당신을 위해 준비된 문장을 만들지는 않지만 매개 변수를 보여줍니다.
오래된 게시물이지만 아마도 누군가 유용 할 것입니다.
function pdo_sql_debug($sql,$placeholders){
foreach($placeholders as $k => $v){
$sql = preg_replace('/:'.$k.'/',"'".$v."'",$sql);
}
return $sql;
}
다음은 php.net의 "Mark"에 의해 주석에서 수정 된 효과적인 SQL이 무엇인지 확인하는 함수입니다 .
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;
}
아니요. PDO 쿼리는 클라이언트 쪽에서 준비되지 않았습니다. PDO는 단순히 SQL 쿼리 및 매개 변수를 데이터베이스 서버로 보냅니다. 데이터베이스 (의 대체를 무엇이다 ?
'들). 두 가지 옵션이 있습니다.
- DB의 로깅 기능을 사용하십시오 (그러나 그럼에도 Postgres에서는 적어도 두 개의 별도 명령문으로 표시됩니다 (즉, "최종 아님")).
- SQL 쿼리와 매개 변수를 출력하고 직접 조각하십시오.
check error logs 이외의 에러 표시에 대해서는 거의 언급되지 않았지만 다소 유용한 기능이 있습니다.
<?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 연결에 대한 래퍼를 작성하여 오류가 발생하면 예외를 기록하고 throw 할 수 있습니다.
다음은 간단한 예입니다.
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을 WAMP에 기록하려면 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 면제를 포착하는 솔루션에서 내가 가진 문제는 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'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);
}
}
정확히 여기에 현대 Composer로드 프로젝트 / 저장소를 만들었습니다.
pdo-debug
프로젝트의 GitHub 홈을 여기 에서 찾으십시오 ( 여기 에서 설명 하는 블로그 게시물 참조) . composer.json에 추가 할 한 줄을 다음과 같이 사용할 수 있습니다.
echo debugPDO($sql, $parameters);
$ sql은 원시 SQL 문이고 $ parameters는 매개 변수의 배열입니다. 키는 자리 표시 자 이름 ( ": user_id") 또는 명명되지 않은 매개 변수의 번호 ( "?")이며 값은 ..입니다. 값.
The logic behind: This script will simply grad the parameters and replace them into the SQL string provided. Super-simple, but super-effective for 99% of your use-cases. Note: This is just a basic emulation, not a real PDO debugging (as this is not possible as PHP sends raw SQL and parameters to the MySQL server seperated).
A big thanks to bigwebguy and Mike from the StackOverflow thread Getting raw SQL query string from PDO for writing basically the entire main function behind this script. Big up!
How to debug PDO mysql database queries in Ubuntu
TL;DR Log all your queries and tail the mysql log.
These directions are for my install of Ubuntu 14.04. Issue command lsb_release -a
to get your version. Your install might be different.
Turn on logging in mysql
- Go to your dev server cmd line
- Change directories
cd /etc/mysql
. You should see a file calledmy.cnf
. That’s the file we’re gonna change. - Verify you’re in the right place by typing
cat my.cnf | grep general_log
. This filters themy.cnf
file for you. You should see two entries:#general_log_file = /var/log/mysql/mysql.log
&&#general_log = 1
. - Uncomment those two lines and save via your editor of choice.
- Restart mysql:
sudo service mysql restart
. - You might need to restart your webserver too. (I can’t recall the sequence I used). For my install, that’s nginx:
sudo service nginx restart
.
Nice work! You’re all set. Now all you have to do is tail the log file so you can see the PDO queries your app makes in real time.
Tail the log to see your queries
Enter this cmd tail -f /var/log/mysql/mysql.log
.
Your output will look something like this:
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
Any new queries your app makes will automatically pop into view, as long as you continue tailing the log. To exit the tail, hit cmd/ctrl c
.
Notes
- Careful: this log file can get huge. I’m only running this on my dev server.
- Log file getting too big? Truncate it. That means the file stays, but the contents are deleted.
truncate --size 0 mysql.log
. - Cool that the log file lists the mysql connections. I know one of those is from my legacy mysqli code from which I'm transitioning. The third is from my new PDO connection. However, not sure where the second is coming from. If you know a quick way to find it, let me know.
Credit & thanks
Huge shout out to Nathan Long’s answer above for the inspo to figure this out on Ubuntu. Also to dikirill for his comment on Nathan’s post which lead me to this solution.
Love you stackoverflow!
In Debian NGINX environment i did the following.
Goto /etc/mysql/mysql.conf.d
edit mysqld.cnf
if you find log-error = /var/log/mysql/error.log
add the following 2 lines bellow it.
general_log_file = /var/log/mysql/mysql.log
general_log = 1
To see the logs goto /var/log/mysql
and tail -f mysql.log
Remember to comment these lines out once you are done with debugging if you are in production environment delete mysql.log
as this log file will grow quickly and can be huge.
참고URL : https://stackoverflow.com/questions/2411182/how-to-debug-pdo-database-queries
'development' 카테고리의 다른 글
jQuery“속성이없는”선택기? (0) | 2020.06.28 |
---|---|
Enterprise Library Unity 및 기타 IoC 컨테이너 (0) | 2020.06.28 |
C #의 GetHashCode 지침 (0) | 2020.06.28 |
Visual Studio 2012-Intellisense가 때때로 사라지거나 손상됨 (0) | 2020.06.28 |
OS X에서 터미널을 사용하여 디렉토리를 이동하는 방법 (0) | 2020.06.28 |