PHP 和 MySQL:预准备语句与用于安全性和编码速度的函数



这实际上更像是一个"最佳实践"的问题。

目前正在编写一个网站后端的代码,并且已经听到强烈建议使用mysqli编写的语句,所以一直在这么做。

然而,在过去,我也被告知"更聪明而不是更努力地工作"这句格言,当我从HTML表单中获得数据时,每个输入的名称都是它们的字段名称,并且处理后的数据在一个具有相同字段名称的数组中,我现在遇到了一点困惑。

我有这样的代码,我过去曾用它将数据从数组中放入数据库,更新为使用mysqli->escape_string()而不是mysql_real_eescape_string():

function create_insert_from_array($array, $table) {
  global $mysqli;
  $part_one = ""; $part_two = "";
  foreach ($array as $field => $input) {
    $part_one .= "`" . $mysqli->escape_string($field) . "`, ";
    $part_two .= "'" . $mysqli->escape_string($input) . "', ";
  }
  $to_ret = "INSERT INTO `" . $table . "` (" . substr($part_one, 0, -2) . ") VALUES ("
          . substr($part_two, 0, -2) . ");";
  return $to_ret;
} 

这允许我放入数组和它要进入的表,然后弹出一条SQL语句。

我的一个表的SQL Prepared Statement方法使用以下代码:

$stmt = $mysqli->prepare("INSERT INTO aio_sched_programmes
                          VALUES ('', ?, ?, ?, ?, ?, ?, ?, UNIX_TIMESTAMP(), ?, ?, '', '', '');");
$stmt->bind_param('sssssssis',
                    $end_data['name'],
                    $url_slug,
                    $end_data['long_desc'],
                    $end_data['short_desc'],
                    $end_data['image_url'],
                    $end_data['twitter'],
                    $end_data['facebook'],
                    $_SESSION['user_id'],
                    $user_profile['name']    );
if ($stmt->execute()) {
  // DO SOME STUFF AFTER CODE EXECUTION
}

我现在遇到的问题是,对于那个特别准备好的语句,所有字段都是必需的。然而,在我正在处理的表中,并不是所有的都存在,而且在我的数组中,有些不会存在,因此在我的函数创建语句时会被忽略。但是,对于准备好的语句,所有字段都需要放入语句中,并且可能有空格。

我已经意识到(主要是从这个网站上!)准备好的语句更安全,但我的函数将节省大量代码,因为我可以直接调用它,而不必写出准备好的声明。

那么,使用哪一种更好,或者有什么方法可以将两者结合起来,实现两全其美呢?

一旦外部数据涉及到语句中,就应该尽可能使用prepared语句。由于在INSERT INTO中,未提及的字段被设置为默认值,因此您也可以在PHP中提供这些值。将一个具有默认值的数组和参数化SQL放在一起。当您必须插入缺少条目的数据时,请将其合并到默认值中。

<?php
function store_data(Array &$data)
{
  $default_data =
  [
    'street'  => null,
    'city'    => null,
    'credits' => 0
  ];
  $dat = array_merge($default_data, $data);
  var_dump($dat);
}
$data = 
[
  'city' => 'New York'
];
store_data($data);
?>

输出:

array (size=3)
  'street' => null
  'city' => string 'New York' (length=8)
  'credits' => int 0

正如你在评论中所说:

如果在执行查询时不知道列,则不应依赖http请求输入。使用SHOW FULL COLUMNS FROM `tablename` FROM `db`;可以获取名称、类型、默认值等信息。那些绳子更安全,但不"安全"。您仍然需要对这些字符串进行替换,因为它们在法律上可能包含特殊字符。要获取表名,请使用SHOW TABLE FROM `db`;

prepare语句的整个思想是保留一个"数据库查询"模板,将其发送一次到数据库,然后只发送参数。在bindParam定义中可以找到(http://php.net/manual/pl/pdostatement.bindparam.php)值作为对变量的引用传递,这样您就可以执行以下操作:

$stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) 
VALUES (:firstname, :lastname, :email)");
$stmt->bindParam(':firstname', $firstname);
$stmt->bindParam(':lastname', $lastname);
$stmt->bindParam(':email', $email);
// insert a row
$firstname = "John";
$lastname = "Doe";
$email = "john@example.com";
$stmt->execute();
// insert another row
$firstname = "Mary";
$lastname = "Moe";
$email = "mary@example.com";
$stmt->execute();

(示例来自:http://www.w3schools.com/php/php_mysql_prepared_statements.asp)。

它可能会稍微简化代码,但大型应用程序中的imho会造成更大的混乱。

准备好的语句很好的一点是,只创建一次整个查询,而不是字符串串联(这确实很容易出错)。最常见的问题之一是,当您连接字符串并且变量为空时,您会得到以下内容:

INSERT INTO table ...... VALUES (1, 12, , 'ABC');

MySQL会出现语法错误(因为,,)。当人们进行查询字符串连接时,这是一个非常常见的问题。

另一个很棒的功能是,当您将参数绑定到查询时,您可以指定参数类型(我强烈建议使用它)。使用这种方法,您可以轻松地支持任何数据库,因为例如,如果列position的类型为int,并且您推送一个字符串:

INSERT INTO `myTable` (id, position) VALUES (1, '1');

MySQL将执行该命令,PostgreSQL将失败,因为您试图将string分配给integer。指定类型还可以解决所有SQL注入问题。我看到在第一个例子中,您正在转义字符串(非常好),但很容易忘记这个小东西。若有人从你们身边经过,你们已经逃脱了那个根绳子怎么办??

IMHO创建数据库查询的最佳方式是-使用准备好的语句(性能更好),不易出错-键入每个查询参数

漂亮的代码示例(从php.net网站窃取):

<?php
/* Execute a prepared statement by binding PHP variables */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour');
$sth->bindParam(':calories', $calories, PDO::PARAM_INT);
$sth->bindParam(':colour', $colour, PDO::PARAM_STR, 12);
$sth->execute();
?>

还有一个关于代码的小提示,而不是使用foreach创建字符串,然后删除join()函数的最后一个参数。

最新更新