Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony 1.4: Best way provide a file download without using a template/view

Of cource some other people have discussed these problem on stackoverflow, but not all ansers works for me and often they do not provide a version of there symfony installation.

Topics I read:

  • Send attachment/Download file from Symfony action
  • How to download a file on clicking thefile path using PHP-Symfony?
  • symfony: setHttpHeader() doesn't work, header() does

Thats the point for me to ask how you handle file downloads in symfony 1.4 (without using the view)? In all my use cases I need a template file to render the response. If I send the response due the controller there is the only possibility to send it without an php error (header already sent) with

controller:

/** @var $response sfWebResponse */
$response = $this->getResponse();
$response->clearHttpHeaders();
$response->setContentType($mimeType);
$response->setHttpHeader('Content-Disposition', 'attachment; filename="' . basename($filePath) . '"');
$response->setHttpHeader('Content-Description', 'File Transfer');
$response->setHttpHeader('Content-Transfer-Encoding', 'binary');
$response->setHttpHeader('Content-Length', filesize($filePath));
$response->setHttpHeader('Cache-Control', 'public, must-revalidate');
$response->setHttpHeader('Pragma', 'public');
$response->sendHttpHeaders();

readfile($filePath); die();

This works without an template file. But imho this is not so pretty coding.

The alternative way with the template:

controller:

 /** @var $response sfWebResponse */
$response = $this->getResponse();
$response->clearHttpHeaders();
$response->setContentType($mimeType);
$response->setHttpHeader('Content-Disposition', 'attachment; filename="' . basename($filePath) . '"');
$response->setHttpHeader('Content-Description', 'File Transfer');
$response->setHttpHeader('Content-Transfer-Encoding', 'binary');
$response->setHttpHeader('Content-Length', filesize($filePath));
$response->setHttpHeader('Cache-Control', 'public, must-revalidate');
$response->setHttpHeader('Pragma', 'public');
$response->setContent(file_get_contents($filePath));
$response->sendHttpHeaders();

return sfView::NONE;

view:

<?php echo $sf_response->getRawValue()->getContent(); ?>
like image 379
falsch Avatar asked Jan 03 '13 16:01

falsch


2 Answers

My prefered solution

$filePath = $document->getAbsoluteFilePath();
$mimeType = mime_content_type($filePath);

/** @var $response sfWebResponse */
$response = $this->getResponse();
$response->clearHttpHeaders();
$response->setContentType($mimeType);
$response->setHttpHeader('Content-Disposition', 'attachment; filename="' . basename($filePath) . '"');
$response->setHttpHeader('Content-Description', 'File Transfer');
$response->setHttpHeader('Content-Transfer-Encoding', 'binary');
$response->setHttpHeader('Content-Length', filesize($filePath));
$response->setHttpHeader('Cache-Control', 'public, must-revalidate');
// if https then always give a Pragma header like this  to overwrite the "pragma: no-cache" header which
// will hint IE8 from caching the file during download and leads to a download error!!!
$response->setHttpHeader('Pragma', 'public');
//$response->setContent(file_get_contents($filePath)); # will produce a memory limit exhausted error
$response->sendHttpHeaders();

ob_end_flush();
return $this->renderText(readfile($filePath));

No need of use a template file. Usage of the symfony standard behaviour. Important: The template file must present!

like image 135
falsch Avatar answered Sep 28 '22 10:09

falsch


I've used two methods depending on the content of the file. For documents such as Excel docs I usually use this approach:

$this->getResponse()->clearHttpHeaders();
$this->getResponse()->setHttpHeaders('Content-Description','File Transer');
$this->getResponse()->setHttpHeaders('Content-Type','application/vnd.ms-excel'); //this would be based on your file
$this->getResponse()->setHttpHeaders('Content-Disposition','attachment;filename='.$filename); //$filename is name of file on server
$this->getResponse()->setHttpHeaders('Pragma','');
$this->getResponse()->setHttpHeaders('Cache-Control','');
$this->getResponse()->sendHttpHeaders();

$error_reporting = error_reporting(0);
$this->renderText($some_data->save('php://output')); //in this case the $some_data was a PHPExcel writer object but anything that can be saved to a [php://output][1] should work e.g. fwrite()
error_reporting($error_reporting);
return sfView::NONE

The error_reporting switch off and on had to do with using PHPExcel to write to the stream.

The other method I've used uses the sendContent() method of sfResponse. Example of this usage is:

$this->getResponse()->clearHttpheaders();
$this->getResponse()->setHttpHeader('Content-Description','File Transfer');
$this->getResponse()->setHttpHeader('Cache-Control', 'public, must-revalidate, max-age=0');
$this->getResponse()->setHttpHeader('Pragma: public',true);
$this->getResponse()->setHttpHeader('Content-Transfer-Encoding', 'binary'); 
$this->getResponse()->setHttpHeader('Content-length',filesize($filename)) //send the size of the file
$this->getResponse()->setHttpHeader('Content-Type','some_mime_type') // e.g. application/pdf, image/png etc.
$this->getResponse()->setHttpHeader('Content-Disposition','attachment; filename='.$filename) //some filename
$this->getResponse()->sendHttpHeaders(); //edited to add the missed sendHttpHeaders
$this->getResponse()->setContent(readfile($filename));

$this->getResponse()->sendContent();

return sfView::NONE;

Both approaches work and you don't need a template to render the content/file.

Note: Edited to add in $this->getResponse()->sendHttpHeaders() before setting and sending the content

like image 38
Simon Cast Avatar answered Sep 28 '22 10:09

Simon Cast