145

準備されたステートメントで PDOStatement::execute() を呼び出すときに実行される生の SQL 文字列を取得する方法はありますか? デバッグ目的では、これは非常に便利です。

4

17 に答える 17

117

パラメーター値が補間された最終的な SQL クエリが必要なのだと思います。これがデバッグに役立つことは理解していますが、準備済みステートメントが機能する方法ではありません。パラメーターはクライアント側の準備済みステートメントと結合されないため、PDO はそのパラメーターと結合されたクエリ文字列にアクセスできません。

prepare() を実行すると SQL ステートメントがデータベース サーバに送信され、execute() を実行するとパラメータが個別に送信されます。MySQL の一般的なクエリ ログには、execute() 後に値が補間された最終的な SQL が表示されます。以下は、私の一般的なクエリ ログからの抜粋です。PDO からではなく、mysql CLI からクエリを実行しましたが、原則は同じです。

081016 16:51:28 2 Query       prepare s1 from 'select * from foo where i = ?'
                2 Prepare     [2] select * from foo where i = ?
081016 16:51:39 2 Query       set @a =1
081016 16:51:47 2 Query       execute s1 using @a
                2 Execute     [2] select * from foo where i = 1

PDO 属性 PDO::ATTR_EMULATE_PREPARES を設定すると、必要なものを取得することもできます。このモードでは、PDO はパラメータを SQL クエリに補間し、execute() を実行するとクエリ全体を送信します。 これは真の準備済みクエリではありません。 execute() の前に変数を SQL 文字列に補間することで、準備されたクエリの利点を回避できます。


@afilina からの再コメント:

いいえ、テキスト SQL クエリは、実行中にパラメーターと結合されません。したがって、PDO が表示するものは何もありません。

内部的に、PDO::ATTR_EMULATE_PREPARES を使用すると、PDO は SQL クエリのコピーを作成し、準備と実行を行う前にパラメーター値を挿入します。しかし、PDO はこの変更された SQL クエリを公開しません。

PDOStatement オブジェクトにはプロパティ $queryString がありますが、これは PDOStatement のコンストラクターでのみ設定され、クエリがパラメーターで書き換えられても更新されません。

書き直されたクエリを公開するよう PDO に依頼することは、PDO にとって合理的な機能要求です。しかし、それでも PDO::ATTR_EMULATE_PREPARES を使用しない限り、「完全な」クエリは得られません。

これが、MySQL サーバーの一般的なクエリ ログを使用する上記の回避策を示す理由です。この場合、パラメーター プレースホルダーを使用して準備されたクエリでさえサーバー上で書き換えられ、パラメーター値がクエリ文字列に埋め戻されるからです。ただし、これはロギング中にのみ行われ、クエリの実行中には行われません。

于 2008-10-16T23:55:25.883 に答える
110
/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public static function interpolateQuery($query, $params) {
    $keys = array();

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }
    }

    $query = preg_replace($keys, $params, $query, 1, $count);

    #trigger_error('replaced '.$count.' keys');

    return $query;
}
于 2009-09-04T01:41:32.663 に答える
34

メソッドを変更して、WHERE IN (?) などのステートメントの配列の出力を処理するようにしました。

更新: NULL 値のチェックを追加し、$params を複製したため、実際の $param 値は変更されません。

bigwebguy さん、お疲れ様でした。

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_string($value))
            $values[$key] = "'" . $value . "'";

        if (is_array($value))
            $values[$key] = "'" . implode("','", $value) . "'";

        if (is_null($value))
            $values[$key] = 'NULL';
    }

    $query = preg_replace($keys, $values, $query);

    return $query;
}
于 2011-12-06T16:16:55.113 に答える
10

少し遅いかもしれませんが、今はありますPDOStatement::debugDumpParams

準備されたステートメントに含まれる情報を出力に直接ダンプします。使用中の SQL クエリ、使用されているパラメーターの数 (Params)、パラメーターのリスト、名前、型 (paramtype) を整数として、キーの名前または位置、およびクエリ内の位置 (この場合は PDO ドライバーでサポートされています。それ以外の場合は -1 になります)。

詳細については、公式の php ドキュメントを参照してください。

例:

<?php
/* Execute a prepared statement by binding PHP variables */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour');
$sth->bindParam(':calories', $calories, PDO::PARAM_INT);
$sth->bindValue(':colour', $colour, PDO::PARAM_STR, 12);
$sth->execute();

$sth->debugDumpParams();

?>
于 2014-07-23T13:25:18.213 に答える
9

