223

I have data in a MySQL database. I am sending the user a URL to get their data out as a CSV file.

I have the e-mailing of the link, MySQL query, etc. covered.

How can I, when they click the link, have a pop-up to download a CVS with the record from MySQL?

I have all the information to get the record already. I just don't see how to have PHP create the CSV file and let them download a file with a .csv extension.

d-_-b
  • 18,782
  • 33
  • 120
  • 200

19 Answers19

475
header("Content-Type: text/csv");
header("Content-Disposition: attachment; filename=file.csv");

function outputCSV($data) {
  $output = fopen("php://output", "wb");
  foreach ($data as $row)
    fputcsv($output, $row); // here you can change delimiter/enclosure
  fclose($output);
}

outputCSV(array(
  array("name 1", "age 1", "city 1"),
  array("name 2", "age 2", "city 2"),
  array("name 3", "age 3", "city 3")
));

php://output
fputcsv

ntzm
  • 4,301
  • 2
  • 25
  • 35
tellnobody
  • 8,201
  • 3
  • 29
  • 28
  • 66
    More people need to see this and vote it up. This is easily the best answer on this page. PHP already has built-in functions for formatting CSV content. Everyone should use them. Great answer, @Andrey :) – maček Aug 06 '11 at 08:32
  • 2
    Too bad fputcsv() doesn't work correctly though :( https://bugs.php.net/bug.php?id=50686 – gou1 Oct 15 '12 at 11:13
  • 4
    Nice, but the `outputCSV` function is needlessly complicated. You can just `foreach` over `$array`, sending its values straight into `fputcsv`. No need for that `__outputCSV` and the `array_walk` :) `foreach($data as $vals) {fputcsv($filehandler,$vals);}` – Sygmoral Sep 03 '13 at 21:14
  • I'm having some issues with French characters in the array because I eventually need the .csv to be open in Excel. It chokes on the accented characters. Things like `"Prévalence","age 1","city 1"` Any ideas? Messing with UTF-8 hasn't helped thus far. – Voodoo Sep 10 '13 at 21:48
  • @Voodoo How do you open it? The correct way is `Data` -> `Get External Data` -> `From Text` (Excel 2010, other versions should have alike option). – tellnobody Sep 12 '13 at 14:12
  • @Voodoo Also you don't need Unicode as ANSI contains accent characters. And Excel should open it even without import. Which version of Excel do you use? – tellnobody Sep 12 '13 at 14:20
  • Don't know why but its not working in my case. I'm fetching rows from database as `while($row as mysql_fetch_array($res)){$data[] = $row;}`. Also, I'm not getting what is `php://output` used for – Mohammad Faisal Sep 25 '13 at 12:45
  • Its not working on `localhost` but working on hosted server. Any reason? Also, as I've mentioned code above, values are being added twice in a row – Mohammad Faisal Sep 25 '13 at 13:19
  • Works great in my installation. The only change I had to do is to set the delimiter to ";" for excel to open it correctly – theosem May 24 '14 at 12:44
  • 1
    @theosem Delimiter depends on OS or Excel export settings. I've added note about delimiter. – tellnobody May 24 '14 at 20:25
  • You should use ' instead of " for your strings. It is safer and has an infinitesimal performance improvement. – danielson317 Mar 03 '15 at 18:56
  • @danielson317 In which way it is safer? – tellnobody Mar 05 '15 at 21:27
  • @andreyne The use of single quotes primary protects against human error. For example I want to output "lots of $money$" and forget that I have a variable in scope called $money. The double quotes which are just plain not needed are now a security liability if $money happens to have user input in it. Their are a lot of escape sequences in double quotes so unless you are intentionally taking advantage of them it's safer to use single quotes to protect agains human error, which is the most common error in any programing project. – danielson317 Mar 06 '15 at 17:23
  • in case of laravel in the csv file i am getting twi additional blank lines at the start – kakon Dec 08 '15 at 05:19
287

Try:

header("Content-type: text/csv");
header("Content-Disposition: attachment; filename=file.csv");
header("Pragma: no-cache");
header("Expires: 0");

echo "record1,record2,record3\n";
die;

etc

Edit: Here's a snippet of code I use to optionally encode CSV fields:

function maybeEncodeCSVField($string) {
    if(strpos($string, ',') !== false || strpos($string, '"') !== false || strpos($string, "\n") !== false) {
        $string = '"' . str_replace('"', '""', $string) . '"';
    }
    return $string;
}
Popnoodles
  • 27,674
  • 1
  • 40
  • 53
