As mentioned in my comments of Edson's answer, I expected a "headers already sent" warning at the last line of code:
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.
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 sent to whatever back end PHP is being used (CGI,
mod_php, FastCGI). Output buffering is always Off in 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).
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:
// 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.
mail().