PHP
PDF
laravel
laravel5.5

Laravel-Snappyでpdfを出力できるようになるまで[1/25更新]

1/25 更新

新しい形式のGoogle Charts(=loader.jsを読み込むヤツ)がうまくいかないのは、
どうやらwkhtmltopdfがES6に対応していないことが原因っぽい。
google.charts.load()promiseを使っているようなので、Polyfillでなんとかならないか実験中...(ダメっぽい)

はじめに

Laravel-Snappyでpdfを書き出すまででちょっとつまずいたりしたので、最終的にどうやってできるようになったかの記録。

やること

  • pdfで集計データとかをダウンロードしたい!
  • グラフ入れて!グラフはGoogleChartで書くよ!
  • ってことはJSが動かなきゃだめ!もちろんCSSも!

環境

  • PHP 7.1
  • Laravel 5.5 (Laradock使用)

基本的なやり方はreadmeに書いてありますが、一応順を追って。

とりあえず動かしてみる

Laravel-Snappyのインストール

$ composer require barryvdh/laravel-snappy

Laravelの設定に追加

/config/app.php
...
'providers' => [
    ...
+   Barryvdh\Snappy\ServiceProvider::class, //追加
    ...
],
'aliases' => [
    ...
+   'PDF' => Barryvdh\Snappy\Facades\SnappyPdf::class,
+   'SnappyImage' => Barryvdh\Snappy\Facades\SnappyImage::class,
    ...
],

Laravel-Snappyのconfigファイルを召喚

$ php artisan vendor:publish --provider="Barryvdh\Snappy\ServiceProvider"

SnappyはwkhtmltopdfというGoogle謹製のツールをPHPでイイカンジに使えるようにしたアレです。
なので動作にはwkhtmltopdf(と、wkhtmltoimage)そのものであるバイナリが必要なわけです。

wkhtmltopdfをインストールします

$ composer require h4cc/wkhtmltopdf-amd64
$ composer require h4cc/wkhtmltoimage-amd64

./vender/bin以下にシンボリックリンクを貼ってくれるので、これを使うようにLaravel-Snappy側を設定してあげます。
デフォルトでは/usr/local/bin/wkhtmltopdfを使おうとしていますね。

/config/snappy.php
    'pdf' => array(
        'enabled' => true,
-       'binary'  => '/usr/local/bin/wkhtmltopdf',      //これを消して
+       'binary'  => base_path('vendor/bin/wkhtmltopdf-amd64'),  //これを追加
        'timeout' => false,
        'options' => array(),
        'env'     => array(),
    ),
    'image' => array(
        'enabled' => true,
-       'binary'  => '/usr/local/bin/wkhtmltoimage',    //消して
+       'binary'  => base_path('vendor/bin/wkhtmltoimage-amd64'), //追加
        'timeout' => false,
        'options' => array(),
        'env'     => array(),
    ),

pdf出力するページを用意

PdfController.php
 ...
    public function index()
    {
        $pdf = \PDF::loadView('pdf_tamplate');
        return $pdf->inline('thisis.pdf');  //ブラウザ上で開ける
        // return $pdf->download('thisis.pdf'); //こっちにすると直接ダウンロード
    }
...
pdf_template.blade.php
<h1>ウソみたいだろ。PDFなんだぜ。これで。</h1>

アクセスしてみます。

stderr: "/var/www/vendor/bin/wkhtmltopdf-amd64: error while loading shared libraries: libXrender.so.1: cannot open shared object file: No such file or directory

どうやら世の中はそう甘くないようです。


必要なモジュールとかインストール

libXrender.so.1 とかいうのがなくて怒られていますね。
なお、それを入れたらまた他のものが足りないと怒られました。
で、以下。

$ apt-get install -y libxrender1 libfontconfig1 libxext6 fonts-ipafont

なお、これらはPHPが動作するコンテナ上にインストールする必要があります。
laradockならphp-fpmコンテナですね。よく考えればわかるのですが、ここでしばらくつまづいてしまいました。

fonts-ipafontですが、デフォルトのままでは日本語フォントが存在しないので日本語が表示できません。
ついでに入れときましょう

アクセスしてみましょう。

pdf1.png

あああああああああああああああ

pdfのエンコードを指定

