RSS
 
21. Nov. 2011

Serving views as files in Cake2

21 Nov

Actually, its not that different in Cake1.3. But as I just played around with it in 2.0, I will stick to that version for examples.

How to start

Skip this, if you want to cut to the chase.

In your routes.php you need to add Router::parseExtensions(); (or only specific ones). That tells cake that urls ending with “.xyz” will be served as files (either inline or as download attachment).

Setup

Let’s say you want to display an invoice as pdf. The normal url is /invoices/view/1. Now, we set a link in the view to the file like so:

$this->Html->link('View as PDF', array('action'=>'view', 'ext'=>'pdf', 1));

Since cake automatically detects that this will be a pdf file, it will a) set the correct header (application/pdf) b) will try to find the specific pdf view in /View/Invoices/pdf/index.ctp and the pdf layout in /View/Layouts/pdf/default.ctp

That’ all :)

Download it right away

Files like pdf can be displayed inline. So the browser will usually not force you to download it. If you want this, though, you need to call $this->request->download($filename); in your controller action or in your layout.

Note: If your browser does not understand the file format (in this case pdf) it will probably trigger the download right away.

Browser bugs and easter eggs

Well, you could also call it a bug. But during my tryouts I found out that files served inline (Content-Disposition: inline; filename=”…”) will not use the given filename on save. They will be saved with the name in the url instead. In the above example it would be “1.pdf”. I did some research: Thats a well known browser deficiency that nobody yet fixed. Or so it seems. Ok, but nobody wants his invoice to be “1.pdf”. So what can we do about it?

I found a pretty well working workaround:

$this->Html->link('View as PDF', array('action'=>'view', 'ext'=>'pdf', 1, 'invoice-2011-11-01_some_customer_tag'));
As you can see we simply add our filename to the url – after the id (!).

Since it is only “filename cosmetics” we dont need to add this second passed param to our method:

public function view($id = null) {}
It will be ignored in the action itself.

So the generated url is /invoices/view/1/invoice-2011-11-01_some_customer_tag.pdf and will result in a file saved as “invoice-2011-11-01_some_customer_tag.pdf”. Job done.

Let me know what you think.

Example for PDFs

A pretty quick example how to output your content as pdf using DOMPDF.

In your layout (default.ctp in /pdf/):

App::import('Vendor', 'dompdf/dompdf.php');
$dompdf = new DOMPDF();
$dompdf->load_html(utf8_decode($content_for_layout), Configure::read('App.encoding'));
$dompdf->render();
echo $dompdf->output();
For me it seemed that without utf8_decode the DOMPDF lib seems to be buggy – although it claims to support utf8.

Example for ics (ical calendar) files

Just a very basic example using an IcalHelper from my Tools Plugin.

Please note: There is a pending ticket on this. Until then you either need to manually patch the ResponseClass (quick solution) by adding the missing mimetype 'ics' => 'text/calendar', or define it in your Controller via $this->response->type(array('ics' => 'text/calendar')); and set all paths manually… Hopefully the second issue for unknown mimetypes will be addressed soon, as well.

Controller code:

$reservation = ...;
$this->plugin = 'Tools'; //not necessary, only that I want to store the layout once (in the plugin)
$this->set(compact('reservation'));
$this->helpers[] = 'Tools.Ical';

Layout (in /Plugin/Tools/View/Layouts/ics/default.ctp):

<?php echo $content_for_layout; ?>

View (in /View/Reservations/ics/export.ctp):

$data = array(
    'start' => $this->Time->toAtom($reservation['Reservation']['time']),
    'end' => $this->Time->toAtom(strtotime($reservation['Reservation']['time'])+HOUR),
    'summary' => 'Reservation',
    'description' => $reservation['Reservation']['headcount'].' persons @ location foo',
    'organizer' => 'CEO',
    'class' => 'public',
    'timestamp' => '2010-10-08 22:23:34',
    'id' => 'reservation-'.$reservation['Reservation']['id'],
    'location' => $reservation['Restaurant']['address'],
);
$this->Ical->add($data);
echo $this->Ical->generate();

And all that is left again is to link this action accordingly:

echo $this->Html->link($this->Html->image('icons/calendar.gif', array('title'=>'Download reservation as ical-file')), array('action'=>'export', $reservation['Reservation']['id'], 'ext'=>'ical'), array('escape'=>false));

 
7 Comments

Posted by Mark in CakePHP

 

Tags: , , , ,

Leave a Reply

Tip:
If you need to post a piece of code use {code type=php}...{/code}.
Allowed types are "php", "mysql", "html", "js", "css".

Please do not escape your post (leave all ", <, > and & as they are!). If you have encoded characters and need to reverse ("decode") it, you can do that here!
 

 
  1. S.Morteza

    November 27, 2011 at 05:51

    Hi Mark, so glad to your update .
    good article.
    thanks.

     
  2. Adrian Porras

    February 9, 2012 at 04:45

    Hi Mark, thanks for the article, I am almost getting it to work but the code on the layout is not working it gives me an error for your code on the creation of the DOMPDF class.

    $dompdf = new DOMPDF();

    It gives me an error that says:

    "Fatal error: Class 'DOMPDF' not found"

    Any ideas?

     
  3. Mark

    February 9, 2012 at 10:06

    did you App::import correctly? Where is the class? .. exactly (lower/uppercase matters)

     
  4. pgpMagpie

    February 10, 2012 at 14:57

    I'm was problems importing the vendor too. Downloaded latest version of dompdf and extracted to /app/Vendor/dompdf.

    In layout I had:
    App::import('Vendor', 'dompdf/dompdf.php');

    This failed silently as App::import simply returns false is file not found. I got the import working by rmoving the file extension, this left me with:
    App::import('Vendor', 'dompdf/dompdf');

    Now I am getting an error500, but at least that's some progress.

    P.S. I am using Cake 2.1

     
  5. phpMagpie

    February 10, 2012 at 15:42

    My previous comment had a few typos in, sorry :P

    I managed to get a PDF displaying by replacing the following:
    App::import('Vendor', 'dompdf/dompdf.php');

    with:
    require_once(APP . 'Vendor' . DS . 'dompdf' . DS . 'dompdf_config.inc.php');

    I came to this conclusion after realising some of the checks in dompdf.php were throwing exceptions that Cake was trying and failing to intercept.

    I then went searching for how to manually include just the config file and ended up reading a StockOverflow response from Jose Lorenzo:
    http://stackoverflow.com/questions/8158129/loading-vendor-files-in-cakephp-2-0

    Hope this helps, or if you can think of anything wrong with this method please advise me.

     
  6. phpMagpie

    February 10, 2012 at 18:52

    Mark,

    Setup is working locally (WAMP), but when uploaded to server getting open_basedir errors.

    I assume this is something to do with paths you can specify in dompdf_config.custom.inc.php and wondered if you could share your config settings so I can replicate?

    Thanks, Paul.

     
  7. phpMagpie

    February 10, 2012 at 20:53

    Scratch that … not sure why, but all appears to be working fine now.

    Thanks for the heads up on your post Mark!