PDOに移行する前に、文字列を連結してPHPにSQLクエリを作成しました。データベース構文エラーが発生した場合、最終的なSQLクエリ文字列をエコーし、データベースで自分で試して、エラーを修正するまで微調整し、それをコードに戻します。
準備されたPDOステートメントはより速く、より良く、より安全ですが、1つ気になることは、データベースに送信される最終クエリが表示されないことです。 Apacheログまたはカスタムログファイルの構文に関するエラー(catch
ブロック内のエラーをログに記録する)を取得すると、それらの原因となったクエリが表示されません。
PDOからデータベースに送信された完全なSQLクエリをキャプチャし、ファイルに記録する方法はありますか?
Pascal MARTINが正しいので、PDOはデータベースに完全なクエリを一度に送信しませんが、DBのロギング機能を使用するというryeguyの提案により、実際に確認できましたデータベースによってアセンブルおよび実行された完全なクエリ。
方法は次のとおりです(これらの手順はWindowsマシン上のMySQL向けです-走行距離は異なる場合があります)
my.ini
の[mysqld]
セクションの下に、log="C:\Program Files\MySQL\MySQL Server 5.1\data\mysql.log"
などのlog
コマンドを追加しますこのファイルはすぐに大きくなるため、テストが完了したら、必ず削除してロギングをオフにしてください。
あなたはこれを言う:
データベースに送信されるため、最終的なクエリは表示されません
実際、準備されたステートメントを使用する場合、"final query"のようなものはありません:
だから、あなたの質問に答えるために:
PDOからデータベースに送信された完全なSQLクエリをキャプチャし、ファイルに記録する方法はありますか?
いいえ:「完全なSQLクエリ」がないため、キャプチャする方法はありません。
デバッグのためにできる最善のことは、ステートメントのSQL文字列に値を挿入することにより、「実際の」SQLクエリを「再構築」することです。
このような状況で私が通常行うことは次のとおりです。
var_dump
(または同等の)を使用しますデバッグに関して言えば、これは素晴らしいことではありませんが、それは準備されたステートメントの価格とそれらがもたらす利点です。
このモードを使用してデバッグできます{{ 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クエリとパラメーターをデータベースサーバーに送信します。 データベースは、(?
の)置換を行うものです。次の2つのオプションがあります。
エラーログのチェック以外のエラー表示についてはほとんど何も言われていませんが、かなり便利な機能があります。
<?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;
MySQLをWAMPにログインするには、my.iniを編集する必要があります(例:wamp\bin\mysql\mysql5.6.17\my.ini)
[mysqld]
に追加します:
general_log = 1
general_log_file="c:\\tmp\\mysql.log"
インターネットを検索すると、これは許容できる解決策であることがわかりました。 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);
}
}
「解決済み」パラメーターを使用して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をデバッグします( 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でロードされたプロジェクト/リポジトリを作成しました。
プロジェクトの GitHubホームはこちら を見つけ、 ここで説明しているブログ投稿 をご覧ください。 composer.jsonに1行追加すると、次のように使用できます。
echo debugPDO($sql, $parameters);
$ sqlは生のSQLステートメント、$ parametersはパラメーターの配列です。キーはプレースホルダー名( ":user_id")または名前のないパラメーターの番号( "?")で、値は..です。値。
背後にあるロジック:このスクリプトは、単にパラメーターを評価し、提供されたSQL文字列に置き換えます。非常にシンプルですが、ユースケースの99%に対して非常に効果的です。注:これは基本的なエミュレーションであり、実際のPDOデバッグではありません(PHPは生のSQLとパラメーターをMySQLサーバーに分離して送信するため、これは不可能です)。
bigwebguyとMikeからの大きな感謝 StackOverflowスレッド PDOから生のSQLクエリ文字列を取得する 基本的にこのスクリプトの背後にあるメイン関数全体を記述します。ビッグアップ!
デバッグの目的で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を自分の名前で置き換えることを忘れないでください
TL; DRすべてのクエリを記録し、mysqlログを追跡します。
これらの指示は、Ubuntu 14.04をインストールするためのものです。コマンドlsb_release -a
を発行して、バージョンを取得します。インストールが異なる場合があります。
cd /etc/mysql
。 my.cnf
というファイルが表示されるはずです。これが変更するファイルです。cat my.cnf | grep general_log
と入力して、正しい場所にいることを確認します。これにより、my.cnf
ファイルがフィルタリングされます。 #general_log_file = /var/log/mysql/mysql.log
&& #general_log = 1
の2つのエントリが表示されます。Sudo service mysql restart
。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='[email protected]' LIMIT 5
75 Close stmt
75 Quit
73 Quit
ログの追跡を続ける限り、アプリが作成する新しいクエリはすべて自動的にビューに表示されます。テールを終了するには、cmd/ctrl c
を押します。
truncate --size 0 mysql.log
。Nathan Longの上記の答え に大声で叫ぶと、inspoがUbuntuでこれを理解します。また、 dikirill に、Nathanの投稿に対するコメントを寄せてくれました。
あなたを愛してます