多次使用绑定参数

Dan*_*iel 31 php mysql sql pdo

我正在尝试为我的数据库实现一个非常基本的搜索引擎,其中用户可能包含不同类型的信息.搜索本身由几个联合选择组成,其中结果总是合并为3列.

然而,返回的数据是从不同的表中获取的.

每个查询使用$ term进行匹配,我将它作为准备参数绑定到":term".

现在,手册说:

调用PDOStatement :: execute()时,必须为要传递给语句的每个值包含唯一的参数标记.您不能在预准备语句中两次使用同名的命名参数标记.

我想,不是用termex替换每个: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);
Run Code Online (Sandbox Code Playgroud)

好的,这是一个样本.我没有时间使用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
        )
)
Run Code Online (Sandbox Code Playgroud)

low*_*nts 19

我现在已经遇到过几次同样的问题了,我想我已经找到了一个非常简单和好的解决方案.如果我想多次使用参数,我只是将它们存储到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
}
Run Code Online (Sandbox Code Playgroud)

唯一的缺点可能是你需要做一个额外的MySQL查询 - 但是这完全是值得的.
由于User-Defined Variables在MySQL中是会话绑定的,因此也无需担心@term导致多用户环境中的副作用的变量.


pas*_*ert 10

我创建了两个函数来通过重命名双重术语来解决问题.一个用于重命名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;
    }
Run Code Online (Sandbox Code Playgroud)

用法示例:

$lstrSqlQuery= $this->prepareSqlForMultipleBindings($pstrSqlQuery, $paParameters);
$laParameters= $this->prepareParamtersForMultipleBindings($pstrSqlQuery, $paParameters);
$this->prepare($lstrSqlQuery)->execute($laParameters);
Run Code Online (Sandbox Code Playgroud)

关于变量命名的说明:
p:参数,l:函数
str中的local :string,n:numeric,a:array,m:mixed


Aar*_*ine 7

我不知道问题发布后是否有变化,但现在检查手册,它说:

除非启用了仿真模式,否则不能在预准备语句中多次使用同名的命名参数标记.

http://php.net/manual/en/pdo.prepare.php - (强调我的.)

因此,从技术上讲,允许使用模拟准备$PDO_obj->setAttribute( PDO::ATTR_EMULATE_PREPARES, true );也会起作用; 虽然这可能不是一个好主意(正如在这个答案中所讨论的那样,关闭模拟预处理语句是防止某些注入攻击的一种方法;尽管有些人反映说,无论是否模拟准备,它对安全性没有任何影响(我不知道,但我不认为后者会考虑前面提到的攻击.)

为了完整起见,我正在添加这个答案; 当我转身emulate_prepares关闭该网站上我工作,和它造成搜索打破,因为它是使用类似的查询(SELECT ... FROM tbl WHERE (Field1 LIKE :term OR Field2 LIKE :term) ...),它工作正常,直到我明确设置PDO::ATTR_EMULATE_PREPARESfalse,然后它开始失败.

(PHP 5.4.38,MySQL 5.1.73 FWIW)

这个问题让我觉得你不能在同一个查询中两次使用命名参数(这对我来说似乎违反直觉,但是很好).(不知怎的,我在手册中错过了,即使我多次查看该页面.)

  • “这个答案”实际上和另一个答案说的是一样的。如果你一开始就跳过所有令人反感的东西,最后它会说,只要你使用受支持的 PHP 版本并通过 DSN 设置字符集,模拟模式就没有已知的漏洞。 (2认同)