1

i have a model that returns car data with id, name, price etc. So i have a car controller and a csv action that fetches this data from a model:

$carTable = $this->getServiceLocator()->get('Application\Model\DbTable\Cars');
$cars = $carTable->fetchAll();

i need to download this "$cars" data as a CSV file, so the user can store it on the disk.

I have tried to disable the layout and echo a CSV string and setting "content-type" and other headers, but it didn't work out of the box. Then i figured out that i should probably create a custom CsvRenderer and register it in configuration.

Since i couldn't find any documentation about this on the ZF2 site or in blogs and Stackoverflow answers, i would like to know if this is the recommended general approach for downloading data as CSV in ZF2? or is there a simpler solution that i am not aware of?

Thanks

akond
  • 15,865
  • 4
  • 35
  • 55
Faris Zacina
  • 14,056
  • 7
  • 62
  • 75
  • This is not a place where one asks for final solutions. You should come here with questions of things you wanna do with a set of things you have already tried. You should name specific questions to specific problems! `I want $a, how can i achieve this` is not something all too welcomed in here – Sam Dec 03 '12 at 17:03
  • Ok. I improved the question. I would appreciate some help. Thank you. – Faris Zacina Dec 03 '12 at 17:45

3 Answers3

11

One solution would be to return a Response object directly from the controller.

Something like this stands a good chance of working:

public function downloadAction()
{
    // magically create $content as a string containing CSV data

    $response = $this->getResponse();
    $headers = $response->getHeaders();
    $headers->addHeaderLine('Content-Type', 'text/csv');
    $headers->addHeaderLine('Content-Disposition', "attachment; filename=\"my_filen.csv\"");
    $headers->addHeaderLine('Accept-Ranges', 'bytes');
    $headers->addHeaderLine('Content-Length', strlen($content));

    $response->setContent($content);
    return $response;
}

(Note that I haven't tested this code, but have updated in line with comment!)

Rob Allen
  • 12,643
  • 1
  • 40
  • 49
  • Y.. the code didn't work exactly because the setHeader function does not exist, so i used $response->getHeaders()->addHeaders.. but anyway, the general approach was correct and it worked. Thanks. – Faris Zacina Dec 04 '12 at 15:20
  • 2
    Someone should edit the answer to reflect the code that did work. – Iznogood Dec 04 '12 at 15:48
3

Since we are talking about ZF2, I would rather solve this by adding a new CSV ViewStrategy/Renderer/ViewModel.

Here you can read more about implementing own rendering and response strategies: Zend Docs

or even here: http://zend-framework-community.634137.n4.nabble.com/ZF2-Implementing-custom-renderer-strategy-td4655896.html

That will turn the code in the controller slimmer, cleaner and more clear, since you don't have to care about the headers in the controller, but in the renderer. Then, each time you need a new CSV output, you don't have to write that action all over again, you just use the CSV View model

Dragos
  • 1,824
  • 16
  • 19
  • Won't it just output the csv content in the browser instead of downloading the file? If so than it is a huge disadvantage in case of larger files. – Zippp Jun 05 '18 at 13:43
  • @Zippp In the strategy you could simply do the exact same thing as in the accepted answer. We used a strategy like this for downloading contact cards and it works perfectly and is the proper way to implement download. Like that you could make one common strategy that works for downloading all kinds of different file types. You could even return a file stream from a strategy... – Wilt Sep 07 '19 at 06:54
2

This is based off the answer provided by Rob.

In Rob's answer, by assigning $response->getHeaders() to a new $headers variable, then applying the ->addHeaderLine to that new variable, it does not properly set the headers on the $response. So when returned, the headers were not attached to the response.

To fix this, simply add the headers directly to the $response shown below:

public function downloadAction()
{
    // magically create $content as a string containing CSV data

    $response = $this->getResponse();
    $response->getHeaders()
             ->addHeaderLine('Content-Type', 'text/csv')
             ->addHeaderLine('Content-Disposition', "attachment; filename=\"my_filen.csv\"")
             ->addHeaderLine('Accept-Ranges', 'bytes')
             ->addHeaderLine('Content-Length', strlen($content));

    $response->setContent($content);
    return $response;
}
krchun
  • 994
  • 1
  • 9
  • 19
  • What are the differences to Rob his answer? I think it does exactly the same, it is only differently formatted... – Wilt Sep 07 '19 at 06:58
  • By assigning `$response->getHeaders()` to a new `$headers` variable, then applying the `->addHeaderLine` to that new variable, it does not properly set the headers on the `$response`. So when returned, the headers were not attached to the response. – krchun Sep 08 '19 at 08:38
  • 1
    Thanks for the response to my comment. May I suggest to add this explanation to your answer, it will help people actually understand the difference. – Wilt Sep 08 '19 at 15:49