2

WHERE文字列の配列に基づいて、クエリで「動的」句を作成したいと考えています。そして、Mysqi の準備済みステートメントを使用して、作成したクエリを実行したいと考えています。

これまでの私のコード、PHP:

$searchArray = explode(' ', $search);
$searchNumber = count($searchArray);
$searchStr = "tags.tag LIKE ? ";
for($i=1; $i<=$searchNumber-1 ;$i++){
    $searchStr .= "OR tags.tag LIKE ? ";
}

私のクエリ:

SELECT tag FROM tags WHERE $searchStr;

その他の PHP:

$stmt -> bind_param(str_repeat('s', count($searchArray)));

bind_param 部分には必要な詳細の半分しか含まれていないため、明らかにエラーが発生します。

どのように進めればよいですか?

これを行う他の(より良い)方法はありますか?

安全ですか?

4

3 に答える 3

3

質問のセキュリティ部分に関しては、プレースホルダーを使用した準備済みステートメントは、これらのプレースホルダーに値を入力する際の検証メカニズムと同じくらい安全です。mysqli の準備済みステートメントの場合、ドキュメントには次のように記載されています。

マーカーは、SQL ステートメントの特定の場所でのみ有効です。たとえば、INSERT ステートメントの VALUES() リストで (行の列の値を指定するために)、または WHERE 句の列との比較で比較値を指定することができます。

ただし、識別子 (テーブル名や列名など)、SELECT ステートメントによって返される列を指定する選択リスト、または = 等号などの二項演算子の両方のオペランドを指定することはできません。後者の制限が必要なのは、パラメーターの型を判別できないためです。? によってマーカーを NULL と比較することはできません。IS NULL も。一般に、パラメーターはデータ操作言語 (DML) ステートメントでのみ有効であり、データ定義言語 (DDL) ステートメントでは有効ではありません。

これにより、クエリの一般的なセマンティックを変更する可能性が明らかに排除されます。これにより、クエリを元の意図からそらすことがはるかに困難になります (ただし、不可能ではありません)。

クエリの動的部分に関してはstr_repeat、ループを実行する代わりに、クエリ条件構築部分で使用できます。

 $searchStr = 'WHERE tags.tag LIKE ?' . 
                        str_repeat($searchNumber - 1, ' OR tags.tag LIKE ?');

bind_param呼び出しには、次のように使用する必要がありますcall_user_func_array

$bindArray[0] = str_repeat('s', $searchNumber);
array_walk($searchArray,function($k,&$v) use (&$bindArray) {$bindArray[] = &$v;});
call_user_func_array(array($stmt,'bind_param'), $bindArray);

うまくいけば、上記のスニペットは、のすべての値を$bindArrayクエリ内の対応するプレースホルダーにバインドする必要があります。


補遺:

ただし、次の 2 つの点に注意する必要があります。

  • call_user_func_arrayは、2 番目のパラメーターに整数のインデックス付き配列を想定しています。辞書でどのように動作するかわかりません。
  • mysqli_stmt_bind_param パラメータを参照渡しする必要があります。

最初の点については、 が整数インデックスを使用していることを確認するだけで済みます$bindArray。これは上記のコードの場合です (または、call_user_func_array提供している配列で がチョークしないことを確認してください)。

2 番目の点については、呼び出し$bindArray bind_param(つまり、call_user_func_array関数を介して)、クエリを実行する前にデータを変更する場合にのみ問題になります。これを行う場合 - たとえば、同じスクリプトで異なるパラメーターの値を使用して同じクエリを数回実行する場合$bindArray、次のクエリの実行に同じ配列 ( ) を使用し、次のクエリを使用して配列エントリを更新する必要があります。同じキー。手動で行わない限り、別の配列をコピーしても機能しません。

foreach($bindArray as $k => $v)
    $bindArray[$k] = some_new_value();

また

foreach($bindArray as &$v)
    $v = some_new_value();

bind_param上記は、以前に呼び出されたときにステートメントにバインドされた配列エントリの参照を壊さないため、機能します。同様に、以前に設定された参照が変更されないため、以下は機能するはずです。

array_walk($bindArray, function($k,&$v){$v = some_new_value();});
于 2013-01-11T00:21:39.340 に答える
1

準備済みステートメントには、明確に定義された数の引数が必要です。動的機能の要素を持つことはできません。つまり、必要な特定のステートメントを生成し、それを準備する必要があります。

データベース接続の存在中にコードが実際に複数回呼び出された場合にできることは、これらの準備されたステートメントのキャッシュを作成し、使用している引数の数でそれらにインデックスを付けることです。これは、3 つの引数を指定して関数を 2 回目に呼び出したときに、ステートメントが既に実行されていることを意味します。ただし、準備されたステートメントはいずれにしても切断に耐えられないため、これは、同じスクリプト実行で複数のクエリを実行する場合にのみ意味があります。(永続的な接続は意図的に除外しています。これは、まったく別のワームの可能性を開くためです。)

ところで、私は MySQL の専門家ではありませんが、where 条件を結合せずに書いても違いはありませんWHERE tags in (tag1, tag2, tag3, tag4)か?

于 2013-01-10T22:37:30.543 に答える
1

ここにある回答の助けを借りて解決しました。

$query = "SELECT * FROM tags WHERE tags.tag LIKE CONCAT('%',?,'%')" . str_repeat(" OR tags.tag LIKE CONCAT('%',?,'%')", $searchNumber - 1)

$stmt = $mysqli -> prepare($query);
$bind_names[] = str_repeat('s', $searchNumber);

for ($i = 0; $i < count($searchArray); $i++){
   $bind_name = 'bind'.$i; //generate a name for variable bind1, bind2, bind3...
   $$bind_name = $searchArray[$i]; //create a variable with this name and put value in it
   $bind_names[] = & $$bind_name; //put a link to this variable in array
}

call_user_func_array(array($stmt, 'bind_param'), &$bind_names);

$stmt -> execute();
于 2013-01-11T20:09:14.220 に答える