正如我在对 Edson 的回答的评论中提到的,我希望在最后一行代码出现“标头已发送”警告:
header('Content-Length: '.$streamSize);
因为输出是在发送此标头之前写入的,但他的示例工作正常。
一些调查使我得出以下结论:
在您使用输出缓冲区(无论是用户缓冲区还是默认的 PHP 缓冲区)时,您可以按照您想要的方式发送 HTTP 标头和内容。您知道任何协议都需要在正文之前发送标头(因此称为“标头”),但是当您使用输出缓冲层时,PHP 会为您处理这些。任何使用输出标头(header()、setcookie()、session_start())的 PHP 函数实际上都将使用内部 sapi_header_op() 函数,该函数只是填充标头缓冲区。然后,当您使用 printf() 写入输出时,它会写入输出缓冲区(假设为一个)。当要发送输出缓冲区时,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();
但是,您仍然受到其他限制。