61

I'm trying to download a file when a user clicks on download link.

In Controller:

    $response = new Response();
    $response->headers->set('Content-type', 'application/octect-stream');
    $response->headers->set('Content-Disposition', sprintf('attachment; filename="%s"', $filename));
    $response->headers->set('Content-Length', filesize($filename));

    return $response;

This is opening the dialog box to save the file, but it says the file is 0 bytes. And changing it to:

        $response = new Response();
        $response->headers->set('Content-type', 'application/octect-stream');
        $response->headers->set('Content-Disposition', sprintf('attachment; filename="%s"', $filename));
        $response->headers->set('Content-Length', filesize($filename));
        $response->headers->set('Content-Transfer-Encoding', 'binary');
        $response->setContent(readfile($filename));

        return $response;

I get a bunch of weird characters instead of the file download dialog box.

Finally, switching the "setContent" line to:

    $response->setContent(file_get_contents($filename));

It returns a PHP error:

Fatal error: Allowed memory size...

Any clues on how to achieve this? I've done it before in PHP (wihtout MVC), but I don't know what can be missing to do it through Symfony2...

Maybe the solution is setting the memory_limit in PHP.INI, but I guess it´s not the best practice...

j0k
  • 22,600
  • 28
  • 79
  • 90
Xavi
  • 1,555
  • 2
  • 13
  • 15

7 Answers7

92

The most comfortable solution is

use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;

$response = new BinaryFileResponse($file);
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT);

return $response;
Darryl Hein
  • 142,451
  • 95
  • 218
  • 261
denkweite
  • 1,053
  • 7
  • 8
  • 1
    I agree to this one. Regarding the documentation: http://symfony.com/doc/current/components/http_foundation/introduction.html#serving-files – Rafał Mnich May 14 '16 at 20:29
  • Great answer, thanks. I've updated the answer to include the import of the ResponseHeaderBag class. – Egg May 16 '16 at 16:17
  • It's the first time I can download a file in less than 584 lines of code! Thank you! – Genarito Dec 13 '21 at 14:11
50

I finally solved this without X-SendFile (which is probably the best practice). Anyway, for those who can't get X-Sendfile apache module to work (shared hosting), here's a solution:

// Generate response
$response = new Response();

// Set headers
$response->headers->set('Cache-Control', 'private');
$response->headers->set('Content-type', mime_content_type($filename));
$response->headers->set('Content-Disposition', 'attachment; filename="' . basename($filename) . '";');
$response->headers->set('Content-length', filesize($filename));

// Send headers before outputting anything
$response->sendHeaders();

$response->setContent(file_get_contents($filename));

return $response;
Dharman
  • 30,962
  • 25
  • 85
  • 135
Xavi
  • 1,555
  • 2
  • 13
  • 15
15

You shouldn't use PHP for downloading files because it's a task for an Apache or Nginx server. Best option is to use X-Accel-Redirect (in case of Nginx) / X-Sendfile (in case of Apache) headers for file downloading.

Following action snippet can be used with configured Nginx to download files from Symfony2:

return new Response('', 200, array('X-Accel-Redirect' => $filename));

UPD1: Code for apache with configured mod_xsendfile:

return new Response('', 200, array(
    'X-Sendfile'          => $filename,
    'Content-type'        => 'application/octet-stream',
    'Content-Disposition' => sprintf('attachment; filename="%s"', $filename))
);
Anjana Silva
  • 8,353
  • 4
  • 51
  • 54
lisachenko
  • 5,952
  • 3
  • 31
  • 51
  • Thank you Alexander for your reply! Do you know how to manage with X-Sendfile? – Xavi Oct 22 '12 at 13:48
  • `X-Sendfile` is a typical header and works like `X-Accel-Redirect`. Linked answer is here: http://stackoverflow.com/questions/80186/using-x-sendfile-with-apache-php – lisachenko Oct 22 '12 at 14:45
9

Don't know if it can help but it's application/octet-stream not application/octect-stream

Pascal
  • 1,288
  • 8
  • 13
9

As of Symfony 3.2 you can use the file() controller helper which is a shortcut for creating a BinaryFileResponse as mentioned in a previous answer:

public function fileAction()
{
    // send the file contents and force the browser to download it
    return $this->file('/path/to/some_file.pdf');
}
COil
  • 7,201
  • 2
  • 50
  • 98
6

+1 for alexander response.

But if you can't use X-Sendfile, you should use the BinaryFileResponse added in the 2.2: http://symfony.com/doc/current/components/http_foundation/introduction.html#serving-files

In my project the result is

$response = new \Symfony\Component\HttpFoundation\BinaryFileResponse($dir .DIRECTORY_SEPARATOR. $zipName);

$d = $response->headers->makeDisposition(
    ResponseHeaderBag::DISPOSITION_ATTACHMENT,
    $zipName
   );

$response->headers->set('Content-Disposition', $d);

return $response;
Jaycreation
  • 2,029
  • 1
  • 15
  • 30
3

For those who don't have the option of setting headers:

The download attribute may help depending on which browsers you need to support:

<a href="{file url}" download>

or

<a href="{file url}" download="{a different file name}">

This is not supported in all legacy browsers. See this page for browser support:

https://www.w3schools.com/tags/att_a_download.asp

Henry
  • 7,721
  • 2
  • 38
  • 38