CakePHP 4 に mPDF 8 を導入してテンプレートから PDF を生成

はじめに

業務用システムなどを開発していると、様々な書類を PDF 形式で発行する場合があると思います。

今日は CakePHP 4 で mPDF 8 を使い、PDFを生成してダウンロードする方法をご紹介します。

CakePHP
4.0.3
mPDF
8.0.5
PHP
7.3.11
OS
Win 10 Home
目次
  1. mPDF を Composer でインストール
  2. PDFをダウンロードするサンプル
  3. 日本語を表示可能に
  4. テンプレートを使って PDF 生成
  5. おわりに

1. mPDF を Composer でインストール

下記公式ドキュメントにある通り composer でインストールします。

ドキュメントには v7.x などの表記がありますが、インストールされるバージョンは 8.0 です。

> cd \path\to\cakephp4
> composer require mpdf/mpdf

2. PDFをダウンロードするサンプル

最初のサンプルとして PDF ファイルを生成し、ダウンロードさせるプログラムを作ります。

/src/Controller/SampleController.php
<?php
declare(strict_types=1);

namespace App\Controller;

use Mpdf\Mpdf;

class SampleController extends AppController
{
  public function index()
  {
    $mpdf = new Mpdf();
    $mpdf->WriteHTML('<h1>Hello world!</h1>');
    $encodedName = rawurlencode('サンプル.pdf');

    return $this->response->withType('pdf')
      ->withHeader('Content-Disposition', "attachment;filename*=UTF-8''{$encodedName}")
      ->withStringBody($mpdf->Output('', 'S'));
  }
}

今回は withStringBody()$mpdf->Ouput('', 'S') を組み合わせているのがポイントです。Output() のオプションは mPDF の公式ドキュメントで確認することが出来ます。

下記サンプルコードのように $mpdf->Output('サンプル.pdf', 'D') を使えば簡単そうですが、そうしないのは、コントローラの中で何かを出力すること(echo や header() など)を避けるためです。

避けたほうがいい理由の詳細は割愛しますが、例えば $mpdf->Output('サンプル.pdf', 'D') を使うと logs/error.log に下記のようなメッセージが出ます。

/src/Controller/SampleController.php ※よくない例
public function index()
{
  $mpdf = new Mpdf();
  $mpdf->WriteHTML('<h1>Hello world!</h1>');
  // ▼ このようにすると error.log に下記 warning が出る
  $mpdf->Output('サンプル.pdf', 'D');
}
/logs/error.log
2020-02-15 12:34:56 Warning: Warning (512): Unable to emit headers. Headers sent in file=path\to\cakephp4\vendor\mpdf\mpdf\src\Mpdf.php line=9464 in [path\to\cakephp4\vendor\cakephp\cakephp\src\Http\ResponseEmitter.php, line 72]
Request URL: /sample
Client IP: 127.0.0.1
Trace:
Cake\Error\BaseErrorHandler::handleError() - CORE\src\Error\BaseErrorHandler.php, line 188
Cake\Http\ResponseEmitter::emit() - CORE\src\Http\ResponseEmitter.php, line 72
Cake\Http\Server::emit() - CORE\src\Http\Server.php, line 130
[main] - ROOT\webroot\index.php, line 40

3. 日本語を表示可能に

実はこのままだと日本語が表示されません。上記コードの「'<h1>Hello world!</h1>'」の箇所に日本語を入れると文字化けします。

日本語を表示するためには、フォントをダウンロードして、それを読み込む必要があります。下記サンプルコードを実行するには、IPA フォントの「IPA Pゴシック(Ver.003.03)」をダウンロードして /data/font/ipagp.ttf に置いてください。

フォントファイルはサイト閲覧者にアクセスさせたくないので /webroot には置かない方が良いです。

public function index()
{
  // ▼ここを変更
  $mpdf = new Mpdf([
    'fontDir' => [ROOT . DS . 'data' . DS . 'font'],
    'fontdata' => [
      'ipa' => ['R' => 'ipagp.ttf'],
    ],
    'default_font' => 'ipa',
  ]);
  $mpdf->WriteHTML('<h1>こんにちは、世界!</h1>');

  ...
}

フォントファイルを vender/mpdf/mpdf/ttfonts に追加する例を見かけますが、これはあまりおススメしません。composer で管理する vendeor フォルダの中は変更を加えないほうが良いと思います。

mpdf に含まれるフォントも併せて使いたい場合は、公式ドキュメントにあるとおり array_merge() などを使って対応してください。

4. テンプレートを使って PDF 生成

CakePHP 4 のテンプレートを使って PDF を生成してみます。今回は下記のように実装しました。

/templates/Sample/my_pdf.php
<html>
  <head>
    <?= $this->Html->css(['sample/my_pdf.css']) ?>
  </head>
  <body>
    <h1><?= $title ?></h1>
    <p>サンプル用のPDFです</p>
  </body>
</html>
/webroot/css/sample/my_pdf.css
h1 {
  color: #f00;
}
/src/Controller/SampleController.php
<?php
declare(strict_types=1);

namespace App\Controller;

use Cake\View\View; // ← これを追加
use Mpdf\Mpdf;

class SampleController extends AppController
{
  public function index()
  {
    $mpdf = new Mpdf([
      'fontDir' => [ROOT . DS . 'data' . DS . 'font'],
      'fontdata' => [
        'ipa' => ['R' => 'ipagp.ttf'],
      ],
      'default_font' => 'ipa',
    ]);
    // ▼ここを変更
    $view = new View();
    $view->set('title', 'こんにちは、世界!');
    $template = 'Sample/my_pdf';
    $layout = false;
    $content = $view->render($template, $layout);
    $mpdf->WriteHTML($content);
    $encodedName = rawurlencode('サンプル.pdf');

    return $this->response->withType('pdf')
      ->withHeader('Content-Disposition', "attachment;filename*=UTF-8''{$encodedName}")
      ->withStringBody($mpdf->Output('', 'S'));
  }
}

テンプレートの内容を View クラスの render() 関数を使ってテキストとして取得しています。$template、$layout 変数に入れてから引数に渡しているのは、コードを分かりやすくするためです。

mPDF で使用できる HTML タグと CSS は公式ドキュメントの下記ページに書いてあります。

5. おわりに

今回は mPDF の使い方をご説明しましたが、肝はレスポンスオブジェクトを生成して返すところかなと思います。CakePHP や mPDF に限らずですが、フレームワークと一緒にライブラリを使うときには、ただ説明書通りに使うのではなく、フレームワークに合わせて使うことが大切かなと思います。

そのためには、フレームワークの仕組みも把握しなければいけないので、最初はなかなか難しいんですけどね。

このブログの記事が、少しでも役に立てばいいなと思っています。