web-dev-qa-db-ja.com

PDOデータベースクエリをデバッグする方法

PDOに移行する前に、文字列を連結してPHPにSQLクエリを作成しました。データベース構文エラーが発生した場合、最終的なSQLクエリ文字列をエコーし​​、データベースで自分で試して、エラーを修正するまで微調整し、それをコードに戻します。

準備されたPDOステートメントはより速く、より良く、より安全ですが、1つ気になることは、データベースに送信される最終クエリが表示されないことです。 Apacheログまたはカスタムログファイルの構文に関するエラー(catchブロック内のエラーをログに記録する)を取得すると、それらの原因となったクエリが表示されません。

PDOからデータベースに送信された完全なSQLクエリをキャプチャし、ファイルに記録する方法はありますか?

132
Nathan Long

データベースログを見る

Pascal MARTINが正しいので、PDOはデータベースに完全なクエリを一度に送信しませんが、DBのロギング機能を使用するというryeguyの提案により、実際に確認できましたデータベースによってアセンブルおよび実行された完全なクエリ。

方法は次のとおりです(これらの手順はWindowsマシン上のMySQL向けです-走行距離は異なる場合があります)

  • my.ini[mysqld]セクションの下に、log="C:\Program Files\MySQL\MySQL Server 5.1\data\mysql.log"などのlogコマンドを追加します
  • MySQLを再起動します。
  • そのファイルのすべてのクエリのログを開始します。

このファイルはすぐに大きくなるため、テストが完了したら、必ず削除してロギングをオフにしてください。

84
Nathan Long

あなたはこれを言う:

データベースに送信されるため、最終的なクエリは表示されません

実際、準備されたステートメントを使用する場合、"final query"のようなものはありません:

  • 最初に、ステートメントがDBに送信され、そこで準備されます
    • データベースはクエリを解析し、その内部表現を構築します
  • また、変数をバインドしてステートメントを実行すると、変数のみがデータベースに送信されます
    • データベースは、ステートメントの内部表現に値を「注入」します


だから、あなたの質問に答えるために:

PDOからデータベースに送信された完全なSQLクエリをキャプチャし、ファイルに記録する方法はありますか?

いいえ:「完全なSQLクエリ」がないため、キャプチャする方法はありません。


デバッグのためにできる最善のことは、ステートメントのSQL文字列に値を挿入することにより、「実際の」SQLクエリを「再構築」することです。

このような状況で私が通常行うことは次のとおりです。

  • ステートメントに対応するSQLコードをプレースホルダーでエコーします
  • パラメーターの値を表示するには、直後にvar_dump(または同等の)を使用します
  • これは、実行できる「実際の」クエリがない場合でも、一般にエラーの可能性を確認するには十分です。

デバッグに関して言えば、これは素晴らしいことではありませんが、それは準備されたステートメントの価格とそれらがもたらす利点です。

96
Pascal MARTIN

このモードを使用してデバッグできます{{ PDO::ATTR_ERRMODE }}クエリの前に新しい行を追加するだけで、デバッグ行が表示されます。

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

おそらくあなたがしたいことは debugDumParams() を使用することです.

14
fijiaaron

古い投稿ですが、おそらく誰かがこれを役に立つと思うでしょう。

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

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;
}
9
Matt Browne

いいえ。PDOクエリはクライアント側で準備されません。 PDOは、単にSQLクエリとパラメーターをデータベースサーバーに送信します。 データベースは、(?の)置換を行うものです。次の2つのオプションがあります。

  • DBのログ機能を使用します(ただし、それでも、通常は少なくともPostgresでは2つの別個のステートメント(つまり「not final」)として表示されます)
  • SQLクエリとパラメーターを出力し、自分で組み立てます
8
ryeguy

エラーログのチェック以外のエラー表示についてはほとんど何も言われていませんが、かなり便利な機能があります。

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

ソースリンク

このコードを変更して、例外メッセージまたはその他の種類のエラー処理として使用できることは明らかです。

5
Zippp

たとえば、次の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;
3
Alireza

