写入PHP输出缓冲区,然后从缓冲区下载CSV

时间:2013-04-02 16:49:09

标签: php csv

我需要将一个CSV文件写入PHP输出缓冲区,然后在写完文件后将该文件下载到客户端的计算机上。 (我想把它写在服务器上并下载它正常工作,但事实证明我在生产服务器上没有写访问权限。)

我有以下PHP脚本:

$basic_info = fopen("php://output", 'w');
$basic_header = array(HEADER_ITEMS_IN_HERE);

@fputcsv($basic_info, $basic_header);

while($user_row = $get_users_stmt->fetch(PDO::FETCH_ASSOC)) {
    @fputcsv($basic_info, $user_row);
}

@fclose($basic_info);

header('Content-Description: File Transfer');
header('Content-Type: application/csv');
header('Content-Disposition: attachment; filename=test.csv');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize("php://output"));
ob_clean();
flush();
readfile("php://output");

我不知道该怎么做。 CSV文件下载但不显示任何内容。我认为它与我的ob_clean()和flush()命令的顺序有关,但我不确定订购这些东西的最佳方法是什么。

感谢任何帮助。

3 个答案:

答案 0 :(得分:8)

你做得太多了。创建脚本的唯一目的是输出CSV。只需将其直接打印到屏幕上即可。不要担心标题或缓冲区或php://输出或类似的东西。

一旦确认您正确地将数据打印到屏幕上,只需在开头添加这些标题:

<?php
header("Content-disposition: attachment; filename=test.csv");
header("Content-Type: text/csv");
?>

...确认正确下载文件。然后你可以添加其他标题如果你喜欢(我上面包含的标题是我自己使用的标题,没有任何额外的工作,其他基本上是效率和缓存控制,其中一些可能已经适当处理由您的服务器,可能或可能不重要您的特定应用程序。)

如果需要,可以使用ob_start()ob_get_clean()的输出缓冲来将输出内容转换为字符串,然后您可以使用该字符串填写Content-Length

答案 1 :(得分:3)

正如我在Edson的回答评论中所提到的,我期望在最后一行代码中出现“已发送标题”警告:

header('Content-Length: '.$streamSize);

因为输出是在发送此标头之前写入的,但是他的示例工作正常。

一些调查让我得出以下结论:

  

当你使用输出缓冲区时(无论是用户还是用户)   默认PHP一),您可以像您一样发送HTTP标头和内容   想。您知道任何协议都需要在正文之前发送标头   (因此术语“标题”),但是当你使用输出缓冲层时,PHP   会为你照顾这个。任何播放输出的PHP函数   header(header(),setcookie(),session_start())实际上会使用   内部sapi_header_op()函数,它只填充标题   缓冲。然后当你写输出时,使用say printf(),它写入   输出缓冲区(假设一个)。 输出缓冲区时   发送,PHP首先开始发送标题,然后是正文。 PHP   为你照顾一切。如果你不喜欢这种行为,你   除了禁用任何输出缓冲层之外别无选择。

  

大多数配置下PHP缓冲区的默认大小为4096   字节(4KB),这意味着PHP缓冲区可以容纳最多4KB的数据。 一旦这个   超出限制或PHP代码执行完成,缓冲内容   自动发送到任何后端使用PHP(CGI,   mod_php,FastCGI)。 PHP-CLI中的输出缓冲始终为Off。

Edson的代码有效,因为输出缓冲区没有自动刷新,因为它没有超过缓冲区大小(并且在发送最后一个标头之前脚本没有明显终止)。

一旦输出缓冲区中的数据超过缓冲区大小,就会引发警告。或者在他的例子中,当数据

$get_users_stmt->fetch(PDO::FETCH_ASSOC)

太大了。

为了防止这种情况,您应该使用ob_start()和ob_end_flush()来自行管理输出缓冲;如下所示:

// Turn on output buffering
ob_start();

// Define handle to output stream
$basic_info   = fopen("php://output", 'w');

// Define and write header row to csv output 
$basic_header = array('Header1', 'Header2');  
fputcsv($basic_info, $basic_header);  

$count = 0; // Auxiliary variable to write csv header in a different way

// Get data for remaining rows and write this rows to csv output
while($user_row = $get_users_stmt->fetch(PDO::FETCH_ASSOC)) {  
    if ($count == 0) {
        // Write select column's names as CSV header
        fputcsv($basic_info, array_keys($user_row));  
    } else {
        //Write data row
        fputcsv($basic_info, $user_row);
    }
    $count++;
}  

// Get size of output after last output data sent  
$streamSize = ob_get_length();

//Close the filepointer
fclose($basic_info);  

// Send the raw HTTP headers
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename=test.csv');  
header('Expires: 0');  
header('Cache-Control: no-cache');
header('Content-Length: '. ob_get_length());

// Flush (send) the output buffer and turn off output buffering
ob_end_flush();

但你仍然受其他限制的约束。

答案 2 :(得分:0)

我对你的代码进行了一些调整。

  • 按照PHP's doc;
  • 的建议,在任何输出之前移动标题
  

请记住,在任何实际输出之前必须调用header()   通过普通HTML标记,文件中的空行或PHP

发送
  • 删除了一些没有太大变化的标题;
  • 使用select列的名称编写了另一个编写csv头的选项;
  • 现在内容长度有效;
  • 没有必要回显$ basic_info,因为它已经在输出缓冲区,我们通过标题将其重定向到文件中;
  • 删除@(PHP错误控制操作符),因为它可能会导致开销,没有现在显示的链接,但如果您搜索,可能会找到它。在沉默错误之前你应该三思而后行,大多数次应该修复而不是沉默。
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename=test.csv');  
header('Expires: 0');  
header('Cache-Control: no-cache'); 

$basic_info   = fopen("php://output", 'w');  
$basic_header = array(HEADER_ITEMS_IN_HERE);  

fputcsv($basic_info, $basic_header);  
$count = 0; // auxiliary variable to write csv header in a different way

while($user_row = $get_users_stmt->fetch(PDO::FETCH_ASSOC)) {  
    // Write select column's names as CSV header
    if ($count == 0) {
        fputcsv($basic_info, array_keys($user_row));  
    }

    fputcsv($basic_info, $user_row);  
    $count++;
}  

// get size of output after last output data sent  
$streamSize = ob_get_length(); 
fclose($basic_info);  

header('Content-Length: '.$streamSize);