【问题标题】:Use bound parameter multiple times多次使用绑定参数
【发布时间】:2013-09-01 22:16:37
【问题描述】:

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

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

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

现在,手册说:

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

我认为与其用 :termX (x for term = n++) 替换每个 :term 参数,还不如有更好的解决方案?

还是我只需要绑定 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
        )
)

【问题讨论】:

  • 首先为什么必须有更好的解决方案? (顺便说一句。您忘记提供具体的 better 在您的情况下意味着什么)和次要的,为什么未命名的参数对您不起作用? (请参阅示例 #2 准备带有问号参数的 SQL 语句 php.net/pdo.prepare
  • @hakre 未命名参数带来了同样的问题,因为绑定值必须与?? 一样多。在我的情况下,更好的解决方案是 -&gt;bindValue(':term', $term) 并多次使用 :term 而不是首先构建查询,然后解析它以最终能够准备它。我猜? 只会使解析最终查询变得更加困难,因为还有其他参数类型。
  • 那么,为什么必须有更好的解决方案?因为你想要?然后,我觉得您的问题与寻找场外资源或图书馆无关。我没有看到底层的编程问题。对不起。
  • @hakre 你是真的还是什么?潜在的编程问题是 API 可能缺少一个关键特性。这就是我想要弄清楚的。我对扩展 PDO 没有任何问题,但我觉得没有必要重新发明轮子。如您所见,我已经解决了它,但觉得必须有更好的方法。
  • 如果您有 PDO 的功能请求,请随时为此打开一个问题,但我怀疑这是一个关键功能。关键特性实际上是提供绑定参数,仅此而已。您正在寻找的可能是一个可以更轻松地制定准备好的语句的库,例如 SqlExpression 类或包装/构成/表示准备好的语句的类。而且我看不到您解决了它,代码在哪里?到目前为止,您已经概述了您的要求,但这并不是一个编程问题恕我直言。缺失的特征可能会产生很大的偏差。

标签: php mysql sql pdo


【解决方案1】:

我现在已经多次遇到同样的问题,我认为我找到了一个非常简单且很好的解决方案。如果我想多次使用参数,我只需将它们存储到 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 在多用户环境中导致副作用。

【讨论】:

  • 这看起来真的很不错,而且它可以是一个非常简洁的功能。
  • @Daniel 谢谢。我很高兴我自己提出了这个想法。
【解决方案2】:

我创建了两个函数来通过重命名重复使用的术语来解决问题。一种用于重命名 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:函数中的局部
str:字符串,n:数字,a:数组,m:混合

【讨论】:

  • 这段代码对我有用。代码 sn-p 应该是任何人的库的标准部分,以使 PDO 的使用更容易和更灵活。谢谢!
  • 谢谢朋友,我同意!
  • 今天看到这个问题,很抱歉没有回复......这也是我今天正在做的事情。我会将此标记为解决方案。
  • 比使用 UDV 的公认答案更简洁的解决方案,恕我直言。
  • 这不是一个完美的解决方案。想象一下,您的查询中有一个硬编码值(我的意思是未绑定):description = "Airport:terminal C"。在这里,您将替换字符串中不正确的“:term”。
【解决方案3】:

我不知道自从问题发布以来它是否发生了变化,但是现在查看手册,它说:

您不能在准备好的语句中多次使用同名的命名参数标记,除非仿真模式开启

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

因此,从技术上讲,允许使用 $PDO_obj-&gt;setAttribute( PDO::ATTR_EMULATE_PREPARES, true ); 进行模拟准备也可以;尽管这可能不是一个好主意(正如this answer 中所讨论的,关闭模拟的prepared statements 是防止某些注入攻击的一种方法;尽管some have written to the contrary 认为prepare 是否被模拟对安全性没有影响。(我不知道,但我认为后者没有考虑到前者的攻击。)

为了完整起见,我添加了这个答案;当我在我正在处理的网站上关闭 emulate_prepares 时,它导致搜索中断,因为它使用了类似的查询 (SELECT ... FROM tbl WHERE (Field1 LIKE :term OR Field2 LIKE :term) ...),并且它工作正常,直到我明确将 PDO::ATTR_EMULATE_PREPARES 设置为 @987654327 @,然后它开始失败。

(PHP 5.4.38,MySQL 5.1.73 FWIW)

这个问题告诉我你不能在同一个查询中两次使用一个命名参数(这对我来说似乎违反直觉,但是哦)。 (不知何故,尽管我多次查看该页面,但我还是错过了手册中的内容。)

【讨论】:

  • “这个答案”实际上和另一个一样。如果你一开始就跳过所有丑闻,最后它说只要你使用受支持的 PHP 版本并通过 DSN 设置字符集,就没有已知的仿真模式漏洞。
  • 顺便说一句,不知何故,您也错过了有关此页面上此类设置的其他答案。
【解决方案4】:

一个可行的解决方案:

$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();

【讨论】:

  • 我将对此进行测试,但我不得不问:如果在两个 :term 之间使用 :flag 怎么办?如果 execute() 相应地处理参数,我会将您的问题标记为已解决。
  • 如果你也测试一下看看呢?
  • @N.B.哦,当然,你可以在查询中多写两倍的代码来避免包装变量。
  • 好吧,这仍然不适用于使用无序参数的查询。我得到HY093Invalid parameter number: parameter was not defined。如果:term 是唯一的参数,这将起作用 - 但不幸的是我有更多参数。
  • @N.B.这不是我想说的那么微不足道。在这些闲逛的人中,只有少数人明白这个把戏,它的本质和陷阱。
【解决方案5】:

用户定义的变量是一种在将值绑定到查询时多次使用同一个变量的方法,是的,效果很好。

//Setting this doesn't work at all, I tested it myself 
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);

我根本不想使用用户定义的变量,就像此处发布的解决方案之一一样。 我也不想像此处发布的其他解决方案一样进行参数重命名。因此,这是我的解决方案,无需使用用户定义的变量,也无需使用更少的代码重命名查询中的任何内容,并且它不关心参数在查询中使用了多少次。我在我的所有项目中都使用它,效果很好。

//Example values
var $query = "select * from test_table where param_name_1 = :parameter and param_name_2 = :parameter";
var param_name = ":parameter";
var param_value = "value";

//Wrap these lines of codes in a function as needed sending 3 params $query, $param_name and $param_value. 
//You can also use an array as I do!

//Lets check if the param is defined in the query
if (strpos($query, $param_name) !== false)
{
    //Get the number of times the param appears in the query
    $ocurrences = substr_count($query, $param_name);
    //Loop the number of times the param is defined and bind the param value as many times needed
    for ($i = 0; $i < $ocurrences; $i++) 
    {
        //Let's bind the value to the param
        $statement->bindValue($param_name, $param_value);
    }
}

这是一个简单的工作解决方案!

希望这在不久的将来对某人有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-05-13
    • 2017-02-13
    • 2020-04-28
    • 1970-01-01
    • 2015-10-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多