Oleg Barshay
  • 3,718
  • 3
  • 19
  • 20
  • 11
    note the rules for CSVs are important. To ensure good display, put doublequotes around your fields, and don't forget to replace double-quotes inside fields to double double-quotes: `echo '"'.str_replace('"','""',$record1).'","'.str_replace.... – Mala Jul 01 '10 at 21:21
  • 10
    In general, please follow RFC 4180 when generating CSV files. http://tools.ietf.org/html/rfc4180 – Oleg Barshay Apr 21 '11 at 17:43
  • 46
    Just to clarify, the correct HTTP Content-Type header for CSV is text/csv, not application/csv. I doubt any modern browser will care either way, but since there are standards we might as well use them. – fentie Oct 15 '10 at 21:08
  • 5
    @Oleg Barshay: OMG, that RFC is a master piece: "If double-quotes are used to enclose fields, then a double-quote appearing inside a field must be escaped by preceding it with another double quote.". ANOTHER DOUBLE-QUOTE! – aefxx Dec 03 '11 at 00:36
19

Here is an improved version of the function from php.net that @Andrew posted.

function download_csv_results($results, $name = NULL)
{
    if( ! $name)
    {
        $name = md5(uniqid() . microtime(TRUE) . mt_rand()). '.csv';
    }

    header('Content-Type: text/csv');
    header('Content-Disposition: attachment; filename='. $name);
    header('Pragma: no-cache');
    header("Expires: 0");

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

    foreach($results as $result)
    {
        fputcsv($outstream, $result);
    }

    fclose($outstream);
}

It is really easy to use and works great with MySQL(i)/PDO result sets.

download_csv_results($results, 'your_name_here.csv');

Remember to exit() after calling this if you are done with the page.

ntzm
  • 4,301
  • 2
  • 25
  • 35
Xeoncross
  • 50,836
  • 73
  • 238
  • 351
  • How do you change the separator from , to ; ? – Grumpy Jun 02 '12 at 00:41
  • 1
    how should we add a button, to trigger this event, so that user will be able to download the CSV file on demand ? – CanCeylan Jul 18 '12 at 21:26
  • I'll have "SyntaxError: Unexpected token ILLEGAL" error with the line "fopen("php://output", "w");" When I change it to "$fp = fopen('stats.csv', 'w');" it doesn't show error. But then of course it doesn't work. How should I solve it ? – CanCeylan Jul 19 '12 at 15:25
  • @CanCeylan, That is a question in it's own right and I need more details to solve it. – Xeoncross Jul 19 '12 at 15:39
  • To simplify first, instead of calling with button and javascript. I just call the download_csv_results function. However it does write all of the content into a screen, it doesn't download any file ? – CanCeylan Jul 19 '12 at 15:46
  • 1
    Add `fputcsv($outstream, array_keys($results[0]));` just before your `foreach` to also include column headers. – Justin Jul 01 '13 at 18:01
16

In addition to all already said, you might need to add:

header("Content-Transfer-Encoding: UTF-8");

It's very useful when handling files with multiple languages in them, like people's names, or cities.

Stan
  • 1,211
  • 1
  • 11
  • 23
9

The thread is a little old, I know, but for future reference and for noobs as myself:

Everyone else here explain how to create the CSV, but miss a basic part of the question: how to link. In order to link to download of the CSV-file, you just link to the .php-file, which in turn responds as being a .csv-file. The PHP headers do that. This enables cool stuff, like adding variables to the querystring and customize the output:

<a href="my_csv_creator.php?user=23&amp;othervariable=true">Get CSV</a>

my_csv_creator.php can work with the variables given in the querystring and for example use different or customized database queries, change the columns of the CSV, personalize the filename and so on, e.g.:

User_John_Doe_10_Dec_11.csv
LBJ
  • 101
  • 1
  • 3
  • 1
    Uh - anyone reading this be VERY wary of this approach. I would wager that inputs are not being escaped properly and this can lead to big security holes in your application. – calumbrodie Aug 14 '12 at 22:39
  • His method is very safe, and it works great. You can easily control access with the session, and you can also store all of the information you want to put into a csv in a session var. Then just unset it as part of the process converting it to a csv. I would just suggest using session vars instead of a query string. – inorganik Oct 24 '12 at 23:00
  • 1
    There are no security holes per se with this method. As long as the php file referenced is doing it's job of sanitation and access checking. But it doesn't really make sense to show all of that here. The point of the post is you can link to whatever url generates the csv. – danielson317 Mar 03 '15 at 19:14
8

Create your file then return a reference to it with the correct header to trigger the Save As - edit the following as needed. Put your CSV data into $csvdata.

$fname = 'myCSV.csv';
$fp = fopen($fname,'wb');
fwrite($fp,$csvdata);
fclose($fp);

header('Content-type: application/csv');
header("Content-Disposition: inline; filename=".$fname);
readfile($fname);
ntzm
  • 4,301
  • 2
  • 25
  • 35
typemismatch
  • 1,997
  • 2
  • 22
  • 24
  • 1
    Using "inline" [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) means that the browser should attempt to show it without downloading (IE tend to embed an Excel instance for that). "attachment" is what is needed to force a download. – Gert van den Berg Aug 22 '17 at 15:51
