Generating PDFs with CakePHP

The recommended approach is to use the CakePHP plugin CakePdf for this.
With that plugin it is "a piece of cake" to output HTML views as PDF or render them to files for download/emailing etc.

You can obviously just use an approach just like this article describes.
But this makes the code quite dependent on the actual rendering library used isn’t very DRY when there is more than a single page that needs PDF rendering.

Note: This article is about generating PDF files from HTML templates. If you already have PDF files you just want to output or serve as download, then this article is not for you. See serving-views-as-files-in-cake2 then.

Demo

A live demo can be found in my sandbox app. It also helps to compare the different engines.

Installation

First you need to decide on what engine you want to go with.

WkHtmlToPdf is the fastest, but it also depends on CLI and the binaries being installed properly. So it might work differently or not at all on different OS. But using binary files it should work out of the both on both UNIX and Windows. The latter just needs a single exe file drag-and-dropped on your system. wkhtmltopdf.org/downloads.html contains such a ready-to-use binary.
You can then link it in your Configure set-up for CakePdf:

Configure::write('CakePdf.binary', YOUR_PATH_TO . 'wkhtmltopdf\wkhtmltopdf.exe');

For UNIX those precombiled binaries on that download page might not work and a simple apt-get install wkhtmltopdf usually won’t do the trick. You need to manually compile it in this case. But on most systems it should be fine. Don’t forget to adjust the path to the binary here, as well, if it is not the default one:

Configure::write('CakePdf.binary', YOUR_PATH_TO . 'wkhtmltopdf/bin/wkhtmltopdf');

I usually go with DomPdf as it is quite reliable, and (as PHP code) even though slower than a native approach will work flawlessly with all OS and setups without additional dependencies. If speed really is an issue you might want to reconsider using the server internal libraries. But for me generating invoices and such never is time-critical and usually done asynchronous using background tasks via shells 🙂

For DomPdf one important setting when using images in your PDFs is to "enable remote":

define('DOMPDF_ENABLE_REMOTE', true);

Either way don’t forget the rest of the settings for CakePdf – it could look like this:

$config['CakePdf'] = array(
    'engine' => 'CakePdf.DomPdf',
    'options' => ...,
    'margin' => ...,
    'orientation' => 'portrait',
);

The official documentation on the installation part tells you all you need to know. Check it out.

Advanced Setup

The recommended way is to use CakePlugin::load('CakePdf', array('bootstrap' => true, 'routes' => true)); which is supposed to take care of automatic View class switching.

If you don’t want to load the plugin bootstrap/routes and rather do it manually you need to automatically map all .pdf extension URLs to the CakePdf plugin PdfView ourselves:

public $components = array(
    'RequestHandler' => array(
        'viewClassMap' => array('pdf' => 'CakePdf.Pdf')
    )
);

This can also be useful if you want to overwite/extend the PdfView class for some reason (MyCakePdf etc).

Skipping the plugin routes file is also useful, if you already have extension settings in your routes. You can then simply update your APP/Config/routes.php:

Router::parseExtensions();
Router::setExtensions(array('json', 'xml', 'rss', 'pdf')); // We just add pdf to the already defined ones

With both routes and bootstrap defined manually, CakePlugin::load('CakePdf'); suffices.

Usage

The official documentation already states that, so I won’t go into much detail. For the sake of completeness a short example:

public function view($id) {
    $this->pdfConfig = array(
        'filename' => 'invoice',
        'download' => (bool)$this->request->query('download')
    );
    $invoice = $this->Invoice->find('first', array('conditions' => array('id' => $id)));
    $this->set(compact('invoice');
}

If you use my Tools plugin, you can just use $invoice = $this->Invoice->get($id) here.

Make sure you have a basic pdf layout in /View/Layouts/pdf/default.ctp and a view template for the PDF in /View/Invoices/pdf/view.ctp

Now if you allow access without extension (/invoices/view/1) the action can render a normal HTML page with the invoice in a table like form.

If you link it as /invoices/view/1.pdf it will automatically switch the view class to PdfView and render it as PDF file:

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

Note the trick with download using the query string for it. If you link to it with ?download=1 it will trigger the download instead of just displaying it:

$this->Html->link('Download PDF', array(
        'action' => 'view', $id, 'ext' => 'pdf', '?' => array('download' => 1)
));

This way the action can do both automatically.

Tips

URLs and Paths

For all engines to work with your URLs and images outputted by helpers and therefore with schema-less absolute URLs (/controller/action/ and /img/foo.jpg) this little modification in your AppHelper is quite useful:

/**
 * Overwrite to make URLs absolute for PDF content.
 *
 * @param mixed $url
 * @param bool $full
 * @return string
 */
public function url($url = null, $full = false) {
    if (!empty($this->request->params['ext']) && $this->request->params['ext'] === 'pdf') {
        $full = true;
    }
    return parent::url($url, $full);
}

/**
 * Overwrite to make paths for assets absolute so they can be found by the PDF engine.
 *
 * @param string $path
 * @param array $options
 * @return string
 */
public function assetUrl($path, $options = array()) {
    if (!empty($this->request->params['ext']) && $this->request->params['ext'] === 'pdf') {
            $options['fullBase'] = true;
    }
    return parent::assetUrl($path, $options);
}

This will make them absolute including the full base (schema + domain) in PDF context.

Filename and downloading

If you don’t force downloading and display the PDF, make sure you read the "eastereggs" part of serving-views-as-files-in-cake2/ regarding an additional passed param here to actually make the downloaded file name what you want it to be.

12 Comments

  1. Hi. Thank you for this tutorial. However I have some difficulties in including external style sheet and images. I already modified the AppHelper. When I include an image, the pdf is just blank grey. I also set DOMPDF_ENABLE_REMOTE to true. I don’t know what else to modify. Thank you.

  2. You will need to provide some more details, e.g. the img HTML code in the resulting PDF view for example.

  3. Hi Mark,
    Many thanks for your article.
    Have you tried cakepdf with CakePHP v3 ? it seems that "pdf" router parsing not working.
    Best regards.
    Cyb

  4. I am working on dompdf on cakephp3, too. I got the layout include dompdf config file, but the result is a CakePHP 3 error "Requested HTML document contains no data." and the stack shows:
    ⟩ Frame_Tree->build_tree
    ROOT/vendor/dompdf/include/dompdf.cls.php, line 676
    Thanks for your help.

  5. How do you change default font/fontset? I have to use special characters like öäå and I would need to use Helvetica of something that supports those characters. I already tried to change encoding to "ISO-8859-1" but the PDF -page keeps coming empty because of the characters. If I encode characters to UTF-8 string by string it works, but it is not what I am looking for. I would like to have views of my choice and it would show all the characters I need for Finnish language.

  6. Hi
    Thank you for the amazing plugin however am experiencing a slight error while trying to view the pdf file
    Am using the plugin with cakephp 3.1 while trying to access the pdf document (i.e chrome) I get such an error ‘failed to load PDF Document’
    I would appreciate any assistance
    Thank you?

  7. Sadly following the github Docs and this example it always throws this …

    Error: Call to a member function find() on boolean
    File C:\xampp\htdocs\caketest\src\Controller\InvoicesController.php
    Line: 21

  8. I got error:
    The view for AppointmentsController::getPdfList() was not found.
    /U../src/Template/Appointments/get_pdf_list.ctp not found

    But I have get_pdf_list.ctp in source -src/../Appointments/pdf/get_pdf_list.ctp

  9. Thank you for this tutorial. i have tried to follow up though i have a little challenge. am new to cakephp, in which of the app file will the configuration be (bootstrap, app, appController or appView.

    thanks in anticipation.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.