PdfController.php
 ...
    public function index()
    {
        $pdf = \PDF::loadView('pdf_tamplate')
+           ->setOption('encoding', 'utf-8');
        return $pdf->inline('thisis.pdf');  //ブラウザ上で開ける
        // return $pdf->download('thisis.pdf'); //こっちにすると直接ダウンロード
    }
...

pdf2.png

一件落着。

Google Chartでグラフを表示してみる

というわけで、ビューテンプレートをpdfに出力できました。
この上でJSを動かしてカッチョイイグラフをぶちこんでやりましょう。
グラフ表示用にテンプレートを作成してみます。

pizza.blade.php
<html>
<head>
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">
        function init() {
            google.load('visualization', '1.1', {'packages': ['corechart'], callback: drawCharts});
        }

        function drawCharts() {
            drawSampleChart('piechart');
        }

        function drawSampleChart(containerId) {
            var data = google.visualization.arrayToDataTable([
                ['Taste', 'Range per Pizza'],
                ['BBQChicken', 25],
                ['Charcoal-grilled Beef Rib', 25],
                ['Kokuuma Just Meat', 25],
                ['Shrimp Mayo Bacon', 25],
            ]);

            var options = {
                title: 'Would you like to eat a pizza?'
            };

            var chart = new google.visualization.PieChart(document.getElementById(containerId));
            chart.draw(data, options);
        }
    </script>
</head>
<body onload="init()">
    <div id="piechart" style="width: 900px; height: 500px;"></div>
</body>
</html>

現時点で、Google Chartを使う方法は2パターンあるようです。

このどちらを読み込むかですね。

後者の方が新しい方法のようで、Google Chart公式でもこれを使って説明されています。
ただ、この方法だとwkhtmltopdf上でAPIをロードした際のイベントが発火せず、
google.charts.setOnLoadCallback()関数で登録したコールバックが呼ばれない

グラフが出ない

ので、注意。

とりあえず上の方法でうまくいっていますが、Googleは古いやり方をいきなりスパッと捨てたりするので、なんとかしたいとは思っています。

とりあえずアクセス。

pdf3.png

Yeah.

JavaScriptを別ファイルから読み出す

Google Chartのライブラリは外部から取得しているので当然ですが、JavaScript部分を別ファイルに切り出してもちゃんと動作します。
ここで注意したいのが、Laravel-SnappyはPHPが動作してるバックエンドのコンテナ上でpdfを書き出していることです。

つまり、.jsファイルのパスはサーバ内で考えます。

どういうことかというと、

  • ダメな例(ブラウザからの見た目でjsファイルを取得している)
<script type="text/javascript" src="{{ asset('/js/pizza.js')}}"></script>
  • イケる例(PHPコンテナ内のパスでjsファイルを取得している)
<script type="text/javascript" src="{{ base_path() . '/public/js/pizza.js' }}"></script>

先ほどの例のような、JavaScriptがインラインに書いてあるテンプレートを使っている場合、PHP側の書き換えで

  • return $pdf->inline('thisis.pdf'); -> ブラウザでpdfを表示
  • return view('pdf_template'); -> htmlとして表示

とどちらも対応できたのですが、ここにきて違いが出てくる点に気をつけましょう。
もっともらしく書いているということは、私もここで盛大にハマったということです。

おもむろに{{ base_path()...とか使っていますが、もちろんテンプレートエンジンもきちんと動作します。
すばらしき。

CSSの反映

これもJavaScriptと同じで、PHPが動作しているサーバ内でのパスでCSSを呼び出す必要があります。
とはいえ、CSSはPHP側で当ててあげることが可能なので

  • テンプレートはこれで
...
<link rel="stylesheet" href="{{ asset('/css/app.css') }}">
...
  • PHPではこう書く
...
    $pdf = \PDF::loadView('pdf_tamplate')
        ->setOption('encoding', 'utf-8');
+        ->setOption('user-style-sheet', base_path() . '/public/css/app.css')
        ;
    return $pdf->inline('cool-na-pdf.pdf');

これを両方書いてもイイわけです。
なんならpdf用に調整したcssを個別で当ててもいいですね。


まとめ

pdf出力、コワクナイ

「このデータpdfで出してくれたら使いやすいんだけどなぁ(チラッチラッ」と言われてもドヤ顔でやったりましょう。