MySQLをWAMPにログインするには、my.iniを編集する必要があります(例:wamp\bin\mysql\mysql5.6.17\my.ini)

[mysqld]に追加します:

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

インターネットを検索すると、これは許容できる解決策であることがわかりました。 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); 
}

}
2
bkilinc

「解決済み」パラメーターを使用して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);

この関数はクエリに引用符を追加しませんが、私のために仕事をします。

1
rezden

このクラスを使用して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
Janos Szabo

まさにこれのために、最新のComposerでロードされたプロジェクト/リポジトリを作成しました。

pdo-debug

プロジェクトの GitHubホームはこちら を見つけ、 ここで説明しているブログ投稿 をご覧ください。 composer.jsonに1行追加すると、次のように使用できます。

echo debugPDO($sql, $parameters);

$ sqlは生のSQLステートメント、$ parametersはパラメーターの配列です。キーはプレースホルダー名( ":user_id")または名前のないパラメーターの番号( "?")で、値は..です。値。

背後にあるロジック:このスクリプトは、単にパラメーターを評価し、提供されたSQL文字列に置き換えます。非常にシンプルですが、ユースケースの99%に対して非常に効果的です。注:これは基本的なエミュレーションであり、実際のP​​DOデバッグではありません(PHPは生のSQLとパラメーターをMySQLサーバーに分離して送信するため、これは不可能です)。

bigwebguyMikeからの大きな感謝 StackOverflowスレッド PDOから生のSQLクエリ文字列を取得する 基本的にこのスクリプトの背後にあるメイン関数全体を記述します。ビッグアップ!

0
Sliq

デバッグの目的で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);
    }
}

テーブルモデルをグローバル変数として設定するよりも、エラーハンドラーに関連する情報を取得する方法について、より良いアイデアがあれば、喜んでそれを聞いてコードを編集します。

0
Troy Knapp

このコードは私に最適です:

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

$ dataと$ queryを自分の名前で置き換えることを忘れないでください

0
user3553866

UbuntuでPDO mysqlデータベースクエリをデバッグする方法

TL; DRすべてのクエリを記録し、mysqlログを追跡します。

これらの指示は、Ubuntu 14.04をインストールするためのものです。コマンドlsb_release -aを発行して、バージョンを取得します。インストールが異なる場合があります。

Mysqlでログを有効にする

  1. 開発サーバーのcmd行に移動します
  2. ディレクトリを変更しますcd /etc/mysqlmy.cnfというファイルが表示されるはずです。これが変更するファイルです。
  3. cat my.cnf | grep general_logと入力して、正しい場所にいることを確認します。これにより、my.cnfファイルがフィルタリングされます。 #general_log_file = /var/log/mysql/mysql.log && #general_log = 1の2つのエントリが表示されます。
  4. これらの2行のコメントを外し、選択したエディターで保存します。
  5. Mysqlを再起動します:Sudo service mysql restart
  6. Webサーバーも再起動する必要がある場合があります。 (使用したシーケンスを思い出せません)。私のインストールでは、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='[email protected]' LIMIT 5
75 Close stmt   
75 Quit 
73 Quit 

ログの追跡を続ける限り、アプリが作成する新しいクエリはすべて自動的にビューに表示されます。テールを終了するには、cmd/ctrl cを押します。

ノート

  1. 注意:このログファイルは大きくなる可能性があります。これは開発サーバーでのみ実行しています。
  2. ログファイルが大きくなりすぎていますか?切り捨てます。つまり、ファイルは残りますが、コンテンツは削除されます。 truncate --size 0 mysql.log
  3. ログファイルにmysql接続がリストされているのはすばらしいことです。それらの1つは、移行元のレガシーmysqliコードからのものであることを知っています。 3番目は、私の新しいPDO接続からです。ただし、2番目がどこから来ているのかはわかりません。あなたがそれを見つける簡単な方法を知っているなら、私に知らせてください。

クレジットと感謝

Nathan Longの上記の答え に大声で叫ぶと、inspoがUbuntuでこれを理解します。また、 dikirill に、Nathanの投稿に対するコメントを寄せてくれました。

あなたを愛してます