マイクによってコードにもう少し追加されました - 値をたどって一重引用符を追加します

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_array($value))
            $values[$key] = implode(',', $value);

        if (is_null($value))
            $values[$key] = 'NULL';
    }
    // Walk the array to see if we can add single-quotes to strings
    array_walk($values, create_function('&$v, $k', 'if (!is_numeric($v) && $v!="NULL") $v = "\'".$v."\'";'));

    $query = preg_replace($keys, $values, $query, 1, $count);

    return $query;
}
于 2012-08-18T05:02:53.363 に答える
6

PDOStatement にはパブリック プロパティ $queryString があります。それはあなたが望むものでなければなりません。

PDOStatement にドキュメント化されていないメソッド debugDumpParams() があることに気付きました。これも参照してください。

于 2008-10-16T22:59:58.693 に答える
1

上記の $queryString プロパティは、パラメーターが値に置き換えられずに、渡されたクエリのみを返す可能性があります。.Net では、クエリ実行プログラムの catch 部分に単純な検索を実行させ、パラメータを提供された値に置き換えて、クエリに使用されていた実際の値をエラー ログに表示できるようにします。PHP でパラメーターを列挙し、パラメーターを割り当てられた値に置き換えることができるはずです。

于 2008-10-16T23:12:22.100 に答える
1

既存の回答はどれも完全または安全ではなかったので、次の改善点があるこの関数を思いつきました。

  • ?名前のない ( ) パラメーターと名前付き ( ) パラメーターの両方で機能し:fooます。

  • PDO::quote()を使用してNULL、 、intfloatまたはではない値を適切にエスケープしますbool

  • "?"プレースホルダーと":foo"間違えることなく、文字列値を含む文字列値を適切に処理します。

    function interpolateSQL(PDO $pdo, string $query, array $params) : string {
        $s = chr(2); // Escape sequence for start of placeholder
        $e = chr(3); // Escape sequence for end of placeholder
        $keys = [];
        $values = [];

        // Make sure we use escape sequences that are not present in any value
        // to escape the placeholders.
        foreach ($params as $key => $value) {
            while( mb_stripos($value, $s) !== false ) $s .= $s;
            while( mb_stripos($value, $e) !== false ) $e .= $e;
        }
        
        
        foreach ($params as $key => $value) {
            // Build a regular expression for each parameter
            $keys[] = is_string($key) ? "/$s:$key$e/" : "/$s\?$e/";

            // Treat each value depending on what type it is. 
            // While PDO::quote() has a second parameter for type hinting, 
            // it doesn't seem reliable (at least for the SQLite driver).
            if( is_null($value) ){
                $values[$key] = 'NULL';
            }
            elseif( is_int($value) || is_float($value) ){
                $values[$key] = $value;
            }
            elseif( is_bool($value) ){
                $values[$key] = $value ? 'true' : 'false';
            }
            else{
                $value = str_replace('\\', '\\\\', $value);
                $values[$key] = $pdo->quote($value);
            }
        }

        // Surround placehodlers with escape sequence, so we don't accidentally match
        // "?" or ":foo" inside any of the values.
        $query = preg_replace(['/\?/', '/(:[a-zA-Z0-9_]+)/'], ["$s?$e", "$s$1$e"], $query);

        // Replace placeholders with actual values
        $query = preg_replace($keys, $values, $query, 1, $count);

        // Verify that we replaced exactly as many placeholders as there are keys and values
        if( $count !== count($keys) || $count !== count($values) ){
            throw new \Exception('Number of replacements not same as number of keys and/or values');
        }

        return $query;
    }

さらに改善できると確信しています。

私の場合、最終的には、実際の「準備されていないクエリ」(つまり、プレースホルダーを含む SQL) と JSON エンコードされたパラメーターをログに記録することになりました。ただし、このコードは、最終的な SQL クエリを実際に補間する必要がある一部のユース ケースで使用される可能性があります。

于 2021-03-04T07:08:04.447 に答える
-1

preg_replace は私にはうまくいきませんでした。binding_ が 9 を超えると、binding_1 と binding_10 が str_replace に置き換えられ (0 は残ります)、逆方向に置換を行いました。

public function interpolateQuery($query, $params) {
$keys = array();
    $length = count($params)-1;
    for ($i = $length; $i >=0; $i--) {
            $query  = str_replace(':binding_'.(string)$i, '\''.$params[$i]['val'].'\'', $query);
           }
        // $query  = str_replace('SQL_CALC_FOUND_ROWS', '', $query, $count);
        return $query;

}

誰かが役に立つことを願っています。

于 2016-01-08T16:05:11.847 に答える
-1

bind param の後に完全なクエリ文字列をログに記録する必要があるため、これは私のコードの一部です。同じ問題を抱えているすべての人に役立つことを願っています。

