php pdo готовит повторяющиеся переменные - PullRequest
18 голосов
/ 30 сентября 2011

Можно ли при записи оператора pdo повторить значение переменной? Я имею в виду:

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

Обратите внимание, что я повторяю имя: "name", тогда как я предоставляю значение только один раз. Как я могу сделать эту работу?

Ответы [ 2 ]

21 голосов
/ 30 сентября 2011

Простой ответ: вы не можете. PDO использует абстракцию для подготовленных утверждений, которая имеет некоторые ограничения. К сожалению, это один, вы должны обойти, используя что-то вроде

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

В некоторых случаях, таких как эмулированные подготовленные операторы с некоторыми версиями драйвера PDO / MySQL, поддерживаются повторяющиеся именованные параметры; однако на это не следует полагаться, так как она хрупкая (например, она может сделать обновления более трудоемкими). ​​

Если вы хотите поддерживать множественные появления именованного параметра, вы всегда можете расширить PDO и PDOStatement (по классическому наследованию или по составу) или просто PDOStatement и установить свой класс в качестве класса оператора, установив атрибут PDO::ATTR_STATEMENT_CLASS. Расширенное PDOStatement (или PDO::prepare) может извлекать именованные параметры, искать повторы и автоматически генерировать замены. Было бы также записать эти дубликаты. Методы привязки и выполнения при передаче именованного параметра проверяют, повторяется ли параметр, и связывают значение с каждым параметром замены.

Примечание: следующий пример не проверен и, вероятно, содержит ошибки (некоторые, связанные с разбором операторов, отмечены в комментариях к коду).

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 голосов
/ 28 июня 2017

В моем случае эта ошибка появилась, когда я переключился с dblib freedts на драйвер sqlsrv PDO. Драйвер Dblib обработал дублированные имена параметров без ошибок. У меня довольно сложные динамические запросы с множеством объединений и множеством дублированных параметров, поэтому я использовал следующий помощник в качестве обходного пути:

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];
}

Тогда вы можете использовать это так:

$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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...