26

I'm trying to create a short PHP script that takes a JSON string, converts it to CSV format (using fputcsv), and makes that CSV available as a downloaded .csv file. My thought was to use tmpfile() to not worry about cronjobs or running out of disk space, but I can't seem to make the magic happen.

Here's my attempt, which is a converted example from the PHP docs of readfile:

$tmp = tmpfile();
$jsonArray = json_decode( $_POST['json'] );
fputcsv($tmp, $jsonArray);

header('Content-Description: File Transfer');
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename='.basename($tmp));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($tmp));

ob_clean();
flush();
readfile($tmp);

I feel like the Content-Disposition and Content-Transfer-Encoding headers are wrong, but I'm not sure how to fix them. For example, basename($tmp) doesn't seem to return anything, and I'm not sure text/csv transfers as binary encoding. Likewise, echo filesize($tmp) is blank as well. Is there a way to do what I'm attempting here?

[Edit]

**Below is the working code I wrote as a result of the accepted answer:*

$jsonArray = json_decode( $_POST['json'], true );
$tmpName = tempnam(sys_get_temp_dir(), 'data');
$file = fopen($tmpName, 'w');

fputcsv($file, $jsonArray);
fclose($file);

header('Content-Description: File Transfer');
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename=data.csv');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($tmpName));

ob_clean();
flush();
readfile($tmpName);

unlink($tmpName);
Impirator
  • 1,393
  • 1
  • 12
  • 24
  • For a javascript solution to this problem, take a look at http://stackoverflow.com/questions/4130849/convert-json-format-to-csv-format-for-ms-excel. – Impirator Mar 13 '13 at 16:19
  • Does `json_decode` return `null` by any chance? – Jon Mar 13 '13 at 16:20
  • So the file is just do not want to be downloaded? and opened in browser? that is the problem? – Anton Mar 13 '13 at 16:23

4 Answers4

8

Note that tmpfile() returns a file handle, but all the functions you have need a file path (i.e a string), not a handle - notably basename, filesize, and readfile. So none of those function calls will work correctly. Also basename won't return a file extension either. Just call it whatever you want, i.e

'Content-Disposition: attachment; filename=data.csv'

As @Joshua Burns also says, make sure you're passing in an array to fputcsv or use the assoc parameter.

Ivo
  • 5,378
  • 2
  • 18
  • 18
  • This was everything needed to solve the issue. I'll edit the working code into the question so people can see the fix. Thank you! – Impirator Mar 13 '13 at 16:51
3

json_decode() by default translates elements into objects rather than arrays. fputcsv() expects the data passed to be an array.

I'd recommend changing:

$jsonArray = json_decode( $_POST['json'] );

To:

$jsonArray = json_decode( $_POST['json'], True );

And see if that doesn't fix your problem.

When attempting to tackle problems such as these I'd highly recommend enabling display_errors and setting error_reporting to E_ALL to see if there some sort of error you are missing out on:

<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');

// Rest of your code here
Joshua Burns
  • 8,268
  • 4
  • 48
  • 61
0

Firstly, check this string out, i would change it to:

header('Content-Disposition: attachment; filename="'.basename($tmp).'"');

i had the problem with it once, with browser compatibility :)

Anton
  • 1,029
  • 7
  • 19
0

One problem is that tmpfile() returns a file handle, while filesize takes a filename as parameter.
The following line:

header('Content-Length: ' . filesize($tmp));

probably gets evaluated as:

header('Content-Length: 0');

Check that all the calls to header() are executed before any output happens.

Jocelyn
  • 11,209
  • 10
  • 43
  • 60