我正在尝试为我的数据库实现一个非常基本的搜索引擎,其中用户可能包含不同类型的信息。搜索本身包含一些联合选择,其中结果总是合并为3列。
然而,返回的数据是从不同的表中提取的。
每个查询都使用$ term进行匹配,我将它作为准备参数绑定到“:term”。
现在,手册说:
在调用PDOStatement :: execute()时,必须为要传递给语句的每个值包含唯一的参数标记。您不能在预准备语句中两次使用同名的命名参数标记。
我认为不是将每个:term参数替换为:termX(x代表term = n ++),必须有一个更好的解决方案吗?
或者我只需绑定X号:termX?
修改发布我的解决方案:
$query = "SELECT ... FROM table WHERE name LIKE :term OR number LIKE :term";
$term = "hello world";
$termX = 0;
$query = preg_replace_callback("/\:term/", function ($matches) use (&$termX) { $termX++; return $matches[0] . ($termX - 1); }, $query);
$pdo->prepare($query);
for ($i = 0; $i < $termX; $i++)
$pdo->bindValue(":term$i", "%$term%", PDO::PARAM_STR);
好的,这是一个样本。我没有时间使用sqlfiddle,但如果有必要,我会稍后添加一个。
(
SELECT
t1.`name` AS resultText
FROM table1 AS t1
WHERE
t1.parent = :userID
AND
(
t1.`name` LIKE :term
OR
t1.`number` LIKE :term
AND
t1.`status` = :flagStatus
)
)
UNION
(
SELECT
t2.`name` AS resultText
FROM table2 AS t2
WHERE
t2.parent = :userParentID
AND
(
t2.`name` LIKE :term
OR
t2.`ticket` LIKE :term
AND
t1.`state` = :flagTicket
)
)
答案 0 :(得分:16)
我现在已经遇到过几次同样的问题了,我想我找到了一个非常简单和好的解决方案。如果我想多次使用参数,我只需将它们存储到MySQL User-Defined Variable
这使代码更具可读性,并且您不需要PHP中的任何其他功能:
$sql = "SET @term = :term";
try
{
$stmt = $dbh->prepare($sql);
$stmt->bindValue(":term", "%$term%", PDO::PARAM_STR);
$stmt->execute();
}
catch(PDOException $e)
{
// error handling
}
$sql = "SELECT ... FROM table WHERE name LIKE @term OR number LIKE @term";
try
{
$stmt = $dbh->prepare($sql);
$stmt->execute();
$stmt->fetchAll();
}
catch(PDOException $e)
{
//error handling
}
唯一的缺点可能是您需要进行额外的MySQL查询 - 但这完全是值得的。
由于User-Defined Variables
在MySQL中是会话绑定的,因此也无需担心在多用户环境中导致副作用的变量@term
。
答案 1 :(得分:9)
我创建了两个函数来通过重命名双用术语来解决问题。一个用于重命名SQL,另一个用于重命名绑定。
/**
* Changes double bindings to seperate ones appended with numbers in bindings array
* example: :term will become :term_1, :term_2, .. when used multiple times.
*
* @param string $pstrSql
* @param array $paBindings
* @return array
*/
private function prepareParamtersForMultipleBindings($pstrSql, array $paBindings = array())
{
foreach($paBindings as $lstrBinding => $lmValue)
{
// $lnTermCount= substr_count($pstrSql, ':'.$lstrBinding);
preg_match_all("/:".$lstrBinding."\b/", $pstrSql, $laMatches);
$lnTermCount= (isset($laMatches[0])) ? count($laMatches[0]) : 0;
if($lnTermCount > 1)
{
for($lnIndex = 1; $lnIndex <= $lnTermCount; $lnIndex++)
{
$paBindings[$lstrBinding.'_'.$lnIndex] = $lmValue;
}
unset($paBindings[$lstrBinding]);
}
}
return $paBindings;
}
/**
* Changes double bindings to seperate ones appended with numbers in SQL string
* example: :term will become :term_1, :term_2, .. when used multiple times.
*
* @param string $pstrSql
* @param array $paBindings
* @return string
*/
private function prepareSqlForMultipleBindings($pstrSql, array $paBindings = array())
{
foreach($paBindings as $lstrBinding => $lmValue)
{
// $lnTermCount= substr_count($pstrSql, ':'.$lstrBinding);
preg_match_all("/:".$lstrBinding."\b/", $pstrSql, $laMatches);
$lnTermCount= (isset($laMatches[0])) ? count($laMatches[0]) : 0;
if($lnTermCount > 1)
{
$lnCount= 0;
$pstrSql= preg_replace_callback('(:'.$lstrBinding.'\b)', function($paMatches) use (&$lnCount) {
$lnCount++;
return sprintf("%s_%d", $paMatches[0], $lnCount);
} , $pstrSql, $lnLimit = -1, $lnCount);
}
}
return $pstrSql;
}
使用示例:
$lstrSqlQuery= $this->prepareSqlForMultipleBindings($pstrSqlQuery, $paParameters);
$laParameters= $this->prepareParamtersForMultipleBindings($pstrSqlQuery, $paParameters);
$this->prepare($lstrSqlQuery)->execute($laParameters);
关于变量命名的说明:
p:参数,l:函数中的local
str:string,n:numeric,a:array,m:mixed
答案 2 :(得分:5)
我不知道自问题发布后它是否发生了变化,但现在查看手册,它说:
您不能在预准备语句中多次使用同名的命名参数标记 ,除非仿真模式已启用 。
http://php.net/manual/en/pdo.prepare.php - (强调我的。)
因此,从技术上讲,允许使用$PDO_obj->setAttribute( PDO::ATTR_EMULATE_PREPARES, true );
进行模拟准备也会起作用;虽然这可能不是一个好主意(正如this answer中所讨论的那样,关闭模拟预处理语句是防止某些注入攻击的一种方法;尽管some have written to the contrary它对安全性没有影响,是否模拟了模拟(我不知道,但我不认为后者会考虑前面提到的攻击。)
我为了完整起见而添加了这个答案;当我在我正在处理的网站上关闭emulate_prepares时,它导致搜索中断,因为它使用了类似的查询(SELECT ... FROM tbl WHERE (Field1 LIKE :term OR Field2 LIKE :term) ...
),并且它工作正常,直到我明确设置{{ 1}}到PDO::ATTR_EMULATE_PREPARES
,然后开始失败。
(PHP 5.4.38,MySQL 5.1.73 FWIW)
这个问题让我觉得你不能在同一个查询中使用两次命名参数(这对我来说似乎违反直觉,但是很好)。 (不知怎的,我在手册中错过了,即使我多次查看该页面。)
答案 3 :(得分:2)
工作解决方案:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);
$query = "SELECT * FROM table WHERE name LIKE :term OR number LIKE :term";
$term = "hello world";
$stmt = $pdo->prepare($query);
$stmt->execute(array('term' => "%$term%"));
$data = $stmt->fetchAll();