php pdo预处理查询,关于php:从PDO预处理语句中获取原始SQL查询字符串
在對準備好的語句調用pdoStatement::execute()時,是否有方法執行原始SQL字符串?出于調試目的,這將非常有用。
對于php>=5.1,請查看php.net/manual/en/pdoStatement.debugdumpparams.php
檢查一行功能PDO調試。
我找到的最干凈的方法是E&U數據庫。你只需要做一個$stmt = $pdo->prepare($query); /* ... */ echo $stmt->fullQuery;。它通過擴展pdoStatement類來工作,因此在pdo api允許的情況下也非常優雅。
/**
* 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;
}
號
你真是太棒了,搖滾!什么是你的貝寶地址,所以我可以給你一個捐贈,認真嗎?
三年后我又問了一次。我怎么付錢給你?多年來,這個函數一手為我節省了無數小時調試復雜的準備好的語句。我真的很想報答你。
@史密斯剛剛從www.gittip.com上發了一個邀請,這是表達慷慨的最禮貌和最好的方式:)順便說一下,再次感謝@bigwebguy!
或者使用比特幣。:)
@史密斯:你可以賞金:)
該死。每次我看到這個,我都要給那個家伙50美元;-)到目前為止,大概是250美元!
為什么不直接使用strtr():更快、更簡單、結果相同。江戶十一〔一〕號
這個的用法是什么?
這種方法很厲害!我一直在用它;-)
我只是想順便過來感謝一下,因為這門課太小了,太聰明了,我現在已經把它取消了。因此,通過記錄應用程序在每個頁面上執行的所有查詢,對于消除它們的bug非常有用:d
看到這個功能,我很高興,雖然有些事情我不明白,為什么你要檢查$key是一個string,而不是$value?我錯過什么了嗎?我之所以這樣問是因為這個輸出,第二個參數不被看作字符串:string(115)"INSERT INTO tokens (token_type, token_hash, user_id) VALUES ('resetpassword', hzFs5RLMpKwTeShTjP9AkTA2jtxXls86, 1);"。
這是一個很好的開始,但如果$param本身的值包含問號("?"),則會失敗。.
我假設您的意思是需要最后一個SQL查詢,其中插入了參數值。我知道這對調試很有用,但這不是準備好的語句的工作方式。參數不與客戶端上準備好的語句組合在一起,因此PDO不應該訪問與其參數組合在一起的查詢字符串。
SQL語句在您執行prepare()時發送到數據庫服務器,參數在您執行()時單獨發送。mysql的常規查詢日志確實顯示了最終的SQL,其中的值是在執行()之后插入的。下面是我的常規查詢日志的摘錄。我從mysql cli運行查詢,而不是從pdo運行,但原理是相同的。
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_仿真_準備,也可以獲得所需的。在此模式下,pdo將參數插入到SQL查詢中,并在執行()時發送整個查詢。這不是真正準備好的查詢。您將通過在execute()之前將變量插入SQL字符串來規避準備好的查詢的好處。
@afilina回復評論:
不,執行期間文本SQL查詢不與參數組合。所以PDO沒有什么可以給你看的。
在內部,如果使用pdo::attr_仿真_準備,pdo會復制SQL查詢,并在準備和執行之前將參數值插入其中。但是PDO不會公開這個修改過的SQL查詢。
pPostatement對象具有屬性$queryString,但它僅在pPostatement的構造函數中設置,并且在用參數重寫查詢時不會更新。
對于PDO來說,要求他們公開重寫的查詢是一個合理的特性請求。但即使這樣,也不能提供"完整"的查詢,除非您使用pdo::attr_仿真_準備。
這就是我上面展示使用mysql服務器的常規查詢日志的解決方法的原因,因為在這種情況下,即使是帶有參數占位符的準備好的查詢也會在服務器上重寫,參數值會被反寫到查詢字符串中。但這只在日志記錄期間完成,而不是在查詢執行期間。
當pdo::attr_仿真準備設置為true時,如何獲取孔查詢?
@yasen zhelev:如果pdo正在模擬prepare,那么它將在準備查詢之前將參數值插入查詢中。因此,MySQL從未看到帶有參數占位符的查詢版本。MySQL只記錄完整的查詢。
@bill:'參數沒有與客戶端的準備好的語句組合在一起'-等等-但是它們是在服務器端組合的嗎?或者MySQL如何將值插入數據庫?
但是我可以只使用完全組合的查詢來調試它嗎?我不在乎準備好的語句在試圖找出我的查詢行為不當的原因時的優勢。
@阿菲莉娜,不,你不能。看上面我的解釋。
哇,投反對票?請不要射殺信使。我只是在描述它是如何工作的。
我修改了該方法,將處理數組輸出的語句包括在內(?).
更新:只是增加了對空值和重復的$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;
}
我認為你必須做$values = $params;,而不是$values = array()。
另一個漏掉的小部分是弦。為了捕捉這些,把這個放在is_array支票的上方:if (is_string($value)) $values[$key] ="'" . $value ."'";。
在preg_replace中,此值僅限于一次綁定值。在$values = $params;$values_limit = []; $words_repeated = array_count_values(str_word_count($sql, 1, ':_'));之后加上這一行,如果在foreach $values_limit[$key] = (isset($words_repeated[':'.$key]) ? intval($words_repeated[':'.$key]) : 1);中加上這一行,如果在foreach $values_limit = [];中加上這一行,則在foreach $values_limit = [];中加上這一行,再次使用foreach loop$值,以preg替換為isset($values_limit[$key])。
例如循環$values。埃多克斯1〔15〕
可能有點晚了,但現在有了PDOStatement::debugDumpParams。
Dumps the informations contained by a prepared statement directly on
the output. It will provide the SQL query in use, the number of
parameters used (Params), the list of parameters, with their name,
type (paramtype) as an integer, their key name or position, and the
position in the query (if this is supported by the PDO driver,
otherwise, it will be -1).
號
您可以在官方的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();
?>
注意php bug 52384:pDestatement::DebugDumpParams不發出綁定參數值。
pPostStatement具有公共屬性$queryString。這應該是你想要的。
我剛剛注意到pDestatement有一個未記錄的debugdumpParams()方法,您可能還需要查看它。
debugdumpparams沒有文檔記錄php.net/manual/en/pdoStatement.debugdumpparams.php
不。$querystring不顯示包含的參數值。
Mike在代碼中添加了更多的內容-通過值添加單引號
/**
* 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;
}
。
非常有用,我做了一些修改來重寫pdoStatement類的bindparam函數,并驗證該值是帶有pdo:params值的字符串還是整數。
我們在哪里能看到?
您可以擴展pPostatement類來捕獲有界變量并存儲它們以供以后使用。然后可以添加兩個方法,一個用于變量清理(debugbinderables),另一個用于打印帶有這些變量的查詢(debugquery):
class DebugPDOStatement extends \PDOStatement{
private $bound_variables=array();
protected $pdo;
protected function __construct($pdo) {
$this->pdo = $pdo;
}
public function bindValue($parameter, $value, $data_type=\PDO::PARAM_STR){
$this->bound_variables[$parameter] = (object) array('type'=>$data_type, 'value'=>$value);
return parent::bindValue($parameter, $value, $data_type);
}
public function bindParam($parameter, &$variable, $data_type=\PDO::PARAM_STR, $length=NULL , $driver_options=NULL){
$this->bound_variables[$parameter] = (object) array('type'=>$data_type, 'value'=>&$variable);
return parent::bindParam($parameter, $variable, $data_type, $length, $driver_options);
}
public function debugBindedVariables(){
$vars=array();
foreach($this->bound_variables as $key=>$val){
$vars[$key] = $val->value;
if($vars[$key]===NULL)
continue;
switch($val->type){
case \PDO::PARAM_STR: $type = 'string'; break;
case \PDO::PARAM_BOOL: $type = 'boolean'; break;
case \PDO::PARAM_INT: $type = 'integer'; break;
case \PDO::PARAM_NULL: $type = 'null'; break;
default: $type = FALSE;
}
if($type !== FALSE)
settype($vars[$key], $type);
}
if(is_numeric(key($vars)))
ksort($vars);
return $vars;
}
public function debugQuery(){
$queryString = $this->queryString;
$vars=$this->debugBindedVariables();
$params_are_numeric=is_numeric(key($vars));
foreach($vars as $key=>&$var){
switch(gettype($var)){
case 'string': $var ="'{$var}'"; break;
case 'integer': $var ="{$var}"; break;
case 'boolean': $var = $var ? 'TRUE' : 'FALSE'; break;
case 'NULL': $var = 'NULL';
default:
}
}
if($params_are_numeric){
$queryString = preg_replace_callback( '/\?/', function($match) use( &$vars) { return array_shift($vars); }, $queryString);
}else{
$queryString = strtr($queryString, $vars);
}
echo $queryString.PHP_EOL;
}
}
class DebugPDO extends \PDO{
public function __construct($dsn, $username="", $password="", $driver_options=array()) {
$driver_options[\PDO::ATTR_STATEMENT_CLASS] = array('DebugPDOStatement', array($this));
$driver_options[\PDO::ATTR_PERSISTENT] = FALSE;
parent::__construct($dsn,$username,$password, $driver_options);
}
}
。
然后您可以使用這個繼承的類來調試目的。
$dbh = new DebugPDO('mysql:host=localhost;dbname=test;','user','pass');
$var='user_test';
$sql=$dbh->prepare("SELECT user FROM users WHERE user = :test");
$sql->bindValue(':test', $var, PDO::PARAM_STR);
$sql->execute();
$sql->debugQuery();
print_r($sql->debugBindedVariables());
導致
SELECT user FROM users WHERE user = 'user_test'
Array (
[:test] => user_test
)
號
為了我自己的需要,我花了很多時間研究這種情況。這個和其他幾個這樣的線程幫助了我很多,所以我想分享我的想法。
雖然在故障排除時可以訪問插入的查詢字符串是一項重要的好處,但我們希望能夠只維護某些查詢的日志(因此,為此目的使用數據庫日志并不理想)。我們還希望能夠在任何給定時間使用日志重新創建表的條件,因此,我們需要確保正確地轉義了內插字符串。最后,我們希望將此功能擴展到整個代碼庫,必須盡可能少地重新編寫代碼(截止日期、市場營銷等;您知道它是怎樣的)。
我的解決方案是擴展默認pDestatement對象的功能以緩存參數化值(或引用),在執行語句時,使用pdo對象的功能在參數被注入回查詢字符串時正確地轉義這些參數。然后我們可以連接到語句對象的execute方法,并記錄當時執行的實際查詢(或者至少盡可能忠實于復制)。
如我所說,我們不想修改整個代碼庫來添加這個功能,所以我們覆蓋pdoStatement對象的默認bindParam()和bindValue()方法,緩存綁定數據,然后調用parent::bindParam()或parent::bindValue()。這允許我們現有的代碼庫繼續正常工作。
最后,當調用execute()方法時,我們執行插值,并將結果字符串作為新屬性E_PDOStatement->fullQuery提供。這可以輸出以查看查詢,或者,例如,寫入日志文件。
該擴展以及安裝和配置說明在GitHub上提供:
https://github.com/noaheck/e pdoStatement(網址:http://github.com/noaheck/e pdoStatement)
免責聲明:顯然,正如我提到的,我編寫了這個擴展。因為它是在許多線程的幫助下開發的,所以我想在這里發布我的解決方案,以防其他人遇到這些線程,就像我所做的那樣。
謝謝分享。不贊成票,因為答案太長,代碼太少
解決方法是自動在查詢中輸入錯誤并打印錯誤消息:
//Connection to the database
$co = new PDO('mysql:dbname=myDB;host=localhost','root','');
//We allow to print the errors whenever there is one
$co->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//We create our prepared statement
$stmt = $co->prepare("ELECT * FROM Person WHERE age=:age"); //I removed the 'S' of 'SELECT'
$stmt->bindValue(':age','18',PDO::PARAM_STR);
try {
$stmt->execute();
} catch (PDOException $e) {
echo $e->getMessage();
}
。
標準輸出:
SQLSTATE[42000]: Syntax error or access violation: [...] near 'ELECT * FROM Person WHERE age=18' at line 1
號
需要注意的是,它只打印查詢的前80個字符。
我不知道為什么會投反對票。它很簡單而且很有效。它工作得很快。比打開日志,在日志中搜索正確的行,然后禁用日志,然后清除日志文件快得多。
上面提到的$querystring屬性可能只返回傳入的查詢,而不使用其值替換參數。在.NET中,我讓查詢執行器的catch部分用提供的值對參數進行簡單的搜索替換,以便錯誤日志可以顯示用于查詢的實際值。您應該能夠在PHP中枚舉參數,并用它們的賦值替換這些參數。
我知道這個問題有點老了,但是,我從很久以前就開始使用這個代碼(我使用了@chris go的response),現在,這些代碼在php 7.2中已經過時了。
我將發布這些代碼的更新版本(主要代碼來自@bigwebguy、@mike和@chris go,它們都是這個問題的答案):
/**
* 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, function(&$v, $k) { if (!is_numeric($v) && $v !="NULL") $v ="\'" . $v ."\'"; });
$query = preg_replace($keys, $values, $query, 1, $count);
return $query;
}
注意,代碼的更改是在array_walk()函數上進行的,用匿名函數替換create_函數。這使得這些代碼具有良好的功能性,并與php 7.2兼容(也希望將來的版本)。
我需要在綁定參數之后記錄完整的查詢字符串,所以這是我代碼中的一部分。希望,它對每個有相同問題的人都有用。
/**
*
* @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);
}
。
有點關聯…如果您只是想清理一個特定的變量,那么可以使用pdo::quote。例如,如果您使用有限的框架(如cakephp)搜索多個部分類似的條件:
$pdo = $this->getDataSource()->getConnection();
$results = $this->find('all', array(
'conditions' => array(
'Model.name LIKE ' . $pdo->quote("%{$keyword1}%"),
'Model.name LIKE ' . $pdo->quote("%{$keyword2}%"),
),
);
號
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;
}
希望有人發現它有用。
在您使用"重用"綁定值之前,Mike的答案是有效的。例如:
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)
麥克的回答只能取代第一個:搜索,而不是第二個。所以,我重寫了他的答案,使其能夠正確地使用多個參數。
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;
}
。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的php pdo预处理查询,关于php:从PDO预处理语句中获取原始SQL查询字符串的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php拉图片 图片变形,请大神帮我看这个
- 下一篇: linux终端刷新网络命令,在Ubunt