5

Here is a full working example using PDO and including column headers:

$query = $pdo->prepare('SELECT * FROM test WHERE id=?');
$query->execute(array($id));    
$results = $query->fetchAll(PDO::FETCH_ASSOC);
download_csv_results($results, 'test.csv'); 
exit();


function download_csv_results($results, $name)
{            
    header('Content-Type: text/csv');
    header('Content-Disposition: attachment; filename='. $name);
    header('Pragma: no-cache');
    header("Expires: 0");

    $outstream = fopen("php://output", "wb");    
    fputcsv($outstream, array_keys($results[0]));

    foreach($results as $result)
    {
        fputcsv($outstream, $result);
    }

    fclose($outstream);
}
ntzm
  • 4,301
  • 2
  • 25
  • 35
Justin
  • 22,998
  • 16
  • 104
  • 122
4

First make data as a String with comma as the delimiter (separated with ","). Something like this

$CSV_string="No,Date,Email,Sender Name,Sender Email \n"; //making string, So "\n" is used for newLine

$rand = rand(1,50); //Make a random int number between 1 to 50.
$file ="export/export".$rand.".csv"; //For avoiding cache in the client and on the server 
                                     //side it is recommended that the file name be different.

file_put_contents($file,$CSV_string);

/* Or try this code if $CSV_string is an array
    fh =fopen($file, 'w');
    fputcsv($fh , $CSV_string , ","  , "\n" ); // "," is delimiter // "\n" is new line.
    fclose($fh);
*/
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
3

Hey It works very well....!!!! Thanks Peter Mortensen and Connor Burton

<?php
header("Content-type: application/csv");
header("Content-Disposition: attachment; filename=file.csv");
header("Pragma: no-cache");
header("Expires: 0");

ini_set('display_errors',1);
$private=1;
error_reporting(E_ALL ^ E_NOTICE);

mysql_connect("localhost", "user", "pass") or die(mysql_error());
mysql_select_db("db") or die(mysql_error());

$start = $_GET["start"];
$end = $_GET["end"];

$query = "SELECT * FROM customers WHERE created>='{$start} 00:00:00'  AND created<='{$end} 23:59:59'   ORDER BY id";
$select_c = mysql_query($query) or die(mysql_error());

while ($row = mysql_fetch_array($select_c, MYSQL_ASSOC))
{
    $result.="{$row['email']},";
    $result.="\n";
    echo $result;
}

?>

Kaddy
  • 31
  • 1
2

Simple method -

$data = array (
    'aaa,bbb,ccc,dddd',
    '123,456,789',
    '"aaa","bbb"');

$fp = fopen('data.csv', 'wb');
foreach($data as $line){
    $val = explode(",",$line);
    fputcsv($fp, $val);
}
fclose($fp);

So each line of the $data array will go to a new line of your newly created CSV file. It only works only for PHP 5 and later.

ntzm
  • 4,301
  • 2
  • 25
  • 35
user244641
  • 31
  • 2
2

You can simply write your data into CSV using fputcsv function. let us have a look at the example below. Write the list array to CSV file

$list[] = array("Cars", "Planes", "Ships");
$list[] = array("Car's2", "Planes2", "Ships2");
//define headers for CSV 
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=file_name.csv');
//write data into CSV
$fp = fopen('php://output', 'wb');
//convert data to UTF-8 
fprintf($fp, chr(0xEF).chr(0xBB).chr(0xBF));
foreach ($list as $line) {
    fputcsv($fp, $line);
}
fclose($fp);
ntzm
  • 4,301
  • 2
  • 25
  • 35
Shahbaz
  • 3,153
  • 1
  • 24
  • 41
1

Already very good solution came. I'm just puting the total code so that a newbie get total help

<?php
extract($_GET); //you can send some parameter by query variable. I have sent table name in *table* variable

header("Content-type: text/csv");
header("Content-Disposition: attachment; filename=$table.csv");
header("Pragma: no-cache");
header("Expires: 0");

require_once("includes/functions.php"); //necessary mysql connection functions here

//first of all I'll get the column name to put title of csv file.
$query = "SHOW columns FROM $table";
$headers = mysql_query($query) or die(mysql_error());
$csv_head = array();
while ($row = mysql_fetch_array($headers, MYSQL_ASSOC))
{
    $csv_head[] =  $row['Field'];
}
echo implode(",", $csv_head)."\n";

//now I'll bring the data.
$query = "SELECT * FROM $table";
$select_c = mysql_query($query) or die(mysql_error()); 

while ($row = mysql_fetch_array($select_c, MYSQL_ASSOC))
{
    foreach ($row as $key => $value) {
            //there may be separator (here I have used comma) inside data. So need to put double quote around such data.
        if(strpos($value, ',') !== false || strpos($value, '"') !== false || strpos($value, "\n") !== false) {
            $row[$key] = '"' . str_replace('"', '""', $value) . '"';
        }
    }
    echo implode(",", $row)."\n";
}

