【问题标题】:Why PHP continue to output to CSV file after fclose()?为什么 PHP 在 fclose() 之后继续输出到 CSV 文件?
【发布时间】:2014-04-14 09:34:06
【问题描述】:

在网页上,我正在使用以下代码将一些数据写入 CSV 文件,最后使用 fclose(); 关闭

header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename='.$filename);
$out = fopen('php://output', 'w');
fputcsv($out, $cvs_cols);
fclose($out);
echo "HELLO WORLD"; // sneaks into CSV!?

为什么“HELLO WORLD”已经进入了 CSV 下载文件,但它已经有 fclose()?我想输出要在浏览器中显示的页面的其余 HTML。我该怎么做?

【问题讨论】:

  • "HELLO WORLD" 实际上是我想要发送到浏览器的 HTML,但就像我说的,它们进入了 CVS 文件。
  • 虽然您还没有重新路由输出,并且由于正在执行的 PHP 代码仍在构建文档,所以 echo 的内容将附加到 CSV。
  • 它不会继续输出到 CSV,但是因为您正在将 csv 写入 php://output 并且回显也转到 php://output,所以这两个要同一个地方
  • fclose() 为文件写入函数关闭文件句柄,如fwrite()、fputcsv()等....但是php://output 也是用于回显、打印等的标准输出流......这相当于必须为同一个文件提供文件句柄,而您只需关闭其中一个
  • 您不能在同一请求中同时发送 HTML 响应 CSV(或任何其他文件类型)响应

标签: php fclose


【解决方案1】:

这里有一个非常老的线程,但为了解决这个问题,我只添加了一个简单的 exit();命令。因此,一个按钮使用“action=export_csv”的查询字符串调用同一页面,然后该操作使用 exit() 运行;最后一行,希望对您有所帮助。

<a href="?action=export_csv">Export CSV</a>

那么页面上的'action'就是:

if(isset($_GET['action']) && $_GET['action']=='export_csv'){
    // output headers so that the file is downloaded rather than displayed
    header('Content-Type: text/csv; charset=utf-8');
    header('Content-Disposition: attachment; filename=email-responses.csv');

    // create a file pointer connected to the output stream
    $output = fopen('php://output', 'w');

    // output the column headings
    fputcsv($output, array('Email address'));

    $db = new PDO('mysql:host=hostname_mysql;dbname=database_mysql;charset=UTF8', username_mysql, password_mysql);
    $query = "SELECT XXX FROM XXXX";
    $result = $db->query($query);
    $data = $result->fetchAll(PDO::FETCH_ASSOC);
    // loop over the rows, outputting them
    foreach($data as $row){
        fputcsv($output, $row);
    }
    fclose($output);
    exit();
}

【讨论】:

    【解决方案2】:

    在 1 个 HTTP 请求之后跟随 1 个响应。您不能同时发送内容类型 text/csv 和内容类型 text/htmlSPDY 可能是,但纯 HTTP 则不行)。

    fclose 关闭您的文件描述符,但不关闭浏览器的输出。

    您还应该设置一个Content-Length 标头并放入文件大小。

    Mark Ba​​ker 已经给出了 cmets 中最重要的一点:

    echo 和写入php://output 会将内容放入同一流中:STDOUT。其他选项是将 CSV 写入内存(但如果你不使用它就毫无意义)或文件。阅读有关这些流的更多信息:http://www.php.net/manual/en/features.commandline.io-streams.php

    可能的解决方案:

    您需要 2 个 HTTP 请求。 1 用于下载,另一个用于您的 HTML。最流行的方法是首先使用 HTML 响应并在其中添加类似

    <meta http-equiv="refresh"
          content="3; URL=http://yourserver.com/download.php?id=pdf&amp;id=123" />
    

    这将在 3 秒后开始下载。

    【讨论】:

    • 谢谢,很好的链接和解释。刚开始没有意识到只有一个输出流,现在我可以清楚地看到了。
    【解决方案3】:

    还没有“CSV 文件”。

    您正在做的是向客户端发送一个数据流,并告诉客户端该流具有text/csvContent-Type$filenamefilename。然后,客户端可以选择将其保存为 CSV 文件或仅在浏览器中显示。

    这段代码:

    $out = fopen('php://output', 'w');
    fputcsv($out, $cvs_cols);
    fclose($out);
    

    正在有效地做 echo $cvs_cols 会做的事情(用一些额外的东西来格式化 csv 输出)。

    因此,当调用 echo "HELLO WORLD"; 时,它会以与 $cvs_cols 变量的内容相同的数据流发送。

    当您调用fopen('php://output', 'w') 时,您正在创建php://output 的第二个文件句柄,因为默认情况下会创建一个以从调用echo 等输出。所以当您调用fclose($out) 时,您只是关闭第二个文件句柄。

    【讨论】:

      【解决方案4】:

      使用

      ob_clean() : ob_clean — 清理(擦除)输出缓冲区

      flush() : flush - 刷新输出缓冲区(flush)

      ob_start();
      
      header('Content-Type: text/csv; charset=utf-8');
      header('Content-Disposition: attachment; filename='.$filename);
      $out = fopen('php://output', 'w');
      fputcsv($out, $cvs_cols);
      fclose($out);
      
      ob_end_clean();   //    the buffer and never prints or returns anything.
      
      
      echo "HELLO WORLD"; // sneaks into CSV!?
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-08-26
        • 2020-08-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多