写入 PHP 输出缓冲区,然后从缓冲区下载 CSV
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15769732/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Write to PHP output buffer and then download CSV from buffer
提问by Jon Rubins
I need to write a CSV file to the PHP output buffer and then download that file to the client's computer after it's done writing. (I wanted to just write it on the server and download it which was working, but it turns out I won't have write access on production servers).
我需要将一个 CSV 文件写入 PHP 输出缓冲区,然后在写入完成后将该文件下载到客户端的计算机。(我只想将它写在服务器上并下载它正在运行,但事实证明我在生产服务器上没有写访问权限)。
I have the following PHP script:
我有以下 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");
I'm not sure what to do. The CSV file downloads but displays nothing. I assume it has something to do with the ordering of my ob_clean() and flush() commands, but I'm not sure what's the best way to order these things.
我不知道该怎么做。CSV 文件下载但不显示任何内容。我认为它与我的 ob_clean() 和 flush() 命令的顺序有关,但我不确定订购这些东西的最佳方式是什么。
Any help is appreciated.
任何帮助表示赞赏。
回答by Jason
You're doing a little too much. Create the script with the sole purpose of outputting the CSV. Just print it out directly to the screen. Don't worry about headers or buffers or php://output or anything like that yet.
你做的有点太多了。创建脚本的唯一目的是输出 CSV。只需将其直接打印到屏幕上即可。不要担心标题或缓冲区或 php://output 或类似的东西。
Once you've confirmed that you're printing the data out to the screen appropriately, just add these headers at the beginning:
一旦您确认将数据正确打印到屏幕上,只需在开头添加这些标题:
<?php
header("Content-disposition: attachment; filename=test.csv");
header("Content-Type: text/csv");
?>
... confirm that that downloads the file appropriately. Then you can add the other headers if you like (the headers I included above are those I've used myself without any extra cruft to get this working, the others are basically for efficiency and cache control, some of which may already be handled appropriately by your server, and may or may not be important for your particular application).
...确认正确下载文件。然后,您可以根据需要添加其他标头(我上面包含的标头是我自己使用的标头,没有任何额外的麻烦来使其正常工作,其他标头基本上是为了效率和缓存控制,其中一些可能已经得到适当处理由您的服务器决定,并且对于您的特定应用程序可能重要也可能不重要)。
If you want, use output buffering with ob_start()
and ob_get_clean()
to get the output contents into a string which you can then use to fill out Content-Length
.
如果需要,可以使用输出缓冲ob_start()
和ob_get_clean()
将输出内容放入一个字符串中,然后您可以使用该字符串来填写Content-Length
.
回答by DigiLive
As mentioned in my comments of Edson's answer, I expected a "headers already sent" warning at the last line of code:
正如我在对 Edson 回答的评论中提到的,我希望在最后一行代码中出现“标题已经发送”警告:
header('Content-Length: '.$streamSize);
since output is written before this header is sent, but his example works ok.
由于输出是在发送此标头之前写入的,但他的示例工作正常。
Some investigation leads me to to following conclusions:
一些调查使我得出以下结论:
At the time you use an output buffer (weither a user one, or the default PHP one), you may send HTTP headers and content the way you want. You know that any protocol require to send headers before body (thus the term "header"), but when you use an ouput buffer layer, PHP will take care of this for you. Any PHP function playing with output headers (header(), setcookie(), session_start()) will in fact use the internal sapi_header_op() function which just fills in the headers buffer. When you then write output, using say printf(), it writes into the output buffer (assuming one). When the output buffer is to be sent, PHP starts sending the headers first, and then the body. PHP takes care of everything for you.If you dont like this behavior, you have no other choice than disabling any output buffer layer.
当您使用输出缓冲区(无论是用户缓冲区还是默认的 PHP 缓冲区)时,您可以按照自己的方式发送 HTTP 标头和内容。您知道任何协议都需要在正文之前发送标头(因此称为“标头”),但是当您使用输出缓冲层时,PHP 会为您处理这些。任何处理输出标头(header()、setcookie()、session_start())的 PHP 函数实际上都将使用内部 sapi_header_op() 函数,它只是填充标头缓冲区。当您然后使用 printf() 写入输出时,它会写入输出缓冲区(假设为一个)。当要发送输出缓冲区时,PHP 开始首先发送标头,然后是正文。PHP 会为您处理一切。如果您不喜欢这种行为,除了禁用任何输出缓冲层外,您别无选择。
and
和
The default size of the PHP buffer under most configurations is 4096 bytes (4KB) which means PHP buffers can hold data up to 4KB. Once this limit is exceeded or PHP code execution is finished, buffered content is automatically sentto whatever back end PHP is being used (CGI, mod_php, FastCGI). Output buffering is always Off in PHP-CLI.
大多数配置下 PHP 缓冲区的默认大小为 4096 字节 (4KB),这意味着 PHP 缓冲区最多可以容纳 4KB 的数据。一旦超出此限制或 PHP 代码执行完成,缓冲的内容将自动发送到正在使用的任何后端 PHP(CGI、mod_php、FastCGI)。PHP-CLI 中的输出缓冲总是关闭的。
Edson's code works because the output buffer did not automatically get flushed because it doesn't exceed the buffer size (and the script isn't terminated obviously before the last header is sent).
Edson 的代码有效,因为输出缓冲区没有自动刷新,因为它没有超过缓冲区大小(并且在发送最后一个标头之前脚本没有明显终止)。
As soon as the data in the output buffer exceeds the buffer size, the warning will be raised. Or in his example, when the data of
一旦输出缓冲区中的数据超过缓冲区大小,就会引发警告。或者在他的例子中,当数据
$get_users_stmt->fetch(PDO::FETCH_ASSOC)
is too large.
太大了。
To prevent this, you should manage the output buffering yourself with the ob_start() and ob_end_flush(); like below:
为了防止这种情况,您应该使用 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();
You're still bound to other limits, though.
不过,您仍然受到其他限制的约束。
回答by Edson Horacio Junior
I did some tweaks to your code.
我对你的代码做了一些调整。
- Moved headers before any output, as suggested by PHP's doc;
- 按照PHP 文档的建议,在任何输出之前移动标题;
Remember that header() must be called before any actual output is sent, either by normal HTML tags, blank lines in a file, or from PHP
请记住,header() 必须在发送任何实际输出之前调用,无论是通过普通的 HTML 标记、文件中的空行还是来自 PHP
- Removed some headers that didn't make much of a change;
- Commented another option of writing csv header using the select column's names;
- Now content length works;
- There is no need to echo $basic_info as it is already at output buffer and we redirected it inside a file through headers;
- Removed
@
(PHP Error Control Operator) as it may cause overhead, don't have a link to show you right now but you might find it if you search. You should think twice before silencing errors, mosttimes it should be fixed instead of silenced.
- 删除了一些没有太大变化的标题;
- 评论了使用选择列的名称编写 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);