準備された動的ステートメントは悪いですか? (php + mysqliを使用)
-
03-07-2019 - |
質問
動的SQLの柔軟性が好きで、プリペアドステートメントのセキュリティ+パフォーマンスの向上が好きです。したがって、私が本当に欲しいのは、動的準備済みステートメントです。これは、bind_paramとbind_resultが「修正済み」を受け入れるため、作成するのが面倒です。引数の数。そこで、この問題を回避するためにeval()ステートメントを使用しました。しかし、これは悪い考えだと感じています。ここに私が意味するもののサンプルコードがあります
// array of WHERE conditions
$param = array('customer_id'=>1, 'qty'=>'2');
$stmt = $mysqli->stmt_init();
$types = ''; $bindParam = array(); $where = ''; $count = 0;
// build the dynamic sql and param bind conditions
foreach($param as $key=>$val)
{
$types .= 'i';
$bindParam[] = '$p'.$count.'=$param["'.$key.'"]';
$where .= "$key = ? AND ";
$count++;
}
// prepare the query -- SELECT * FROM t1 WHERE customer_id = ? AND qty = ?
$sql = "SELECT * FROM t1 WHERE ".substr($where, 0, strlen($where)-4);
$stmt->prepare($sql);
// assemble the bind_param command
$command = '$stmt->bind_param($types, '.implode(', ', $bindParam).');';
// evaluate the command -- $stmt->bind_param($types,$p0=$param["customer_id"],$p1=$param["qty"]);
eval($command);
その最後のeval()ステートメントは悪い考えですか?変数名$ paramの後ろに値をカプセル化することで、コードインジェクションを回避しようとしました。
意見やその他の提案はありますか?知っておく必要がある問題はありますか?
解決
ここで eval()
を使用するのは危険だと思います。
これを試してください:
- params配列を繰り返し、疑問符
" SELECT * FROM t1 WHERE p1 =? AND p2 =?"
- その上で
prepare()
を呼び出します -
call_user_func_array()
を使用してbind_param()
を呼び出し、動的なparams配列を渡します。
コード:
call_user_func_array(array($stmt, 'bind_param'), array($types)+$param);
他のヒント
配列を受け取るフィルター関数を$ _GETのような連想配列にしました:
モデルクラスでは、スキーマを含むいくつかのプロパティを定義しました:
private $table_name = "products";
protected $schema = [
'id' => 'INT',
'name' => 'STR',
'description' => 'STR',
'size' => 'STR',
'cost' => 'INT',
'active' => 'BOOL'
];
次に、条件の連想配列を受け取るフィルターメソッド:
function filter($conditions)
{
$vars = array_keys($conditions);
$values = array_values($conditions);
$where = '';
foreach($vars as $ix => $var){
$where .= "$var = :$var AND ";
}
$where =trim(substr($where, 0, strrpos( $where, 'AND ')));
$q = "SELECT * FROM {$this->table_name} WHERE $where";
$st = $this->conn->prepare($q);
foreach($values as $ix => $val){
$st->bindValue(":{$vars[$ix]}", $val, constant("PDO::PARAM_{$this->schema[$vars[$ix]]}"));
}
$st->execute();
return $st->fetchAll(PDO::FETCH_ASSOC);
}
結果のフィルタリングに最適です
mysql_real_escape_string()を常に使用できるため、準備されたステートメントとバインドされた引数は本当に必要ありません。そしてあなたは正しい。動的に生成されたSQLは、はるかに柔軟で価値があります。
通常のmysql_ *インターフェースを使用した簡単な例:
// Array of WHERE conditions
$conds = array("customer_id" => 1, "qty" => 2);
$wherec = array("1");
foreach ($conds as $col=>$val) $wherec[] = sprintf("`%s` = '%s'", $col, mysql_real_escape_string($val));
$result_set = mysql_query("SELECT * FROM t1 WHERE " . implode(" AND ", $wherec);
もちろん、これは単純な例であり、便利にするためには多くのことを構築して洗練する必要がありますが、アイデアを示しており、非常に非常に便利です。たとえば、新しい行を任意のテーブルに挿入するための完全に汎用的な関数を次に示します。列には連想配列からの値が入力され、完全にSQLインジェクションに対して安全です。
function insert($table, $record) {
$cols = array();
$vals = array();
foreach (array_keys($record) as $col) $cols[] = sprintf("`%s`", $col);
foreach (array_values($record) as $val) $vals[] = sprintf("'%s'", mysql_real_escape_string($val));
mysql_query(sprintf("INSERT INTO `%s`(%s) VALUES(%s)", $table, implode(", ", $cols), implode(", ", $vals)));
}
// Use as follows:
insert("customer", array("customer_id" => 15, "qty" => 86));