I'm serving some records from a MySQL database using PHP's fputcsv() by creating a file on the server, filling it, then linking to it on the next page.
This works and is great but as this could be sensitive data, I don't want a buch of files hanging about on the server when they were created for (probably) a one-time download.
So what I want to know is this: is there a way to create this file & serve it for download without actually writing a permanent file on the server?
For instance could I create a comma separated string instead of using fputcsv() and serve that with the right headers in an output buffer?
The obvious move is to delete the file but I need to wait until the client downloads it first so that makes it a little difficult to decide when to do it.
Any suggestions welcome
The code:
$fp = fopen($filename, 'w');
fputcsv($fp, array("Last Name", "First Name"));
foreach ($result as $fields)
{
fputcsv($fp, $fields);
}
fclose($fp);
http://php.net/manual/en/function.fputcsv.php
fputcsv() is a fabulous little function, so I wouldn't abandon it.
Instead, I suggest you play around with PHP's built-in I/O Wrappers
You, can, for example, do this to "stream" your CSV data line-by-line (subject to various output buffers, but that's another story):
<?php
header('Content-type: text/csv; charset=UTF-8');
header('Content-disposition: attachment; filename=report.csv');
$fp = fopen('php://output','w');
foreach($arrays as $array) fputcsv($fp, $array);
That works great, but if something goes wrong, your users will have a broken download.
So, if you don't have too much data, you can just write to an in-memory stream, just swap out php://output with php://memory and move things around:
<?php
$fp = fopen('php://memory','rw');
// our generateData() function might throw an exception, in which case
// we want to fail gracefully, not send the user a broken/incomplete csv.
try {
while($row = generateData()) fputcsv($fp, $row);
}catch(\Exception $e){
// display a nice page to your user and exit/return
}
// SUCCESS! - so now we have CSV data in memory. Almost like we'd spooled it to a file
// on disk, but we didn't touch the disk.
//rewind our file handle
rewind($fp);
//send output
header('Content-type: text/csv; charset=UTF-8');
header('Content-disposition: attachment; filename=report.csv');
stream_get_contents($fp);
Rather than that, why not just have your page echo out a csv mime type and then echo out the file to the user?
It works a charm, the file is never created and passed as a one off to the client.
Something like this:
header("Content-type: application/csv");
header("Content-Disposition: attachment; filename=file.csv");
header("Pragma: no-cache");
header("Expires: 0");
echo "col1,col2";
for($i=0; $i<25;$i++)
{
echo "key :".$i.", ".($i*$i)."\r\n";
}
You should be able to test that out as is and see how it works.
The added beauty is that most users will be directed to download the file rather than opening it, so the user doesn't even leave the page (most of the time).
Related
I'm trying to output data returned by an MS SQL query to an Excel or CSV file with PHP.
I've used the script in this answer and can output the file OK. Without the header lines (at the bottom of my code) it saves in my server's folder structure rather than outputs as a download to the browser.
If I add the header lines, it ouputs to a CSV file but writes the page's HTML to the file rather than the extract from the database! Am I missing a setting somewhere? I tried running the code on a page with no HTML in it (PHP and SQL code only), but it still happens.
// Give the file a suitable name:
$FileName= $PartNumber.".csv";
$fp = fopen($FileName, 'w');
// Connect to MS SQL server; the actual database is chosen in the form
// ConnSQL defined in inc/dbconn/config.php
ConnSQL($idDatabase);
// the query is a biggie; here it is:
require 'inc_sql.php';
// run it through the SQL server
$rstBOM = sqlsrv_query($GLOBALS['ConnSQL'], $sqlBOM);
while ($export= sqlsrv_fetch_array($rstBOM, SQLSRV_FETCH_ASSOC)) {
if (!isset($headings))
{
$headings = array_keys($export);
fputcsv($fp, $headings, ',', '"');
}
fputcsv($fp, $export, ',', '"');
}
// force download csv - exports HTML to CSV!
header("Content-type: application/force-download");
header('Content-Disposition: inline; filename="'.$FileName.'"');
header("Content-Transfer-Encoding: Binary");
header("Content-length: ". filesize($FileName));
header('Content-Type: application/excel');
header('Content-Disposition: attachment; filename="'.$FileName.'"');
fclose($fp);
Any ideas where I'm going wrong please?
You need to output your csv file to the browser simply by putting
readfile($FileName);
At the end of your code after the fclose($fp); function.
Otherwise, browser receives the headers for files, but no content in sent from your PHP code.
You could also generate your csv file on the fly and just echo $csvFileContents; instead. This would prevent server from creating and writing data to file, which could lead to security breaches.
Good luck!
Im trying to make a CSV export from data entered in an array on my website. I was using this question to help me. I am getting the data that should be in the CSV echoed on my website but not exported to a file. This is the code that I took from the question:
header( "Content-Type: text/csv;charset=utf-8" );
header( "Content-Disposition: attachment;filename=\"$filename\"" );
header("Pragma: no-cache");
header("Expires: 0");
$fp= fopen('php://output', 'w');
foreach ($data as $fields){
fputcsv($fp, $fields);
}
fclose($fp);
exit();
I dont exactly understand what the header() functions are doing. How would I get this to download to a file?
if it helps my array is in this format:
$data = array(dataset1(array, of, data), dataset2(array, of, data), dataset#(array, of, data));
EDIT:My $data array is in a session varible and the reason it wasnt downloading was because there I had session_start() and some includes at the top. Instead of downloading it would echo to the screen but if I remove this it downloads at the cost of there being no data to export. Anyone have a solution to this?
The header() function is sending HTTP headers to your browser with the respective values.
It then sends the CSV data to the output stream which the browser interprets as a downloadable file due to the headers.
Stuck on what is likely a silly problem and only posting after reading several related threads.
Have a page with a lot going on, one of the form options I'm trying to add is so the user can select to download array results in CSV. Problem is HTML header info is coming through in addition to the CSV data I want.
Code is:
function Array2Csv($result, $filename){
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=' .$filename);
$output = fopen('php://output', 'w');
while($row = mysql_fetch_assoc($result)) {
fputcsv($output, $row,'|','"');
}
}
Problem is the result file includes BOTH undesired markup (headers and scripting references) in addition to the CSV itself. Desired output should only include the CSV data.
You have send the the header before any output was send. Disable view and layout.
See also http://php.net/manual/en/function.header.php
I have a script that generates a large CSV file using fputcsv and sends it to the browser. It works, but the browser doesn't show the file download prompt (or start downloading the file) until the whole CSV file has been generated serverside, which takes a long time.
Instead, I'd like the download to begin while the remainder of the file has still being generated. I know this is possible because it's how the 'Export database' option in PHPMyAdmin works - the download starts as soon as you click the 'export' button even if your database is huge.
How can I tweak my existing code, below, to let the download begin immediately?
$csv = 'title.csv';
header( "Content-Type: text/csv;charset=utf-8" );
header( "Content-Disposition: attachment;filename=\"$csv\"" );
header( "Pragma: no-cache" );
header( "Expires: 0" );
$fp = fopen('php://output', 'w');
fputcsv($fp, array_keys($array), ';', '"');
foreach ($array as $fields)
{
fputcsv($fp, $fields, ';', '"');
}
fclose($fp);
exit();
Empirically, it seems that when receiving responses featuring a Content-Disposition: attachment header, different browsers will show the file download dialog at the following moments:
Firefox shows the dialog as soon as it receives the headers
Internet Explorer shows the dialog once it has received the headers plus 255 bytes of the response body.
Chromium shows the dialog once it has received the headers plus 1023 bytes of the response body.
Our objectives, then, are as follows:
Flush the first kilobyte of the response body to the browser as soon as possible, so that Chrome users see the file download dialog at the earliest possible moment.
Thereafter, regularly send more content to the browser.
Standing in the way of these objectives are, potentially, multiple levels of buffering, which you can try to fight in different ways.
PHP's output_buffer
If you have output_buffering set to a value other than Off, PHP will automatically create an output buffer which stores all output your script tries to send to the response body. You can prevent this by ensuring that you have output_buffering set to Off from your php.ini file, or from a webserver config file like apache.conf or nginx.conf. Alternatively, you can turn off the output buffer, if one exists, at the start of your script using ob_end_flush() or ob_end_clean():
if (ob_get_level()) {
ob_end_clean();
}
Buffering done by your webserver
Once your output gets past the PHP output buffer, it may be buffered by your webserver. You can try to get around this by calling flush() regularly (e.g. every 100 lines), although the PHP manual is hesitant about providing any guarantees, listing some particular cases where this may fail:
flush
...
Flushes the write buffers of PHP and whatever backend PHP is using (CGI, a web server, etc). This attempts to push current output all the way to the browser with a few caveats.
flush() may not be able to override the buffering scheme of your web server ...
Several servers, especially on Win32, will still buffer the output from your script until it terminates before transmitting the results to the browser.
Server modules for Apache like mod_gzip may do buffering of their own that will cause flush() to not result in data being sent immediately to the client.
You can alternatively have PHP call flush() automatically every time you try to echo any output, by calling ob_implicit_flush at the start of your script - though beware that if you have gzip enabled via a mechanism that respects flush() calls, such as Apache's mod_deflate module, this regular flushing will cripple its compression attempts and probably result in your 'compressed' output being larger than if it were uncompressed. Explicitly calling flush() every n lines of output, for some modest but non-tiny n, is thus perhaps a better practice.
Putting it all together, then, you should probably tweak your script to look something like this:
<?php
if (ob_get_level()) {
ob_end_clean();
}
$csv = 'title.csv';
header( "Content-Type: text/csv;charset=utf-8" );
header( "Content-Disposition: attachment;filename=\"$csv\"" );
header( "Pragma: no-cache" );
header( "Expires: 0" );
flush(); // Get the headers out immediately to show the download dialog
// in Firefox
$array = get_your_csv_data(); // This needs to be fast, of course
$fp = fopen('php://output', 'w');
fputcsv($fp, array_keys($array), ';', '"');
foreach ($array as $i => $fields)
{
fputcsv($fp, $fields, ';', '"');
if ($i % 100 == 0) {
flush(); // Attempt to flush output to the browser every 100 lines.
// You may want to tweak this number based upon the size of
// your CSV rows.
}
}
fclose($fp);
?>
If this doesn't work, then I don't think there's anything more you can do from your PHP code to try to resolve the problem - you need to figure out what's causing your web server to buffer your output and try to solve that using your server's configuration files.
have not tested this. try to flush the script after n number of data rows.
flush();
Try Mark Amery's answer, but just emphasize on the statement:
$array = get_your_csv_data(); // This needs to be fast, of course
If you're fetching huge number of records, fetch them by chunks (every 1000 records for example).
So:
Fetch 1000 records
Output them
Repeat
I think you are looking for the octet-stream header.
$csv = 'title.csv';
header('Content-Type: application/octet-stream');
header("Content-Disposition: attachment;filename=\"$csv\"" );
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate');
header('Expires: 0');
$fp = fopen('php://output', 'w');
fputcsv($fp, array_keys($array), ';', '"');
foreach ($array as $fields)
{
fputcsv($fp, $fields, ';', '"');
}
fclose($fp);
exit();
I've seen this asked before and I am having trouble getting this to work properly after trying a number of solutions. The problem is I can't get my data to export into a csv format properly. Before I added my ob_end_clean it would export out to a csv with html, now it doesn't give me a csv, just text.
Here is my code on the file that is being required.
if (isset($_POST["hidden"])) {
$list = array (
array('aaa', 'bbb', 'ccc', 'dddd'),
array('123', '456', '789'),
array('"aaa"', '"bbb"')
);
$fp = fopen('php://output','w');
foreach ($list as $row) {
ob_end_clean();
fputcsv($fp, $row);
}
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=data.csv');
}
Right now when I do my export, the data gets put back on the screen similar to a var_dump(). I just simply want this to go to a csv file without having html all over it.
Got it working!
I invoked my csv code before anything on the page. :) Then I did my connection to my table, then did my logic for my code. I didn't have an ob_start or ob_flush on my main file which made a big difference. I had the ob_clean before the while loop and then I did an exit() after declaring the header. Hopefully, this explains it well.
Here is my code.
if (isset($_POST["hidden"])) {
$sql = "SELECT * FROM `newsletter`";
$result = mysql_query($sql);
ob_end_clean();
$fp = fopen('php://output','w');
while ($list = mysql_fetch_assoc($result)) {
fputcsv($fp, $list);
}
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=data.csv');
exit();
}
Place, ob_end_clean(); before you output the csv.
ob_end_clean() meaning:
"Clean (erase) the output buffer and turn off output buffering" - PHP manual.
The logic for it to work is to construct your php script so that it:
first echoes all the html/javascript... content intended for the browser page like echo "<html...>;" (this already uses php's output buffer behind the scenes)
after that cleans the php's output buffer so far, with ob_end_clean() (depending how ob_start() was called this may prevent the previous export) or ob_clean() which just sends content so far to the browser and cleans the buffer without turning it off.
lastly uses this clean output buffer again to export any further content (as downloadable csv in our case) to the browser, like shown above by wowzuzz. So if any html is echoed by the script after that, it will be included in the csv as well.