/**
 * 
 * @param string $str
 * @return string
 */
public function quote($str) {
    if (!is_array($str)) {
        return $this->pdo->quote($str);
    } else {
        $str = implode(',', array_map(function($v) {
                    return $this->quote($v);
                }, $str));

        if (empty($str)) {
            return 'NULL';
        }

        return $str;
    }
}

/**
 * 
 * @param string $query
 * @param array $params
 * @return string
 * @throws Exception
 */
public function interpolateQuery($query, $params) {
    $ps = preg_split("/'/is", $query);
    $pieces = [];
    $prev = null;
    foreach ($ps as $p) {
        $lastChar = substr($p, strlen($p) - 1);

        if ($lastChar != "\\") {
            if ($prev === null) {
                $pieces[] = $p;
            } else {
                $pieces[] = $prev . "'" . $p;
                $prev = null;
            }
        } else {
            $prev .= ($prev === null ? '' : "'") . $p;
        }
    }

    $arr = [];
    $indexQuestionMark = -1;
    $matches = [];

    for ($i = 0; $i < count($pieces); $i++) {
        if ($i % 2 !== 0) {
            $arr[] = "'" . $pieces[$i] . "'";
        } else {
            $st = '';
            $s = $pieces[$i];
            while (!empty($s)) {
                if (preg_match("/(\?|:[A-Z0-9_\-]+)/is", $s, $matches, PREG_OFFSET_CAPTURE)) {
                    $index = $matches[0][1];
                    $st .= substr($s, 0, $index);
                    $key = $matches[0][0];
                    $s = substr($s, $index + strlen($key));

                    if ($key == '?') {
                        $indexQuestionMark++;
                        if (array_key_exists($indexQuestionMark, $params)) {
                            $st .= $this->quote($params[$indexQuestionMark]);
                        } else {
                            throw new Exception('Wrong params in query at ' . $index);
                        }
                    } else {
                        if (array_key_exists($key, $params)) {
                            $st .= $this->quote($params[$key]);
                        } else {
                            throw new Exception('Wrong params in query with key ' . $key);
                        }
                    }
                } else {
                    $st .= $s;
                    $s = null;
                }
            }
            $arr[] = $st;
        }
    }

    return implode('', $arr);
}
于 2016-06-27T08:57:50.683 に答える
-2

多少関連しています...特定の変数をサニタイズしようとしているだけなら、PDO::quoteを使用できます。たとえば、CakePHP のような限られたフレームワークに行き詰まっている場合に、複数の部分的な LIKE 条件を検索するには:

$pdo = $this->getDataSource()->getConnection();
$results = $this->find('all', array(
    'conditions' => array(
        'Model.name LIKE ' . $pdo->quote("%{$keyword1}%"),
        'Model.name LIKE ' . $pdo->quote("%{$keyword2}%"),
    ),
);
于 2014-10-22T09:57:29.067 に答える
-3

「再利用」バインド値を使用するまで、マイクの答えはうまく機能しています。
例えば:

SELECT * FROM `an_modules` AS `m` LEFT JOIN `an_module_sites` AS `ms` ON m.module_id = ms.module_id WHERE 1 AND `module_enable` = :module_enable AND `site_id` = :site_id AND (`module_system_name` LIKE :search OR `module_version` LIKE :search)

マイクの答えは、最初の :search のみを置き換えることができますが、2 番目は置き換えることができません。
そのため、適切に再利用できる複数のパラメーターで機能するように彼の答えを書き直しました。

public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;
    $values_limit = [];

    $words_repeated = array_count_values(str_word_count($query, 1, ':_'));

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
            $values_limit[$key] = (isset($words_repeated[':'.$key]) ? intval($words_repeated[':'.$key]) : 1);
        } else {
            $keys[] = '/[?]/';
            $values_limit = [];
        }

        if (is_string($value))
            $values[$key] = "'" . $value . "'";

        if (is_array($value))
            $values[$key] = "'" . implode("','", $value) . "'";

        if (is_null($value))
            $values[$key] = 'NULL';
    }

    if (is_array($values)) {
        foreach ($values as $key => $val) {
            if (isset($values_limit[$key])) {
                $query = preg_replace(['/:'.$key.'/'], [$val], $query, $values_limit[$key], $count);
            } else {
                $query = preg_replace(['/:'.$key.'/'], [$val], $query, 1, $count);
            }
        }
        unset($key, $val);
    } else {
        $query = preg_replace($keys, $values, $query, 1, $count);
    }
    unset($keys, $values, $values_limit, $words_repeated);

    return $query;
}
于 2015-11-26T06:42:57.557 に答える