php pdo预处理查询,关于php:从PDO预处理语句中获取原始SQL查询字符串
在對準備好的語句調(diào)用pdoStatement::execute()時,是否有方法執(zhí)行原始SQL字符串?出于調(diào)試目的,這將非常有用。
對于php>=5.1,請查看php.net/manual/en/pdoStatement.debugdumpparams.php
檢查一行功能PDO調(diào)試。
我找到的最干凈的方法是E&U數(shù)據(jù)庫。你只需要做一個$stmt = $pdo->prepare($query); /* ... */ echo $stmt->fullQuery;。它通過擴展pdoStatement類來工作,因此在pdo api允許的情況下也非常優(yōu)雅。
/**
* 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;
}
號
你真是太棒了,搖滾!什么是你的貝寶地址,所以我可以給你一個捐贈,認真嗎?
三年后我又問了一次。我怎么付錢給你?多年來,這個函數(shù)一手為我節(jié)省了無數(shù)小時調(diào)試復雜的準備好的語句。我真的很想報答你。
@史密斯剛剛從www.gittip.com上發(fā)了一個邀請,這是表達慷慨的最禮貌和最好的方式:)順便說一下,再次感謝@bigwebguy!
或者使用比特幣。:)
@史密斯:你可以賞金:)
該死。每次我看到這個,我都要給那個家伙50美元;-)到目前為止,大概是250美元!
為什么不直接使用strtr():更快、更簡單、結(jié)果相同。江戶十一〔一〕號
這個的用法是什么?
這種方法很厲害!我一直在用它;-)
我只是想順便過來感謝一下,因為這門課太小了,太聰明了,我現(xiàn)在已經(jīng)把它取消了。因此,通過記錄應用程序在每個頁面上執(zhí)行的所有查詢,對于消除它們的bug非常有用:d
看到這個功能,我很高興,雖然有些事情我不明白,為什么你要檢查$key是一個string,而不是$value?我錯過什么了嗎?我之所以這樣問是因為這個輸出,第二個參數(shù)不被看作字符串:string(115)"INSERT INTO tokens (token_type, token_hash, user_id) VALUES ('resetpassword', hzFs5RLMpKwTeShTjP9AkTA2jtxXls86, 1);"。
這是一個很好的開始,但如果$param本身的值包含問號("?"),則會失敗。.
我假設您的意思是需要最后一個SQL查詢,其中插入了參數(shù)值。我知道這對調(diào)試很有用,但這不是準備好的語句的工作方式。參數(shù)不與客戶端上準備好的語句組合在一起,因此PDO不應該訪問與其參數(shù)組合在一起的查詢字符串。
SQL語句在您執(zhí)行prepare()時發(fā)送到數(shù)據(jù)庫服務器,參數(shù)在您執(zhí)行()時單獨發(fā)送。mysql的常規(guī)查詢?nèi)罩敬_實顯示了最終的SQL,其中的值是在執(zhí)行()之后插入的。下面是我的常規(guī)查詢?nèi)罩镜恼洝N覐膍ysql 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將參數(shù)插入到SQL查詢中,并在執(zhí)行()時發(fā)送整個查詢。這不是真正準備好的查詢。您將通過在execute()之前將變量插入SQL字符串來規(guī)避準備好的查詢的好處。
@afilina回復評論:
不,執(zhí)行期間文本SQL查詢不與參數(shù)組合。所以PDO沒有什么可以給你看的。
在內(nèi)部,如果使用pdo::attr_仿真_準備,pdo會復制SQL查詢,并在準備和執(zhí)行之前將參數(shù)值插入其中。但是PDO不會公開這個修改過的SQL查詢。
pPostatement對象具有屬性$queryString,但它僅在pPostatement的構(gòu)造函數(shù)中設置,并且在用參數(shù)重寫查詢時不會更新。
對于PDO來說,要求他們公開重寫的查詢是一個合理的特性請求。但即使這樣,也不能提供"完整"的查詢,除非您使用pdo::attr_仿真_準備。
這就是我上面展示使用mysql服務器的常規(guī)查詢?nèi)罩镜慕鉀Q方法的原因,因為在這種情況下,即使是帶有參數(shù)占位符的準備好的查詢也會在服務器上重寫,參數(shù)值會被反寫到查詢字符串中。但這只在日志記錄期間完成,而不是在查詢執(zhí)行期間。
當pdo::attr_仿真準備設置為true時,如何獲取孔查詢?
@yasen zhelev:如果pdo正在模擬prepare,那么它將在準備查詢之前將參數(shù)值插入查詢中。因此,MySQL從未看到帶有參數(shù)占位符的查詢版本。MySQL只記錄完整的查詢。
@bill:'參數(shù)沒有與客戶端的準備好的語句組合在一起'-等等-但是它們是在服務器端組合的嗎?或者MySQL如何將值插入數(shù)據(jù)庫?
但是我可以只使用完全組合的查詢來調(diào)試它嗎?我不在乎準備好的語句在試圖找出我的查詢行為不當?shù)脑驎r的優(yōu)勢。
@阿菲莉娜,不,你不能。看上面我的解釋。
哇,投反對票?請不要射殺信使。我只是在描述它是如何工作的。
我修改了該方法,將處理數(shù)組輸出的語句包括在內(nèi)(?).
更新:只是增加了對空值和重復的$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])。
例如循環(huán)$values。埃多克斯1〔15〕
可能有點晚了,但現(xiàn)在有了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不發(fā)出綁定參數(shù)值。
pPostStatement具有公共屬性$queryString。這應該是你想要的。
我剛剛注意到pDestatement有一個未記錄的debugdumpParams()方法,您可能還需要查看它。
debugdumpparams沒有文檔記錄php.net/manual/en/pdoStatement.debugdumpparams.php
不。$querystring不顯示包含的參數(shù)值。
Mike在代碼中添加了更多的內(nèi)容-通過值添加單引號
/**
* 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函數(shù),并驗證該值是帶有pdo:params值的字符串還是整數(shù)。
我們在哪里能看到?
您可以擴展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);
}
}
。
然后您可以使用這個繼承的類來調(diào)試目的。
$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
)
號
為了我自己的需要,我花了很多時間研究這種情況。這個和其他幾個這樣的線程幫助了我很多,所以我想分享我的想法。
雖然在故障排除時可以訪問插入的查詢字符串是一項重要的好處,但我們希望能夠只維護某些查詢的日志(因此,為此目的使用數(shù)據(jù)庫日志并不理想)。我們還希望能夠在任何給定時間使用日志重新創(chuàng)建表的條件,因此,我們需要確保正確地轉(zhuǎn)義了內(nèi)插字符串。最后,我們希望將此功能擴展到整個代碼庫,必須盡可能少地重新編寫代碼(截止日期、市場營銷等;您知道它是怎樣的)。
我的解決方案是擴展默認pDestatement對象的功能以緩存參數(shù)化值(或引用),在執(zhí)行語句時,使用pdo對象的功能在參數(shù)被注入回查詢字符串時正確地轉(zhuǎn)義這些參數(shù)。然后我們可以連接到語句對象的execute方法,并記錄當時執(zhí)行的實際查詢(或者至少盡可能忠實于復制)。
如我所說,我們不想修改整個代碼庫來添加這個功能,所以我們覆蓋pdoStatement對象的默認bindParam()和bindValue()方法,緩存綁定數(shù)據(jù),然后調(diào)用parent::bindParam()或parent::bindValue()。這允許我們現(xiàn)有的代碼庫繼續(xù)正常工作。
最后,當調(diào)用execute()方法時,我們執(zhí)行插值,并將結(jié)果字符串作為新屬性E_PDOStatement->fullQuery提供。這可以輸出以查看查詢,或者,例如,寫入日志文件。
該擴展以及安裝和配置說明在GitHub上提供:
https://github.com/noaheck/e pdoStatement(網(wǎng)址:http://github.com/noaheck/e pdoStatement)
免責聲明:顯然,正如我提到的,我編寫了這個擴展。因為它是在許多線程的幫助下開發(fā)的,所以我想在這里發(fā)布我的解決方案,以防其他人遇到這些線程,就像我所做的那樣。
謝謝分享。不贊成票,因為答案太長,代碼太少
解決方法是自動在查詢中輸入錯誤并打印錯誤消息:
//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屬性可能只返回傳入的查詢,而不使用其值替換參數(shù)。在.NET中,我讓查詢執(zhí)行器的catch部分用提供的值對參數(shù)進行簡單的搜索替換,以便錯誤日志可以顯示用于查詢的實際值。您應該能夠在PHP中枚舉參數(shù),并用它們的賦值替換這些參數(shù)。
我知道這個問題有點老了,但是,我從很久以前就開始使用這個代碼(我使用了@chris go的response),現(xiàn)在,這些代碼在php 7.2中已經(jīng)過時了。
我將發(fā)布這些代碼的更新版本(主要代碼來自@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()函數(shù)上進行的,用匿名函數(shù)替換create_函數(shù)。這使得這些代碼具有良好的功能性,并與php 7.2兼容(也希望將來的版本)。
我需要在綁定參數(shù)之后記錄完整的查詢字符串,所以這是我代碼中的一部分。希望,它對每個有相同問題的人都有用。
/**
*
* @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);
}
。
有點關聯(lián)…如果您只是想清理一個特定的變量,那么可以使用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;
}
希望有人發(fā)現(xiàn)它有用。
在您使用"重用"綁定值之前,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)
麥克的回答只能取代第一個:搜索,而不是第二個。所以,我重寫了他的答案,使其能夠正確地使用多個參數(shù)。
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位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的php pdo预处理查询,关于php:从PDO预处理语句中获取原始SQL查询字符串的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php拉图片 图片变形,请大神帮我看这个
- 下一篇: 动态规划算法php,php算法学习之动态