In previous tutorial, we have learned how to generate PDFs from html and snappy. In this tutorial, we will use the same approach, but apply it in CakePHP 3 framework.

This tactic will enable the CakePHP controllers of your application to generate PDFs with a few lines of code.

Table Of Content

  1. Installing dependencies
  2. Creating the PdfWriter class
  3. Using the PdfWriter class in a controller action
  4. Make it reusable
  5. The end

Installing dependencies

The same as previous email, we will need to install the dependencies, which include wkhtmltopdf and wkhtmltoimage's binary files and the Snappy package.

A gentle reminder: make sure you specify the correct wkhtmltopdf and wkhtmltoimage's binary files according to your operating system. Otherwise it won't work. Here is the list of the binary files again.

In the CakePHP project's root folder, install dependencies via composer require. We are still demonstrating in this in an OSX machine:

composer require profburial/wkhtmltopdf-binaries-osx:0.12.1
composer require knplabs/knp-snappy

Creating the PdfWriter class

Let's create the PdfWriter class, but this time, we will have to place it in a proper CakePHP namespace.

The PdfWriter serves as a utility class, so we will create a new namespace called App\Utility for it.

Create the PdfWriter class file at src/Utility/PdfWriter.php:

<?php

namespace App\Utility;

class PdfWriter
{

}

Next we will configure the binary file path of wkhtmltopdf file in the PdfWriter's constructor:

private $binaryPath;

public function __construct()
{
    $this->binaryPath = ROOT . '/vendor/bin/wkhtmltopdf-amd64-osx';;
}

Next we will create a similar write method, but this time, this method does a bit more than just accepting a path string. It accepts a CakePHP template path and an array variable:

public function write($template, $data)
{

}

The design of this method parameters is that, we will allow user pass a template and its view variables.

Next we will create a Snappy instance as the first line of the method:

$snappy = new Pdf($this->binaryPath);

Then we will get the raw HTML out of a CakePHP view:

$html = (new ViewBuilder())->layout('ajax')->build($data)->render($template);

Note how we use CakePHP's ViewBuilder to render the template's HTML source code. ViewBuilder is a pretty useful class of CakePHP.

Lastly we will generate the PDF by Snappy and return it: return $snappy->getOutputFromHtml($html); Note in this step, we are using a different Snappy API getOutputFromHtmlinstead of generateFromHtml in previous tutorial. The reason is we want to get a PDF as a return value.

Below is the full source code for the PdfWriter class:

<?php

namespace App\Utility;

use Cake\View\ViewBuilder;
use Knp\Snappy\Pdf;

class PdfWriter
{
    private $binaryPath;

    public function __construct()
    {
        $this->binaryPath = ROOT . '/vendor/bin/wkhtmltopdf-amd64-osx';;
    }

    public function write($template, $data)
    {
        $snappy = new Pdf($this->binaryPath);

        $html = (new ViewBuilder())->layout('ajax')->build($data)->render($template);

        return $snappy->getOutputFromHtml($html);
    }
}

Using the PdfWriter class in a controller action

Let's pretend we have a PdfsController controller and we want its generate action to create a PDF using the generate.tcp template as its content, and force user to download the PDF.

This is how we do it in CakePHP 3.

We first create a PdfWriter object and call its write method by supplying template generate.ctp's path and its template data.

$pdfWriter = new PdfWriter();

$pdf = $pdfWriter->write('Pdfs/generate', ['name' => 'xu']);

Next we utilise CakePHP's response object to force user to download the generated PDF:

$this->response->body($pdf);
$this->response->type('pdf');
$this->response->download(sprintf('export-%s.pdf', time()));

return $this->response;

That's all when we need to do to make the generate action a PDF view. Here is the full source code for generate action:

public function generate()
{
    $pdfWriter = new PdfWriter();
    $pdf = $pdfWriter->write('Pdfs/generate', ['name' => 'xu']);
    
    $this->response->body($pdf);
    $this->response->type('pdf');
    $this->response->download(sprintf('export-%s.pdf', time()));
    
    return $this->response;
}

Make it reusable

If you ever need to have more than one PDF view in your CakePHP 3 application. You will soon find out some repeated code in the action code.

If we re-exam the generate() action code, it is not difficult to spot the repetitive code:

$this->response->body($pdf);
$this->response->type('pdf');
$this->response->download(sprintf('export-%s.pdf', time()));
    
return $this->response;

There is some code extraction we can do to make it reusable.

One of the possibilities is to create a generic downloadPdf method in AppController class, since it is the gold-father of all userland controller classes.

Let's do that in AppController class:

public function downloadPdf($template, $vars)
{
    $pdfWriter = new PdfWriter();

    $pdf = $pdfWriter->write($template, $vars);

    $this->response->body($pdf);
    $this->response->type('pdf');
    $this->response->download(sprintf('export-%s.pdf', time()));
    return $this->response;
}

Now we can refactor PdfsController::generate() to:

public function generate()
{
    return $this->downloadPdf('Pdfs/generate', ['name' => 'xu']);
}

Much easier and cleaner! The best part is that we can now create any future PDF view with one line of code in controller's action.

The end

In this tutorial, we have learned how to generate PDF from HTML in CakePHP 3. The approach showed in this tutorial allows us to create a PDF view with one single line of code.