【问题标题】:How to properly escape CSV file before import in SQL using PHP如何在使用 PHP 导入 SQL 之前正确转义 CSV 文件
【发布时间】:2019-03-02 21:24:17
【问题描述】:

我必须建立报告网站,使用从另一个系统导出的 csv 文件导入 SQL 数据。

我构建了代码,但无法继续执行,要导入 SQL,我只有消息 QUERY FAILED,不是 SQL 错误或与数据库连接。

有人可以查看我的代码并写信告诉我我的错误在哪里吗?我用户对数据进行了转义,因为我收到了类似..."John's company":

的数据
<?php

if(isset($_POST['import'])) {

$filename = $_FILES['file']['tmp_name'];

if($_FILES['file']['size'] > 0 ) {
    $file = fopen($filename, 'r');

    while (($data = fgetcsv($file, 10000, ",")) !== FALSE ) {
        $data = array_map('mysqli_real_escape_string', $data);
        $query = "INSERT INTO purchasing ";
        $query .= "(
            purchase_req, 
            pr_date,  
            pr_item,  
            pr_created_by,  
            po_created_by,  
            purch_group,  
            purchase_ord,  
            po_date,  
            po_item,  
            po_state,  
            vendor_numb,  
            vendor_name,  
            mat_desc,  
            gl_acc,  
            cost_cent,  
            po_qty,  
            po_del_date,  
            po_delivered_qty,  
            po_to_be_del
        ) ";
        $query .= "VALUES ( 
            '" . $data[0] . "',  
            '" . $data[1] . "', 
            '" . $data[2] . "',  
            '" . $data[3] . "',  
            '" . $data[4] . "',  
            '" . $data[5] . "',  
            '" . $data[6] . "',  
            '" . $data[7] . "',  
            '" . $data[8] . "',  
            '" . $data[9] . "',  
            '" . $data[10] . "',  
            '" . $data[11] . "',  
            '" . $data[12] . "',  
            '" . $data[13] . "',  
            '" . $data[14] . "',  
            '" . $data[15] . "',  
            '" . $data[16] . "',  
            '" . $data[17] . "',  
            '" . $data[18] . "' 
        ) ";

        $send_to_database = mysqli_query($conn, $query);

        if(!$send_to_database) {
            die("QUERY FAILED <br>" . mysqli_error($conn));
        } else {
            header("Location: index.php");
        }
    }
    fclose($file);
}
}

?>

/// 建议后更新代码

<?php

if(isset($_POST['import'])) {

$filename = $_FILES['file']['tmp_name'];

if($_FILES['file']['size'] > 0 ) {
    $file = fopen($filename, 'r');

    $conn = new mysqli("localhost", "####", "####", "####");

    if($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }

$stmt = $mysqli->prepare("INSERT INTO purchasing (purchase_req, pr_date, pr_item, pr_created_by, po_created_by, purch_group, purchase_ord, po_date, po_item, po_state, vendor_numb, vendor_name, mat_desc, gl_acc, cost_cent, po_qty, po_del_date, po_delivered_qty, po_to_be_del) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ");

    fgetcsv($file, 10000, ",");
    while(($data = fgetcsv($file, 10000, ",")) !== FALSE) {
        $data = fgetcsv($file);

        $stmt->bind_param("sssssssssssssssssss", $data[0], $data[1], $data[2], $data[3], $data[4], $data[5], $data[6], $data[7], $data[8], $data[9], $data[10], $data[11], $data[12], $data[13], $data[14], $data[15], $data[16], $data[17], $data[18]);

        $stmt->execute();
    }
    fclose($file); 
}
}

?>

在第二种情况下,我打印了数组,但我再次无法将其发送到 SQL...

【问题讨论】:

  • 不要逃避它,而是准备它。现在您对文件中的 SQL 注入持开放态度,这种情况很容易发生。事实上,这个错误. i user escape on data because i have come data like... "John's company" 正在执行 sql 注入,这就是您的查询被破坏的原因。您的查询类似于 ..."VALUES ('John's company', ...) 看看 apos 是如何完成第一个单引号 VALUES ('John' [s company'], ...) 然后您有 s company' 只是在那里不寒而栗。
  • 那是因为您将' 注入到您的 SQL 中,您可以通过简单地使用准备好的语句来避免所有这些以及更多......我会发布一些代码,但可能有一个网络上有十亿个例子怎么做。
  • 你说“准备”是什么意思,我理解你的观点,因为我的 csv 中的数据类型有问题,但我该如何防止呢?如何在 SQL 中处理这些数据?你能写下我的一行代码吗?
  • php.net/manual/en/mysqli.prepare.php Prepares the SQL query, and returns a statement handle to be used for further operations on the statement. The query must consist of a single SQL statement. The parameter markers must be bound to application variables using mysqli_stmt_bind_param() and/or mysqli_stmt_bind_result() before executing the statement or fetching rows. 基本上不是将数据连接到 sql 中,而是使用占位符,然后稍后发送数据。 VALUES ( ?, ?, ?, ?)
  • 因为您的 sql VALUES ( ?, ?, ?, ?) 中不会有任何单引号,所以您不必担心转义它们,因为 DB 知道这是 SQL,当您使用时这是 DATA准备它。 Could you please wrote my one line of code? - 不,只是谷歌“MySqli Prepared statements”之类的。

标签: php mysql sql


【解决方案1】:

在修改后的代码中,您使用了准备和绑定参数,但您错过了准备好的语句的好处之一:您可以准备一次并使用不同的值执行多次。

