How to force download a .csv file in Symfony 2, using Response object?

落花浮王杯 提交于 2021-01-20 15:28:07

问题


I'm making a "Download" controller using Symfony 2, that has the sole purpose of sending headers so that I can force a .csv file download, but it isn't working properly.

$response = new Response();
$response->headers->set('Content-Type', "text/csv");
$response->headers->set('Content-Disposition', 'attachment; filename="'.$fileName.'"');
$response->headers->set('Pragma', "no-cache");
$response->headers->set('Expires', "0");
$response->headers->set('Content-Transfer-Encoding', "binary");
$response->headers->set('Content-Length', filesize($fileName));
$response->prepare();
$response->sendHeaders();
$response->setContent(readfile($fileName));
$response->sendContent();

$fileName is a "info.csv" string. Such are my actions inside my controller, there's no return statement. When I tried returning the Response Object, the contents of the file were displayed in the browser, not my intended result.

The problem I've found is that in some pages I do get my info.csv file, but in anothers all I get is a message:

No webpage was found for the web address: http://mywebpage.com/download Error 6 (net::ERR_FILE_NOT_FOUND): The file or directory could not be found.

I'm completely sure the file exists, so there must be another thing wrong. Also, routing.yml is working correctly, since I do get the file from other pages that also link to that path. The Apache error log doesn't show anything about it.

Has anyone forced the download of a .csv file on Symfony 2 before? If so, what am I doing wrong?


回答1:


Here is a minimal example that works just fine in production:

class MyController
public function myAction()

    $response = $this->render('ZaysoAreaBundle:Admin:Team/list.csv.php',$tplData);

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

    return $response;

You can replace the render call with new response and response->setContent if you like.

Your comment about no return statement inside a controller is puzzling. Controllers return a response. Let the framework take care of sending the stuff to the browser.




回答2:


I realize this post is kind of old and that there is, oddly enough, practically no good resources on how to do a CSV Export in symfony 2 besides this post at stackoverflow.

Anyways I used the example above for a client contest site and it worked quite well. But today I received an e-mail and after testing it myself, the code had broken - I was able to get the download working with a small amount of results, but the database now exporting over 31,000 rows it either simply showed the text or with chrome, just basically did nothing.

For anyone having issue with a large data export, this is what I manged to get to work, basically doing what Cerad suggested as an alternate way:

    $filename = "export_".date("Y_m_d_His").".csv";
    $response = $this->render('AppDefaultBundle:Default:csvfile.html.twig', array('data' => $data));

    $response->setStatusCode(200);
    $response->headers->set('Content-Type', 'text/csv');
    $response->headers->set('Content-Description', 'Submissions Export');
    $response->headers->set('Content-Disposition', 'attachment; filename='.$filename);
    $response->headers->set('Content-Transfer-Encoding', 'binary');
    $response->headers->set('Pragma', 'no-cache');
    $response->headers->set('Expires', '0');

    $response->prepare();
    $response->sendHeaders();
    $response->sendContent();

EDIT: After more testing and upping the max seconds allowed, I realized the previous code was printing out the headers at the top so I've updated the code.




回答3:


THis worked for me to export CSV and JSON.

Twig files are named : export.csv.twig, export.json.twig

The Controller :

class PrototypeController extends Controller {

public function exportAction(Request $request) {

    $data = array("data" => "test");

    $format = $request->getRequestFormat();

    if ($format == "csv") {
        $response = $this->render('PrototypeBundle:Prototype:export.' . $format . '.twig', array('data' => $data));
        $filename = "export_".date("Y_m_d_His").".csv";
        $response->headers->set('Content-Type', 'text/csv');
        $response->headers->set('Content-Disposition', 'attachment; filename='.$filename);

        return $response;
    } else if ($format == "json") {
        return new Response(json_encode($data));
    }
}
}

The Routing :

prototype_export:
    pattern:  /export/{_format}
    defaults: { _controller: PrototypeBundle:Prototype:export, _format: json }
    requirements:
        _format:  csv|json

The Twigs:

export.csv.twig (do your comma seperated thing here, this is just a test)

{% for row in data %}
{{ row }}
{% endfor %} 

export.json.twig (data is sent json_encoded, this file is empty)

Hope this helps!




回答4:


This is how I managed to get Silex to return a csv:

// $headers in an array of strings
// $results are the records returned by a PDO query
 $stream = function() use ($headers, $results) {
    $output = fopen('php://output', 'w');
    fputcsv($output, $headers);
    foreach ($results as $rec)
    {
        fputcsv($output, $rec);
    }

    fclose($output);
};

return $app->stream($stream, 200, array(
    'Content-Type' => 'text/csv',
    'Content-Description' => 'File Transfer',
    'Content-Disposition' => 'attachment; filename="test.csv"',
    'Expires' => '0',
    'Cache-Control' => 'must-revalidate',
    'Pragma' => 'public',
));

You may also need to do some Jiggery Pokery with Javascript (I was downloading Via AJAX) but this post was all I needed to get it working.




回答5:


simple function you can use for every case to export an csv for download...

public function getResponse(array $data, $filename, $headers = array())
{
    if(substr(strtolower($filename), -4) == '.csv') {
        $filename = substr($filename, 0, -4);
    }

    $tmpFile = $this
        ->_getContainer()
        ->get('kernel')
        ->getRootDir()
        . '/../var/tmp_'.substr(md5(time()),0,5);

    if(file_exists($tmpFile)) unlink($tmpFile);

    $handle = fopen($tmpFile, 'w');

    foreach ($data as $i => $row) {

        $row = (array) $row;

        if($i == 0) fputcsv($handle, array_keys($row));

        fputcsv($handle, $row);

    }

    fclose($handle);

    $Response = new Response(file_get_contents($tmpFile));

    unlink($tmpFile);

    $filename = preg_replace('[^a-z0-9A-Z_]', '', $filename);

    $headers = array_merge([
        'Expires' => 'Tue, 01 Jul 1970 06:00:00 GMT',
        'Cache-Control' => 'max-age=0, no-cache, must-revalidate, proxy-revalidate',
        'Content-Disposition' => 'attachment; filename='.$filename.'.csv',
        'Content-Type' => 'text/csv',
        'Content-Transfer-Encoding' => 'binary',
    ], $headers);

    foreach ($headers as $key => $val) {
        $Response->headers->set($key, $val);
    }

    return $Response;
}



回答6:


How about using Sonata's Exporter:

use Exporter\Writer\CsvWriter;
/**
 * @param array $orders
 */
public function exportToCsv($orders)
{
    $rootdir = $this->get('kernel')->getRootDir();
    $filename = $rootdir . '/data/orders.csv';
    unlink($filename);
    $csvExport = new CsvWriter($filename);
    $csvExport->open();
    foreach ($orders as $order)
    {
        $csvExport->write($order);
    }
    $csvExport->close();
    return;

}

It crashes if the file already exists, thus the unlink-command.



来源:https://stackoverflow.com/questions/10307048/how-to-force-download-a-csv-file-in-symfony-2-using-response-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!