16
private function convert_to_csv($input_array, $output_file_name, $delimiter) {

    $temp_memory = fopen('php://memory','w');

    foreach ($input_array as $line) {

        fputcsv($temp_memory, $line, $delimiter);

    }

    fseek($temp_memory, 0);

    header('Content-Type: application/csv');
    header('Content-Disposition: attachement; filename="' . $output_file_name . '";');

    fpassthru($temp_memory);

}               

I use the above function to take an array of data, convert to CSV, and output to the browser. Two questions:

  1. Is the file removed from memory after being downloaded via HTTP?
  2. How would this same function be rewritten such that the file could be used (for example, to use as an email attachment sent with PHPMailer) and then removed from memory immediately afterwards?

EDIT: Working Code - But writes to file, not memory

public function emailCSVTest() {

    $test_array = array(array('Stuff','Yep'),array('More Stuff','Yep yep'));

    $temp_file = '/tmp/temptest.csv';

    $this->convertToCSV($test_array, $temp_file);

    $this->sendUserEmail('Test Subject','Test Message','nowhere@bigfurryblackhole.com',$temp_file);

    unlink($temp_file);

}

private function convertToCSV($input_array, $output_file) {

    $temp_file = fopen($output_file,'w');

    foreach ($input_array as $line) {

        fputcsv($temp_file, $line, ',');

    }

    fclose($temp_file);

}

Still unanswered: does the original function remove the file from memory, or no?

FurryWombat
  • 816
  • 2
  • 12
  • 28
  • 1
    I would just store the file on the file system (`/tmp` for example), send the mail (with attachment), and afterwards call `unlink()` on the filename. – Niek van der Steen May 28 '15 at 15:33
  • So change first line to $temp_file = fopen('/tmp/abc.csv','w'), create the file, then replace fseek >> fpassthru with return $temp_file, following up in the parent function with unlink($temp_file)? – FurryWombat May 28 '15 at 15:36
  • something like that, yes. Don't forget to `fclose()` the file first. (before the `unlink()`) – Niek van der Steen May 28 '15 at 15:41
  • Thanks! Working example has been added above. – FurryWombat May 29 '15 at 15:10
  • Why would you ever write to a file in the first place, if you need it to be volatile? – Mjh May 29 '15 at 15:16
  • Good question. Wrong answer, but functional for my purposes. Chosen answer below appears to be more in line with what the original question was asking. – FurryWombat May 29 '15 at 15:20

1 Answers1

29

I would make use PHP's temp fopen wrapper together with threshold of memory like this:

// we use a threshold of 1 MB (1024 * 1024), it's just an example
$fd = fopen('php://temp/maxmemory:1048576', 'w');
if ($fd === false) {
    die('Failed to open temporary file');
}

$headers = array('id', 'name', 'age', 'species');
$records = array(
    array('1', 'gise', '4', 'cat'),
    array('2', 'hek2mgl', '36', 'human')
);

fputcsv($fd, $headers);
foreach($records as $record) {
    fputcsv($fd, $record);
}

rewind($fd);
$csv = stream_get_contents($fd);
fclose($fd); // releases the memory (or tempfile)

The memory treshold is 1MB. If the CSV file get's larger, PHP would create a temporary file, otherwise all will happen in memory. The advantage is that large CSV files won't exhaust the memory.

About your second question, fclose() will release the memory.

I once wrote a blog article regarding to this, you may find it interesting: http://www.metashock.de/2014/02/create-csv-file-in-memory-php/

Nopcea Flavius
  • 303
  • 1
  • 3
  • 15
hek2mgl
  • 152,036
  • 28
  • 249
  • 266
  • what happens when another process (user) runs the same script and temp file must also be created: will each temporary file have unique filename? So that they won't get overwritten? – Andrew Jan 12 '17 at 07:12
  • sure, that's safe. – hek2mgl Jan 12 '17 at 07:14
  • And if I need 'file_path', to be used in other function (something like CurlFile()), then the path would be whole 'php://temp/maxmemory:1048576' ? – Andrew Jan 12 '17 at 08:50
  • No, that would open a new file. You'd need to pass the file descriptor `$fd`. Looks like that doesn't work with `CurlFile()`. That's really sad how that function is designed. I would recommend to file a feature request to support both a filename or a file descriptor (which doesn't really help you atm, I know).. – hek2mgl Jan 12 '17 at 08:55
  • what if I create temp file (not in memory file) and get filepath to that temp file, then maybe it would work with CurlFile() ? I would need to get path to that temp file then... – Andrew Jan 12 '17 at 09:51
  • Yes, but then you don't need the php://temp wrapper. You can simply use `tempnam()` – hek2mgl Jan 12 '17 at 10:40
  • @Andrew Check this answer: http://stackoverflow.com/a/1342760/171318 . It shows how to do it in memory – hek2mgl Jan 12 '17 at 10:46
  • Or maybe I can just do $fd = tmpfile(); ? And then stream_get_meta_data($fd)['uri'] to get absolute path – Andrew Jan 12 '17 at 10:47
  • And what's the benefit? – hek2mgl Jan 12 '17 at 11:12
  • I don't want to handle file creation, unique filename guarantee, file deletion, etc. but i need to 'convert' array into filepath, because CurlFile() accepts filepath (and API on the other end needs csv file to receive). The best would be to do all of this in memory, but as you said yourself - not possible – Andrew Jan 12 '17 at 12:10
  • @Andrew I never said that it is not possible to upload the file created in memory. It is just not possible using CurlFile. It is definitely possible, even without curl. Have you read the answer I've pointed you towards above? – hek2mgl Jan 12 '17 at 12:53
  • Well I most likely have to use CurlFile...Yes I have read, and this is an alternative solution, thanks. But I think i'll go with tmpfile()..why not.. – Andrew Jan 12 '17 at 13:04