顺便说一句,当你绑定时,你需要一个控制字符串,其字符数与参数个数一样多。在这种情况下,您需要一个长度为 19 的字符串。

$stmt = $mysqli->prepare("INSERT INTO purchasing (purchase_req, pr_date, pr_item, 
  pr_created_by, po_created_by, purch_group, purchase_ord, po_date, po_item, po_state, 
  vendor_numb, vendor_name, mat_desc, gl_acc, cost_cent, po_qty, po_del_date, 
  po_delivered_qty, po_to_be_del) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ");

while(($data = fgetcsv($file, 10000, ",")) !== FALSE) {
    $data = fgetcsv($file);

    $stmt->bind_param("sssssssssssssssssss", $data[0], $data[1], $data[2], $data[3], $data[4], $data[5], 
     $data[6], $data[7], $data[8], $data[9], $data[10], $data[11], $data[12],
     $data[13], $data[14], $data[15], $data[16], $data[17], $data[18]);

    $stmt->execute();
}

这样效率更高一点,因为 MySQL 不必为 CSV 输入的每一行重新解析 SQL 语句。

提示:我更喜欢使用 PDO 而不是 mysqli,因为它有一个更好的界面来使用准备好的语句。您不必绑定任何变量。如果您将数据数组作为参数传递给execute(),PDO 会为您执行此操作:

$stmt = $pdo->prepare("INSERT INTO purchasing (purchase_req, pr_date, pr_item, 
  pr_created_by, po_created_by, purch_group, purchase_ord, po_date, po_item, po_state, 
  vendor_numb, vendor_name, mat_desc, gl_acc, cost_cent, po_qty, po_del_date, 
  po_delivered_qty, po_to_be_del) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ");

while(($data = fgetcsv($file, 10000, ",")) !== FALSE) {
    $data = fgetcsv($file);
    $stmt->execute($data);
}

我经常疑惑为什么更多的 PHP 开发人员不使用 PDO。

PDO 被认为是异类而名不副实,因为早在 PHP 5.0 时代,它就没有包含在默认的 PHP 安装中。但是从 PHP 5.1 开始,PDO 就成为了 PHP 官方发行版的一部分。奇怪的是,即使在 15 年后,开发人员都避免使用它!


您需要养成做的一件事是检查错误。在 PHP 和其他语言中也是如此。

如果出现错误,Mysqli 函数将返回 FALSE。然后您应该记录错误,以便您可以将其用于故障排除。

像这样:

$stmt = $mysqli->prepare(...);
if ($stmt === false) {
    error_log($mysqli->error);
    die("Internal error");
}

$ok = $stmt->execute();
if ($ok === false) {
    error_log($stmt->error);
    die("Internal error");
}

您需要在每次调用prepare()execute() 后检查错误。这很痛苦,否则你怎么知道出了什么问题?

在您开发和测试代码时,保持打开一个窗口跟踪您的 http 错误日志,这样您就可以看到记录的错误。

向用户输出一般错误消息是一种很好的做法,因为您不想向用户透露太多有关您的代码的信息。但在错误日志中记录更多技术信息,以便您自行排除故障。

【讨论】:

  • 我是 PHP 新手,所以我还在学习,但我也会阅读 PDO,谢谢您的建议!顺便说一句,我更改了代码,但它仍然无法正常工作,我不知道我的错误在哪里,我现在有服务器错误 500... 代码已更新
  • "PHP 致命错误:调用一个成员函数 prepare() on null in" my upload.php 文件...第 16 行...此行包含我的 $stmt = $mysqli->prepare代码行
  • 抱歉,您的连接位于名为 $conn 的变量中。您的第二个代码示例同时具有$conn$mysqli。您只需要使用其中一个,无论是您使用mysqli_connect() 建立的连接的变量。变量的名称无关紧要,您只需使用为连接创建的变量即可。
  • 而我的$pdo-&gt;prepare() 示例是假设之前已经创建了一个PDO 连接并将其存储在变量$pdo 中。此处记录了创建 PDO 连接:php.net/manual/en/pdo.construct.php 同样,变量的名称由您决定。
  • 这不能太容易...我必须学习更多:) 非常感谢!
【解决方案2】:

为什么不简单地使用MySQL LOAD DATA INFILE syntax 来一次加载文件,只需要一个 SQL 查询?

这个 MySQL 插件是一种加载 CSV 文件的有效方法,同时防止 SQL 注入和处理引用问题。它还避免了使用 php.ini 打开和循环文件。根据MySQL documentation

从文本文件加载表格时,使用LOAD DATA。这通常比使用 INSERT 语句快 20 倍。

这应该很简单:

LOAD DATA INFILE '$filename'
    INTO TABLE purchasing(
        purchase_req, 
        pr_date, pr_item, 
        pr_created_by, 
        po_created_by, 
        purch_group, 
        purchase_ord, 
        po_date, 
        po_item, 
        po_state, 
        vendor_numb, 
        vendor_name, 
        mat_desc, 
        gl_acc, 
        cost_cent, 
        po_qty, 
        po_del_date, 
        po_delivered_qty, 
        po_to_be_del
    )
    FIELDS TERMINATED BY ',' 
    OPTIONALLY ENCLOSED BY '"'
    LINES TERMINATED BY '\r\n' -- or maybe '\n'?
;

注意:OPTIONALLY ENCLOSED BY '"' 选项应该处理像"John's company" 这样的字段值。 LOAD DATA INFILE 有更多有用的选项,如上述链接文档中所述。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-19
    • 1970-01-01
    • 1970-01-01
    • 2018-10-29
    • 2021-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多