Serving PDF Files through Symfony Controllers
3. August 2009 in General, Symfony
A lot of web applications offer to download some kind of PHP file. Maybe you want to implement some kind of export feature or you have paid content in form of a PDF or other media or you just want to secure your PDF files in the same way as the rest of your application (i.e. username/password). If this is the case you wouldn’t place your PDF-files in a subfolder of the web-Directory of your Symfony project, because all these file can be accessed directly via browser.
So how to serve your PDF-Files through a Symfony controller? it is an easy one. Just assume you put your pdf-files in a directory called “media” on the root-level of your Symfony-project.
root |-- apps |-- cache |-- config | |-- demo.pdf |-- media |-- lib |-- web
These files cannot be reached by Browser, so we need a new routing information for Symfony to get them accessible via the webbrowser. For that open the routing.yml of your application and add the following rule topmost in this file.
mediafile:
url: /media/:filename.pdf/*
param: { module: media, action: show }
From now on if you try to access a PDF-File in the media-folder (i.e. http://domain/media/demo.pdf) the media-Module with the action show is called. That was easy but how to implement this action. First you need to create the media module with the symfony command line tool:
$> symfony generate:module appname media
Afterwards add the following method to the class in the media/actions/actions.class.php file:
public function executeShow(sfWebRequest $request) {
// being sure no other content wil be output
$this->setLayout(false);
sfConfig::set('sf_web_debug', false);
$pdfpath = sfConfig::get('sf_root_dir').DIRECTORY_SEPARATOR.'media'
.DIRECTORY_SEPARATOR
.$request->getParameter('filename').'.pdf';
// check if the file exists
$this->forward404Unless(file_exists($pdfpath));
// Adding the file to the Response object
$this->getResponse()->clearHttpHeaders();
$this->getResponse()->setHttpHeader('Pragma: public', true);
$this->getResponse()->setContentType('application/pdf');
$this->getResponse()->sendHttpHeaders();
$this->getResponse()->setContent(readfile($pdfpath));
return sfView::NONE;
}
Thats it! You can improve this Action method a little bit. For example you can add a “download”-switch. If this switch is set to 1 the file is downloaded instead of shown in the browser (i.e. http://domain/media/demo.pdf/download/1).
public function executeShow(sfWebRequest $request) {
// being sure no other content wil be output
$this->setLayout(false);
sfConfig::set('sf_web_debug', false);
$pdfpath = sfConfig::get('sf_root_dir').DIRECTORY_SEPARATOR.'media'
.DIRECTORY_SEPARATOR
.$request->getParameter('filename').'.pdf';
// check if the file exists
$this->forward404Unless(file_exists($pdfpath));
// Adding the file to the Response object
$this->getResponse()->clearHttpHeaders();
$this->getResponse()->setHttpHeader('Pragma: public', true);
$this->getResponse()->setContentType('application/pdf');
// download-switch
if ($request->getParameter('download', 0) == 1) {
$this->getResponse()->setHttpHeader('Content-Disposition',
'attachment; filename="'.
$request->getParameter('filename').'"');
}
$this->getResponse()->sendHttpHeaders();
$this->getResponse()->setContent(readfile($pdfpath));
return sfView::NONE;
}
As we now use a Symfony-Controller you can do just use the complete Symfony infrastructure. You like to secure the PDF-files? Just enable the Symfony security mechanisms (is_secure: on in the security.yml) and only logged in users can download them. In the same way you can create paid content. Or you can add watermarks to your PDF using Zend_Pdf for example.
Hope this is helpful for you.
BarbouK said on 3. August 2009
Thank you very much for these clear explanations !
Exactly what I needed
manubing said on 3. August 2009
very helpful, thank you!
Leonardo Diaz said on 3. August 2009
i dont know if this is the best option to do it, I mean loading symfony by it’s controller (index.php), and even more how much memory will be used on this instruction (important when using shared hosts):
$this->getResponse()->setContent(readfile($pdfpath));
what if the pdf is huge???
on the other hand I think this is the only way to do some things like statistics and as you say some way to control access to the files.
Maybe a different approach like copying the accessed file to a temp location, and executing some redirection so apache server (or any other) can handle the download without executing php script for it.
Thanks for the article, is an excelent starting point for looking on this field.
Aro said on 27. August 2009
thx, it’s Exactly what i need!! I’m changing from java to php and symfony and I really need that. thx again
Hassan said on 26. October 2009
Thank you very much for these clear explanations!
Exactly what I needed
Seb. said on 16. November 2009
Nice wrap up. You might also set the headers in the template:
$sf_response->clearHttpHeaders();
$sf_response->setContentType(‘application/pdf’);
$sf_response->setHttpHeader(‘Content-Disposition’, ‘attachment; filename=’ . $pdfFileName);
readfile($pdfFilePath);
Sean said on 23. December 2009
There’s a potential security hole here that you may want to be wary of – the file path you accept is handed straight from the user… this is dangerous!
For example, say I visited
http://domain/media/..%2Fconfig%2Fdatabases.yml/download/1
Your script would then push out the content of your database config file – including your DB username, password, and domain! And then they can do all sorts of malicious things.
Essentially, since you’re not filtering your files and just concatenating it with the directory path, it’s possible to use “..” to navigate around the file system, and choose any file you want.
A better option is to provide some sort of restricted key that maps to a file path. That way, you have very tight control over which files will be served.
Happy coding!
Kristof Taveirne said on 2. January 2010
ALL RIGHT!
Thanks a lot!
Exactly what I was looking for!
Peter said on 13. April 2010
Great article Timo! Thanks a lot
Keep them coming, symfony is great.
Prasad said on 28. August 2010
Thanks, and very valid point raised by Sean
Oscar B. said on 8. December 2010
Delicious-ed >B
Thanks a lot, just what I needed, and it worked on first try!!!!!!!!!!
haha!!
Tam said on 11. January 2011
thank you sir,
I’m looking for a way to write an image to the controller response. this seems to be what I need.