繁体   English   中英

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

[英]Write to PHP output buffer and then download CSV from buffer

我需要将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()命令的顺序有关,但是我不确定什么是订购这些东西的最佳方法。

任何帮助表示赞赏。

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

确认将数据正确打印到屏幕上之后,只需在开头添加以下标头:

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

...确认已正确下载文件。 然后,您可以根据需要添加其他标头(上面包含的标头是我自己使用的标头,没有任何多余的工作来完成此工作,其他标头主要是为了提高效率和缓存控制,其中某些标头可能已经适当处理了)取决于您的服务器,对于您的特定应用可能不重要)。

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

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

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

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

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

在使用输出缓冲区(用户一个或默认的PHP一个)时,您可以按所需方式发送HTTP标头和内容。 您知道任何协议都需要在正文之前发送标头(因此称为“标头”),但是当您使用输出缓冲层时,PHP会为您解决这一问题。 实际上,任何使用输出标题(header(),setcookie(),session_start())的PHP函数都将使用内部sapi_header_op()函数,该函数仅填充标题缓冲区。 然后,使用诸如printf()写入输出时,它将写入输出缓冲区(假设为1)。 当要发送输出缓冲区时,PHP首先开始发送标头,然后发送正文。 PHP为您处理一切。 如果您不喜欢这种行为,则除了禁用任何输出缓冲层外别无选择。

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

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();

但是,您仍然受其他限制的约束。

我对您的代码做了一些调整。

  • PHP doc所建议,将标头移到任何输出之前;

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

  • 删除了一些没有太大变化的标题;
  • 评论了使用选择列名称编写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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM