Подтвердить что ты не робот

Использовать связанный параметр несколько раз

Я пытаюсь реализовать довольно базовую поисковую систему для моей базы данных, где пользователь может включать различные виды информации. Сам поиск состоит из пары союзников, где результаты всегда объединяются в 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
        )
)
4b9b3361

Ответ 1

Я уже несколько раз сталкивался с той же проблемой, и я думаю, что нашел довольно простое и хорошее решение. В случае, если я хочу использовать параметры несколько раз, я просто храню их в 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-запрос, но имхо это стоит того. Поскольку User-Defined Variables связаны с сеансом в MySQL, также нет необходимости беспокоиться о переменной @term, вызывающей побочные эффекты в многопользовательских средах.

Ответ 2

Я создал две функции для решения проблемы путем переименования двойных используемых терминов. Один для переименования 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;
    }

Пример использования:

$lstrSqlQuery= $this->prepareSqlForMultipleBindings($pstrSqlQuery, $paParameters);
$laParameters= $this->prepareParamtersForMultipleBindings($pstrSqlQuery, $paParameters);
$this->prepare($lstrSqlQuery)->execute($laParameters);

Объяснение о названии переменной:
p: параметр, l: локальный в функции
str: string, n: числовые, a: array, m: mixed

Ответ 3

Я не знаю, изменилось ли это с момента публикации вопроса, но теперь, проверяя руководство, он говорит:

Вы не можете использовать маркер именованного параметра с тем же именем более одного раза в подготовленном операторе , если режим эмуляции не включен.

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, то он начал сбой.

(PHP 5.4.38, MySQL 5.1.73 FWIW)

Этот вопрос подсказывает мне, что вы не можете использовать именованный параметр дважды в одном запросе (что кажется мне неинтересным, но хорошо). (Как-то я пропустил это в руководстве, хотя я много раз смотрел на эту страницу.)

Ответ 4

Рабочее решение:

$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();

Ответ 5

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

//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 bind the value to the param
        $statement->bindValue($param_name, $param_value);
    }
}

И вот простое рабочее решение!

Надеюсь, это поможет кому-то в ближайшем будущем.