При написании инструкции pdo можно ли повторить значение переменной? Я имею в виду:
$query = "UPDATE users SET firstname = :name WHERE firstname = :name"; $stmt = $dbh -> prepare($query); $stmt -> execute(array(":name" => "Jackie"));
Обратите внимание, что я повторяю имя «: name», тогда как я предоставляю значение только один раз. Как я могу сделать эту работу?
Простой ответ: вы не можете. 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
) может извлекать именованные параметры, искать повторы и автоматически генерировать замены. Он также записывал эти дубликаты. Методы bind и execute при передаче именованного параметра будут проверять, повторяется ли параметр и привязывать значение к каждому параметру замены.
Примечание: следующий пример непроверен и, вероятно, содержит ошибки (некоторые из них связаны с разбором операторов отмечены в комментариях кода).
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); } } }
В моем случае эта ошибка появилась, когда я переключился с dblib freedts на драйвер sqlrv 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);