Skip to content

Instantly share code, notes, and snippets.

@zuzuleinen
Last active January 17, 2024 11:04
Show Gist options
  • Save zuzuleinen/6db61b09465e9bc8a7ea to your computer and use it in GitHub Desktop.
Save zuzuleinen/6db61b09465e9bc8a7ea to your computer and use it in GitHub Desktop.
CSV Response in Symfony controller action
<?php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class CsvController extends Controller
{
/**
* Get a CSV file from an array
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function csvAction()
{
$list = array(
//these are the columns
array('Firstname', 'Lastname',),
//these are the rows
array('Andrei', 'Boar'),
array('John', 'Doe')
);
$fp = fopen('php://output', 'w');
foreach ($list as $fields) {
fputcsv($fp, $fields);
}
$response = new Response();
$response->headers->set('Content-Type', 'text/csv');
//it's gonna output in a testing.csv file
$response->headers->set('Content-Disposition', 'attachment; filename="testing.csv"');
return $response;
}
}
@Nyholm
Copy link

Nyholm commented Sep 17, 2020

Thank you for this.

You should avoid writing to php://output directly. Rather put the data in the $response object.

$fp = fopen('php://temp', 'w');
foreach ($list as $fields) {
    fputcsv($fp, $fields);
}

rewind($fp);
$response = new Response(stream_get_contents($fp));
fclose($fp);

$response->headers->set('Content-Type', 'text/csv');
$response->headers->set('Content-Disposition', 'attachment; filename="testing.csv"');

return $response;

@nacholibre
Copy link

@Nyholm
Why even put the files in temp file? You can use fopen('php://memory', 'w');

But why even bother with file descriptors. The Response constructor expects string. So you can do something like:

$csvData = 'email,first_name,last_name' . PHP_EOL;
foreach ($list as $fields) {
  $csvData .= implode(',', [$fields['email'], $fields['first_name'], $fields['last_name'])) . PHP_EOL;
}
$response = new Response($csvData);

@Nyholm
Copy link

Nyholm commented Jan 20, 2021

Good questions.

Using your suggestion or php://memory will store the data in memory. If you have 100MB of CSV data then your PHP process will take at least 100MB of system memory.

Using php://temp will automatically switch to use a temp file when the data is more than a few megabytes. There is a php.ini setting for this.

See more in the manual: https://www.php.net/manual/en/wrappers.php.php

@nacholibre
Copy link

@Nyholm

But stream_get_contents($fp) has to load the file in memory anyway?

@Nyholm
Copy link

Nyholm commented Jan 20, 2021

Correct. But using php://temp allow you to be smart, ie by using Symfony\Component\HttpFoundation\StreamedResponse.

@nacholibre
Copy link

Agree

@d1823
Copy link

d1823 commented Feb 3, 2022

It's also worth to explain why one shouldn't write to php://output in this case. That stream is the same one as the one used by echo or print statements. Obviously, you can also access it via ob_* functions. The thing is - you've got no idea what's in it or who's going to write to it. I've had a case, where using it caused the Content-Type of a response to randomly change between text/csv and text/html. In other words - weird things happened!

Switching to anything else, in my case to php://temp (thanks Nyholm!), did the trick.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment