将准备好的语句包装在函数中


Wrapping a prepared statement in a function

我一直在阅读有关SQL注入的文章,并决定修改我的代码以防止SQL注入。

例如,我有一个输入,我将值插入数据库。最初,我对注射的防范是这样的:

function test_input($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data);
    // $data = addslashes($data);
    $data = mysql_real_escape_string($data);
    return $data;
}
$artist = $_POST["artist"];     // can be anything
$artist = test_input($artist);  // escaped chars are &, quotes, <, >, 'n, 'r, etc.
if ($mysqli->query("SELECT * FROM `my_table` WHERE `artist` = '$artist'")->num_rows == 0) {
    $mysqli->query("INSERT INTO my_table (artist) VALUES ('$artist')");
    echo "New artist is added.";
} else {
    echo "Artist already exists.";
}

在我读过的文章中,有人建议应该使用准备好的语句。我已经改变了我的代码,并使用:

$artist = $_POST["artist"]; // can be anything
$query = $mysqli->prepare("SELECT * FROM my_table WHERE artist = ?");
$query->bind_param("s", $artist);
$query->execute();
$result = $query->get_result();
$query->close();
if ($result->num_rows == 0) {
    echo "Artist doesn't exist in the DB." . PHP_EOL;
    $query = $mysqli->prepare("INSERT INTO my_table (artist) VALUES (?)");
    $query->bind_param("s", $artist);
    $query->execute();
    if ($query->affected_rows > 0) {
        echo "Artist is added to the DB." . PHP_EOL;
    }
    $query->close();
} else {
    echo "Artist already exists in the DB." . PHP_EOL;
}

虽然这可以防止SQL注入,但它对XSS没有任何作用。所以我决定修改test_input(删除$data = mysql_real_escape_string($data);),并使用它来防止脚本注入。

function test_input($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data);
    return $data;
}
$artist = $_POST["artist"]; // can be anything
$artist = test_input($artist);

现在,我的问题是关于使用预处理语句。我将插入三个项目;艺人、专辑和歌曲。一遍又一遍地重复同样的过程(准备、绑定、执行、关闭)对我来说似乎是多余的。我想创建一个函数,并用它包装准备好的语句过程。像这样:

function p_statement($mysqli, $query_string = "", $type = "", $vars = []) {
    $query = $mysqli->prepare($query_string);
    $query->bind_param($type, $vars);
    $query->execute();
    $result = null;
    preg_match("/^[A-Z]+/", $query_string, $command);
    switch ($command[0]) {
        case "SELECT":
            $result = $query->get_result();
            break;
        case "INSERT":
            $result = $query->affected_rows;
            break;
    }
    $query->close();
    return $result;
}

虽然,这提出了一个问题:$vars数组。由于传递给mysqli_stmt::bind_param()的变量数量将是可变的/动态的,因此我在主函数p_statement中使用了一个数组。我不知道我应该如何将数组中的项目传递给mysqli_stmt::bind_param()bind_param期望(type, var1, var2, varn,),我得到了一个数组。

我怎样才能使它工作?

你要找的是implode()

查看手册,它显示了关于使用call_user_func_array的提示。我编辑了一些你的片段。

function p_statement($mysqli, $query_string = "", $type = "", $vars = []) {
    $query = $mysqli->prepare($query_string);
    //assign $type to first index of $vars
    array_unshift($vars, $type);
    //Turn all values into reference since call_user_func_array
    //expects arguments of bind_param to be references
    //@see mysqli::bind_param() manpage
    foreach ($vars as $key => $value) {
        $vars[$key] =& $vars[$key];
    }
    call_user_func_array(array($query, 'bind_param'), $vars);
    $query->execute();
    //INSERT, SELECT, UPDATE and DELETE have each 6 chars, you can
    //validate it using substr() below for better and faster performance
    if (strtolower(substr($query_string, 0, 6)) == "select") {
        $result = $query->get_result();
    } else {
        $result = $query->affected_rows;
    }
    $query->close();
    return $result;
}

我已经找到了使用call_user_func_array来解决问题的方法。

function p_statement($mysqli, $query_string = "", $type = "", $vars = []) {
    $query = $mysqli->prepare($query_string);
    // create an empty array
    $parameters = array();
    // push the type string into the array by reference
    $parameters[] = & $type;
    // push the items from $vars array into the array by reference
    for ($i = 0; $i < count($vars); $i++) {
        $parameters[] = & $vars[$i];
    }
    // call mysqli_stmt::bind_param with the $parameters array, which contains [type, var1, var2, ...]
    call_user_func_array(array($query, "bind_param"), $parameters);
    $query->execute();
    $result = null;
    preg_match("/^[A-Z]+/", $query_string, $command);
    switch ($command[0]) {
        case "SELECT":
            $result = $query->get_result();
            break;
        case "INSERT":
        case "UPDATE":
        case "DELETE":
            $result = $query->affected_rows;
            break;
    }
    $query->close();
    return $result;
}

$artist = "3 Doors Down";
$year   = 2000;
$artist_select = p_statement($mysqli, "SELECT * FROM albums WHERE artist = ? AND year = ?", "si", [$artist, $year]);
var_dump($artist_select->fetch_all(MYSQLI_ASSOC));

输出:

array(1) {
  [0]=>
  array(3) {
    ["album"]=>
    string(15) "The Better Life"
    ["year"]=>
    int(2000)
    ["artist"]=>
    string(12) "3 Doors Down"
  }
}