5

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:

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(); ?>
Community
  • 1
  • 1
falsch
  • 1,425
  • 2
  • 14
  • 21
  • 1
    Have you tried with `return $this->renderText($response->getContent());` instead of `return sfView::NONE;` ? – j0k Jan 03 '13 at 17:15
  • 1
    The `readfile` approach is better than sending the contents to a view, since the former has built-in buffering, whereas the latter must be held entirely in memory, and so will be slower. – halfer Jan 03 '13 at 18:51
  • `return $this->renderText($response->getContent());` does not work in my case. it throws no exception but a "file not found" error from the browser. – falsch Jan 04 '13 at 07:44

3 Answers3

6

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!

falsch
  • 1,425
  • 2
  • 14
  • 21
  • Outstanding. Note the template file actually need not be present. – cmc Jun 27 '13 at 14:59
  • If you configure the module using templates, then it must be present. Only you set them false or in controller null, you need no template file otherwise a waring will be logged (header sent before) – falsch Jun 28 '13 at 08:43
1

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

Community
  • 1
  • 1
Simon Cast
  • 255
  • 1
  • 9
  • Your second solution will works but fill the error log with `PHP Warning: Cannot modify header information - headers already sent` messages. So that is not a really good solution. Only if you exit the output with die() after sendContend() the warning will not thrown. – falsch Jan 04 '13 at 07:46
  • Oh sorry, missed the `$this->getResponse()->sendHttpHeaders()`. I've edited the answer to include it. – Simon Cast Jan 04 '13 at 09:03
  • That is not the point. With `$this->getResponse()->sendContent();` you will send the header except you do not have sent headers before, but with the return statement imho will the next header sent and then the warning thrown. – falsch Jan 04 '13 at 09:32
  • Ok, I'm not really sure what you are on about. I send the headers and then send the content and do not get any of the PHP errors. Are you sure it isn't something else occurring in your code? – Simon Cast Jan 04 '13 at 17:06
0

You can do it with plain php functions as well:

public function executeIndex(sfWebRequest $request)
{
  while(ob_get_level())
  {
    ob_end_clean();
  }

  // use plain php functions to
  // setup headers
  // ...
  // and read and echo the file

  throw new sfStopException();
}
1ed
  • 3,668
  • 15
  • 25