37

ユーザーがさまざまな種類の情報を含めることができるデータベースに、かなり基本的な検索エンジンを実装しようとしています。検索自体は、結果が常に 3 つの列にマージされる 2 つのユニオン選択で構成されます。

ただし、返されるデータは別のテーブルからフェッチされています。

各クエリはマッチメイキングに $term を使用し、準備済みパラメーターとして ":term" にバインドしました。

さて、マニュアルには次のように書かれています。

PDOStatement::execute() を呼び出すときに、ステートメントに渡す値ごとに一意のパラメーター マーカーを含める必要があります。準備済みステートメントで、同じ名前の名前付きパラメーター マーカーを 2 回使用することはできません。

各 :term パラメータを :termX (x for 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
        )
)
4

5 に答える 5

25

私は同じ問題を数回繰り返しましたが、かなりシンプルで良い解決策を見つけたと思います。パラメータを複数回使用したい場合は、それらを 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 クエリを実行する必要があることかもしれませんが、それだけの価値はあります。
MySQL ではセッションにバインドされているため、変数がマルチユーザー環境で副作用を引き起こすことUser-Defined Variablesを心配する必要もありません。@term

于 2015-06-26T08:52:17.293 に答える
9

質問が投稿されてから変更されたかどうかはわかりませんが、今マニュアルを確認すると、次のように書かれています。

エミュレーション モードが on でない限り、準備済みステートメントで同じ名前の名前付きパラメーター マーカーを複数回使用することはできません。

http://php.net/manual/en/pdo.prepare.php -- (強調は私のものです。)

したがって、技術的には、エミュレートされた準備を使用して許可すること$PDO_obj->setAttribute( PDO::ATTR_EMULATE_PREPARES, true );も機能します。それは良い考えではないかもしれませんが (この回答で説明されているように、エミュレートされた準備済みステートメントをオフにすることは、特定のインジェクション攻撃から保護する 1 つの方法です。逆に、準備がエミュレートされているかどうかにかかわらず、セキュリティに違いはないと書いている人もいます。 (わかりませんが、後者は前者の攻撃を念頭に置いていたとは思いません。)

完全を期すために、この回答を追加しています。私が取り組んでいるサイトで emulate_prepares をオフにすると、同様のクエリ ( 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)

この質問は、同じクエリで名前付きパラメーターを 2 回使用できないことを教えてくれました (これは直感に反するように思えますが、まあまあです)。(マニュアルのページを何度も見たのに、どういうわけかそれを見逃していました。)

于 2016-02-13T03:05:07.977 に答える
3

実用的なソリューション:

$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();
于 2013-08-29T13:55:08.393 に答える
0

ユーザー定義変数は、値をクエリにバインドするときに同じ変数を複数回使用して使用する1つの方法であり、うまく機能します。

//Setting this doesn't work at all, I tested it myself 
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);

ここに投稿されたソリューションの1つのように、ユーザー定義変数をまったく使用したくありませんでした。ここに投稿された他のソリューションのように、パラメーターの名前変更も行いたくありませんでした。したがって、これは、ユーザー定義変数を使用せずに、クエリ内の名前を変更することなく、より少ないコードで機能する私のソリューションであり、クエリでパラメーターが使用される回数を気にしません。私はこれをすべてのプロジェクトで使用していますが、うまく機能しています。

//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);
    }
}

そして、ここに簡単な実用的なソリューションがあります!

これが近い将来誰かに役立つことを願っています。

于 2015-11-23T17:58:40.437 に答える