?>

I have saved this code in csv-download.php

Now see how I have used this data to download csv file

<a href="csv-download.php?table=tbl_vfm"><img title="Download as Excel" src="images/Excel-logo.gif" alt="Download as Excel" /><a/>

So when I have clicked the link it download the file without taking me to csv-download.php page on browser.

zahid9i
  • 556
  • 1
  • 8
  • 16
1

Instead of:

$query = "SELECT * FROM customers WHERE created>='{$start} 00:00:00'  AND created<='{$end} 23:59:59'   ORDER BY id";
$select_c = mysql_query($query) or die(mysql_error()); 

while ($row = mysql_fetch_array($select_c, MYSQL_ASSOC))
{
    $result.="{$row['email']},";
    $result.="\n";
    echo $result;
}

Use:

$query = "SELECT * FROM customers WHERE created>='{$start} 00:00:00'  AND created<='{$end} 23:59:59'   ORDER BY id";
$select_c = mysql_query($query) or die(mysql_error()); 

while ($row = mysql_fetch_array($select_c, MYSQL_ASSOC))
{
    echo implode(",", $row)."\n";
}
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Joshua
  • 11
  • 1
1

The easiest way is to use a dedicated CSV class like this:

$csv = new csv();
$csv->load_data(array(
    array('name'=>'John', 'age'=>35),
    array('name'=>'Adrian', 'age'=>23), 
    array('name'=>'William', 'age'=>57) 
));
$csv->send_file('age.csv'); 
Sergiu
  • 17
  • 1
0

How to write in CSV file using PHP script? Actually I was also searching for that too. It is kind of easy task with PHP. fputs(handler, content) - this function works efficiently for me. First you need to open the file in which you need to write content using fopen($CSVFileName, ‘wb’).

$CSVFileName = “test.csv”;
$fp = fopen($CSVFileName, ‘wb’);

//Multiple iterations to append the data using function fputs()
foreach ($csv_post as $temp)
{
    $line = “”;
    $line .= “Content 1″ . $comma . “$temp” . $comma . “Content 2″ . $comma . “16/10/2012″.$comma;
    $line .= “\n”;
    fputs($fp, $line);
}
ntzm
  • 4,301
  • 2
  • 25
  • 35
  • It is always favoured to add the code to so itself instead of linking, as links tends to change or disappear over time. – Sgoettschkes Oct 22 '12 at 03:47
  • Your answer is to a very old thread. While in 2008, it was standard practice to build up your own comma separated lines, the correct method since PHP 5.1.0 would be to use the built in fputcsv function (http://php.net/fputcsv). Take care when reading old threads to ensure that the information contained in them is still relevant. – Luke Mills Oct 22 '12 at 03:51
0

To have it send it as a CSV and have it give the file name, use header():

http://us2.php.net/header

header('Content-type: text/csv');
header('Content-disposition: attachment; filename="myfile.csv"');

As far as making the CSV itself, you would just loop through the result set, formatting the output and sending it, just like you would any other content.

Gavin M. Roy
  • 4,025
  • 4
  • 32
  • 28
0
<?
    // Connect to database
    $result = mysql_query("select id
    from tablename
    where shid=3");
    list($DBshid) = mysql_fetch_row($result);

    /***********************************
    Write date to CSV file
    ***********************************/

    $_file = 'show.csv';
    $_fp = @fopen( $_file, 'wb' );

    $result = mysql_query("select name,compname,job_title,email_add,phone,url from UserTables where id=3");

    while (list( $Username, $Useremail_add, $Userphone, $Userurl) = mysql_fetch_row($result))
    {
        $_csv_data = $Username.','.$Useremail_add.','.$Userphone.','.$Userurl . "\n";
        @fwrite( $_fp, $_csv_data);
    }
    @fclose( $_fp );
?>
ntzm
  • 4,301
  • 2
  • 25
  • 35
0

Writing your own CSV code is probably a waste of your time, just use a package such as league/csv - it deals with all the difficult stuff for you, the documentation is good and it's very stable / reliable:

http://csv.thephpleague.com/

You'll need to be using composer. If you don't know what composer is I highly recommend you have a look: https://getcomposer.org/

John Hunt
  • 3,581
  • 8
  • 39
  • 57
-1

Put in the $output variable the CSV data and echo with the correct headers

header("Content-type: application/download\r\n");
header("Content-disposition: filename=filename.csv\r\n\r\n");
header("Content-Transfer-Encoding: ASCII\r\n");
header("Content-length: ".strlen($output)."\r\n");
echo $output;
0x499602D2
  • 87,005
  • 36
  • 149
  • 233