Outputting CSV as a Downloadable File in PHP

Nearly every application you could write in for the business sphere in PHP probably requires some sort of data export, most likely in the CSV format.

The easiest way to provide a downloadable file is by altering the headers and echo’ing the file content. In our case:

<?php
header("Content-type: text/csv");
header("Cache-Control: no-store, no-cache");
header('Content-Disposition: attachment; filename="filename.csv"');

We want to set our applicable Content-Type so that the browsers associate the file properly. Just relying on the extension doesn’t work, even in Windows. The magic is in the third header setting, “Content-Disposition,” which informs the browser to download as a separate file (don’t open a new window and display a blank page, just display the file download box) and tell the browser the filename is “filename.csv”. This way rewrite rules like http://localhost/export/csv/ will result in a download box that declares the file “filename.csv” rather than a randomly assigned name or whatever the current url is.

Into the meat of the CSV export. At the very beginning we need to open up a stream to the PHP output (the same place where echo sends its string content, which is NOT stdout):

<php
$outstream = fopen("php://output",'w');

Next we’re going to assume you already have your data packed nicely into an array (or array of arrays) so long as we have a single array per row/line.

The magic comes into play using the build in PHP function fgetcsv(). fgetcsv() takes an array for a single row and outputs it, automatically escaping output according to column and enclosure delimiters!

fgetcsv() requires a file resource as its first parameter and the magic of PHP streams is they act like a file resource (actually a file resource is just a file stream), so we give it $outstream to make fputcsv() echo its output. We fill in the rest of the parameters according to the php.net documentation and voila we have:

<php
header("Content-type: text/csv");
header("Cache-Control: no-store, no-cache");
header('Content-Disposition: attachment; filename="filename.csv"');

$outstream = fopen("php://output",'w');

$test_data = array(
	array( 'Cell 1,A', 'Cell 1,B' ),
	array( 'Cell 2,A', 'Cell 2,B' )
);

foreach( $test_data as $row )
{
	fputcsv($outstream, $row, ',', '"');
}

fclose($outstream);

For more output stream craziness, Timothy Boronczyk from the #phpc IRC channel on freenode shared a code snipped that outputs CSV either to the output buffer OR will return it as a string using some clever streams hackery:

<php
function exportCSV($data, $col_headers = array(), $return_string = false)
{
    $stream = ($return_string) ? fopen ('php://temp/maxmemory', 'w+') : fopen ('php://output', 'w');

    if (!empty($col_headers))
    {
        fputcsv($stream, $col_headers);
    }

    foreach ($data as $record)
    {
        fputcsv($stream, $record);
    }

    if ($return_string)
    {
        rewind($stream);
        $retVal = stream_get_contents($stream);
        fclose($stream);
        return $retVal;
    }
    else
    {
        fclose($stream);
    }
}

Comments

  1. kL says:

    Bleh.

    Use make URL like:

    realfile.php/fakefile.csv

    and you won’t need Content-Disposition (or use mod_rewrite to hide .php completely)

  2. @kL: While that is acceptable, it’s good practice to set proper headings to prevent browsers from possibly assuming the file to be straight text (which CSV really is anyways) and outputting it in the browser as opposed to providing a download window so you can open directly into Excel/OpenOffice.

  3. Matt says:

    Adding the no-store, no-cache header causes IE to fail to download the file with the error “Internet Explorer cannot download….blah” . As far as I can tell this is a long standing bug in IE. Removing this line fixes the problem. However, I haven’t tested whether IE then actually caches the file or not.

  4. This article was very helpful – thanks very much.

  5. Jeremy says:

    Would you happen to have another example of using this in a webpage… that is… I’m having problems understand how the php code is executed… like say if you have a form and you click the button… the form’s action goes to a php file with your code above.. but it just shows a blank screen and never shows the download dialog… what am i doing wrong?

  6. Jeremy says:

    I also tried it using <form action="" and then process the export code via a function… I get the download, but it streams the entire webpage and the data in the array. So I get the info I want, but I have a mess with all the other code above it. Hmmmm… frustrated…

  7. @Jeremy all you need is to put exit; on the very last line, just before the closing curly bracket of function that manipulate the output of the form.

  8. Chathushka says:

    @bloghery.info thanks for that mate. Saved a whole lot of time :)

  9. wyixin says:

    i just used exportCSV function, but it seems dont work with ajax call.
    I mean if i use exportCSV in a.php and make a ajax call from b page, post some data to a , then i cant download csv file from b page. Can you have some advise? thanx

  10. wyixin: AJAX requests will not trigger the download dialog. You will want to pass in your data via the GET parameters and just do a window.location = "/path/to/a.php?key=value&more=data" or something similar.

  1. [...] a recent post to his blog Daniel Cousineau shows a method for correctly outputting CSV data in push down to the [...]

  2. [...] rows to output using fputcsv() and tell the browser to hand the user a downloadable file. “Outputting CSV as a Downloadable File in PHP” by Daniel Cousineau covers the entire process from start to [...]

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>