When someone calls a URL, I want to post him/her a .csv file. This works with Content-Disposition headers. However, I would like to tell php in my program when it should start with the output and when it should ignore messages being otherwise printed to the screen.
The reason is that I am calling general classes that lookup data in a database (a CRM). These lookup function 'must' generate some output. The reason for this is that they take longer than 1 minute (sometimes) and the output is like a 'ping' telling browsers not to close the connection and wait for the completion of the script. I do not want this output in my csv-file, though.
<?php
error_reporting(E_ERROR);
header('Expires: Sun, 01 Jan 2014 00:00:00 GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', FALSE);
header('Pragma: no-cache');
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=search.csv');
....
ob_start();
//these two methods can take a long time and they 'ping' between pages
//i.e. after they looked up 450 records, they ping and look up the next 450
$allPublished=OrganizationLookups::lookup(["ORGANISATION_FIELD_4" =>
"true"],$myInsightly);
$allContacts=ContactLookups::lookup(["TAGS" => "TPZ-Mieter"],$myInsightly);
//flush and ob_end_clean was my idea to suppress the output
//but it does not work
flush();ob_flush();
ob_end_clean();
//now i am ready to put everything to the file what comes next
$file = fopen('php://output', 'w');
....
fputcsv($file,$itarray); //in foreach loop
}
fclose($file);
exit();
?>
As you can see in the code, I am trying to fiddle with ob_start() etc. to control the output, but it does not work. (I also tried ob_get_clean etc) I also would think that only stuff written to the $file (php://output) is written to the csv file, but it prints everything into it.
One approach could be a multipart reply showing an HTML page and providing a file download. However, there seem to be support issues by some browser(s), especially Chrome.
<?php declare (strict_types=1);
header('Expires: Sun, 01 Jan 2014 00:00:00 GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', FALSE);
header('Pragma: no-cache');
header('Content-type: multipart/mixed; boundary="MultiPartBoundary"');
?>
--MultiPartBoundary
Content-type: text/html; charset=UTF-8
<!DOCTYPE html>
<html>
<head>
<title>Download</title>
<meta charset="UTF-8">
</head>
<body>
<h1>search.cvs - Download</h1>
<div>
generating CVS
<?php // fake generation showing progress
for($i=0; $i<10; $i++)
{
sleep(1);
echo '.' . str_repeat(' ', 1024), PHP_EOL;
flush();
}
?>
</div>
</body>
</html>
--MultiPartBoundary
Expires: Sun, 01 Jan 2014 00:00:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Cache-Control: post-check=0, pre-check=0
Pragma: no-cache
Content-type: text/csv; charset=utf-8
Content-Disposition: attachment; filename=search.csv
<?php // CVS output
echo <<< __EOS__
Col1,Col2,Col3
Val11,Val12,Val13
Val21,Val22,Val23
__EOS__;
?>
--MultiPartBoundary--
I used .htaccess
to disable buffering
<Files "multipart.php">
php_value output_buffering Off
</Files>
Alternatives
There are more commonly used approaches based on JavaScript. Basically the principle is always the same: You perform an initiating request and the generation is started in the background. Subsequent requests, e.g. AJAX or simple reloads, ask for the data with a temporary ID.
Another more complex alternative is to implement a bidirectional websocket connection with a real server-push functionality.