php pdo 准备重复变量

2021-12-26 00:00:00 php pdo

在编写 pdo 语句时,是否可以重复变量的值?我的意思是:

While writing a pdo statement, is it possible to repeat the value of a variable? I mean:

$query = "UPDATE users SET firstname = :name WHERE firstname = :name";
$stmt = $dbh -> prepare($query);
$stmt -> execute(array(":name" => "Jackie"));

请注意,我重复了 ":name" 名称,而我只提供了一次该值.我怎样才能做到这一点?

Please note that I repeat the ":name" nameholder whereas I provide the value only once. How can I make this work?

推荐答案

简单的答案是:你不能.PDO 对准备好的语句使用抽象,但有一些限制.不幸的是,这是一个,您必须使用类似

The simple answer is: You can't. PDO uses an abstraction for prepared statements which has some limitations. Unfortunately this is one, you have to work-around using something like

$query = "UPDATE users SET firstname = :name1 WHERE firstname = :name2";
$stmt = $dbh -> prepare($query);
$stmt -> execute(array(":name1" => "Jackie", ":name2" => "Jackie"));

在某些情况下,例如使用某些版本的 PDO/MySQL 驱动程序模拟准备好的语句,支持重复命名参数;但是,不应依赖它,因为它很脆弱(例如,它会使升级需要更多工作).

In certain cases, such as emulated prepared statements with some versions of the PDO/MySQL driver, repeated named parameters are supported; however, this shouldn't be relied upon, as it's brittle (it can make upgrades require more work, for example).

如果你想支持一个命名参数的多次出现,你总是可以扩展 PDO 和 PDOStatement(通过经典继承或通过组合),或者只是 PDOStatement 并通过设置 PDO::ATTR_STATEMENT_CLASS 属性将您的类设置为语句类.扩展的 PDOStatement(或 PDO::prepare)可以提取命名参数,查找重复并自动生成替换.它还会记录这些重复项.bind 和 execute 方法在传递命名参数时会测试该参数是否重复并将值绑定到每个替换参数.

If you want to support multiple appearances of a named parameter, you can always extend PDO and PDOStatement (by classical inheritance or by composition), or just PDOStatement and set your class as the statement class by setting the PDO::ATTR_STATEMENT_CLASS attribute. The extended PDOStatement (or PDO::prepare) could extract the named parameters, look for repeats and automatically generate replacements. It would also record these duplicates. The bind and execute methods, when passed a named parameter, would test whether the parameter is repeated and bind the value to each replacement parameter.

注意:以下示例未经测试,可能存在错误(一些与语句解析相关的在代码注释中注明).

Note: the following example is untested and likely has bugs (some related to statement parsing are noted in code comments).

class PDO_multiNamed extends PDO {
    function prepare($stmt) {
        $params = array_count_values($this->_extractNamedParams());
        # get just named parameters that are repeated
        $repeated = array_filter($params, function ($count) { return $count > 1; });
        # start suffixes at 0
        $suffixes = array_map(function ($x) {return 0;}, $repeated);
        /* Replace repeated named parameters. Doesn't properly parse statement,
         * so may replacement portions of the string that it shouldn't. Proper
         * implementation left as an exercise for the reader.
         *
         * $param only contains identifier characters, so no need to escape it
         */
        $stmt = preg_replace_callback(
            '/(?:' . implode('|', array_keys($repeated)) . ')(?=W)/', 
            function ($matches) use (&$suffixes) {
                return $matches[0] . '_' . $suffixes[$matches[0]]++;
            }, $stmt);
        $this->prepare($stmt, 
                       array(
                           PDO::ATTR_STATEMENT_CLASS => array('PDOStatement_multiNamed', array($repeated)))
            );
    }

    protected function _extractNamedParams() {
        /* Not actually sufficient to parse named parameters, but it's a start.
         * Proper implementation left as an exercise.
         */
        preg_match_all('/:w+/', $stmt, $params);
        return $params[0];
    }
}

class PDOStatement_multiNamed extends PDOStatement {
    protected $_namedRepeats;

    function __construct($repeated) {
        # PDOStatement::__construct doesn't like to be called.
        //parent::__construct();
        $this->_namedRepeats = $repeated;
    }

    /* 0 may not be an appropriate default for $length, but an examination of
     * ext/pdo/pdo_stmt.c suggests it should work. Alternatively, leave off the
     * last two arguments and rely on PHP's implicit variadic function feature.
     */
    function bindParam($param, &$var, $data_type=PDO::PARAM_STR, $length=0, $driver_options=array()) {
        return $this->_bind(__FUNCTION__, $param, func_get_args());
    }

    function bindValue($param, $var, $data_type=PDO::PARAM_STR) {
        return $this->_bind(__FUNCTION__, $param, func_get_args());
    }

    function execute($input_parameters=NULL) {
        if ($input_parameters) {
            $params = array();
            # could be replaced by array_map_concat, if it existed
            foreach ($input_parameters as $name => $val) {
                if (isset($this->_namedRepeats[$param])) {
                    for ($i=0; $i < $this->_namedRepeats[$param], ++$i) {
                        $params["{$name}_{$i}"] = $val;
                    }
                } else {
                    $params[$name] = $val;
                }
            }
            return parent::execute($params);
        } else {
            return parent::execute();
        }
    }

    protected function _bind($method, $param, $args) {
        if (isset($this->_namedRepeats[$param])) {
            $result = TRUE;
            for ($i=0; $i < $this->_namedRepeats[$param], ++$i) {
                $args[0] = "{$param}_{$i}";
                # should this return early if the call fails?
                $result &= call_user_func_array("parent::$method", $args);
            }
            return $result;
        } else {
            return call_user_func_array("parent::$method", $args);
        }
    }
}

相关文章