php pdo prepare repetitive variables

前端 未结 2 744
小蘑菇
小蘑菇 2020-11-27 07:50

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 = :nam         


        
相关标签:
2条回答
  • 2020-11-27 07:56

    In my case this error appeared when I switched from dblib freedts to sqlsrv PDO driver. Dblib driver handled duplicate parameters names with no errors. I have quite complicated dynamic queries with lots of unions and a lot of duplicated params so I used following helper as a workaround:

    function prepareMsSqlQueryParams($query, $params): array
    {
        $paramsCount = [];
        $newParams = [];
        $pattern = '/(:' . implode('|:', array_keys($params)) . ')/';
    
        $query = preg_replace_callback($pattern, function ($matches) use ($params, &$newParams, &$paramsCount) {
            $key = ltrim($matches[0], ':');
            if (isset($paramsCount[$key])) {
                $paramsCount[$key]++;
                $newParams[$key . $paramsCount[$key]] = $params[$key];
                return $matches[0] . $paramsCount[$key];
            } else {
                $newParams[$key] = $params[$key];
                $paramsCount[$key] = 0;
                return $matches[0];
            }
        }, $query);
    
        return [$query, $newParams];
    }
    

    Then you can use it this way:

    $query = "UPDATE users SET firstname = :name WHERE firstname = :name";
    $params = [":name" => "Jackie"];
    // It will return "UPDATE users SET firstname = :name WHERE firstname = :name1"; with appropriate parameters array
    list($query, $params) = prepareMsSqlQueryParams($query, $params);
    $stmt = $dbh->prepare($query);
    $stmt->execute(params);
    
    0 讨论(0)
  • 2020-11-27 08:15

    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"));
    

    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).

    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);
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题