Я пытаюсь реализовать довольно базовую поисковую систему для моей базы данных, где пользователь может включать различные виды информации. Сам поиск состоит из пары соединений, в которых результаты всегда объединяются в 3 столбца.
Однако возвращаемые данные извлекаются из разных таблиц.
Каждый запрос использует $ term для matchmaking, и я привязал его к «: term» в качестве подготовленного параметра.
Теперь в руководстве написано:
Вы должны включить уникальный маркер параметра для каждого значения, которое вы хотите передать в оператор, когда вы вызываете PDOStatement :: execute (). Вы не можете использовать маркер именованного параметра с тем же именем дважды в подготовленном операторе.
Я решил, что вместо замены каждого параметра term: termX (x для term = n ++) должно быть лучшее решение?
Или мне просто нужно связать 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 ) )
Я несколько раз сталкивался с той же проблемой, и я думаю, что нашел довольно простое и хорошее решение. В случае, если я хочу использовать параметры несколько раз, я просто храню их в 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, но imho это того стоит.
Поскольку User-Defined Variables
связаны с сессией в MySQL, также нет необходимости беспокоиться о переменной @term
вызывающей побочные эффекты в многопользовательских средах.
Я создал две функции для решения проблемы путем переименования двойных используемых терминов. Один для переименования 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; }
не/** * 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: string, n: числовой, a: array, m: mixed
Я не знаю, изменилось ли это с момента публикации вопроса, но, проверяя руководство сейчас, он говорит:
Вы не можете использовать один и тот же параметр маркера одного и того же имени более одного раза в подготовленном операторе, если не включен режим эмуляции .
http://php.net/manual/en/pdo.prepare.php – (Акцент мой).
Итак, технически, позволяя эмулировать готовит с использованием $PDO_obj->setAttribute( PDO::ATTR_EMULATE_PREPARES, true );
тоже будет работать; хотя это может и не быть хорошей идеей (как обсуждалось в этом ответе , выключение эмулируемых подготовленных заявлений является одним из способов защиты от определенных инъекционных атак, хотя некоторые из них писали об обратном, что не имеет никакого значения для безопасности, подготавливаются ли подготавливаемые или нет (Я не знаю, но я не думаю, что последний имел в виду вышеупомянутую атаку).
Я добавляю этот ответ ради полноты; поскольку я отключил emulate_prepares на сайте, над которым я работаю, и это вызвало поиск break, поскольку он использовал аналогичный запрос ( SELECT ... FROM tbl WHERE (Field1 LIKE :term OR Field2 LIKE :term) ...
), и он работал нормально, пока я явно не установил значение PDO::ATTR_EMULATE_PREPARES
в false
, после чего он начал PDO::ATTR_EMULATE_PREPARES
с PDO::ATTR_EMULATE_PREPARES
.
(PHP 5.4.38, MySQL 5.1.73 FWIW)
Этот вопрос подсказывает мне, что вы не можете использовать именованный параметр дважды в одном запросе (что кажется мне неинтересным, но хорошо). (Как-то я пропустил это в руководстве, хотя много раз смотрел на эту страницу).
Рабочее решение:
$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();
Определенные пользователем переменные – один из способов перехода и использование одной и той же переменной несколько раз для привязки значений к запросам, и да, это хорошо работает.
//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); } }
И вот простое рабочее решение!
Надеюсь, это поможет кому-то в ближайшем будущем.