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

__1/25 更新__

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


## はじめに

[Laravel-Snappy](https://github.com/barryvdh/laravel-snappy)でpdfを書き出すまででちょっとつまずいたりしたので、最終的にどうやってできるようになったかの記録。

### やること

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



### 環境

- PHP 7.1
- Laravel 5.5 (Laradock使用)


基本的なやり方は[readme](https://github.com/barryvdh/laravel-snappy)に書いてありますが、一応順を追って。



## とりあえず動かしてみる

### 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`ですが、デフォルトのままでは日本語フォントが存在しないので日本語が表示できません。
ついでに入れときましょう



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

<img width="1002" alt="pdf1.png" src="https://qiita-image-store.s3.amazonaws.com/0/151247/2e591e98-fa6a-af67-bddc-a96a13db3d63.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'); //こっちにすると直接ダウンロード
    }
...
```



<img width="1001" alt="pdf2.png" src="https://qiita-image-store.s3.amazonaws.com/0/151247/7ac13406-924d-cdfd-0cb3-dbdf40af525c.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パターンあるようです。

- https://www.google.com/jsapi
- https://www.gstatic.com/charts/loader.js


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

後者の方が新しい方法のようで、[Google Chart](https://developers.google.com/chart/interactive/docs/quick_start)公式でもこれを使って説明されています。
ただ、この方法だとwkhtmltopdf上でAPIをロードした際のイベントが発火せず、
`google.charts.setOnLoadCallback()`関数で登録したコールバックが呼ばれない
↓
グラフが出ない

ので、注意。

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




とりあえずアクセス。

<img width="973" alt="pdf3.png" src="https://qiita-image-store.s3.amazonaws.com/0/151247/7b779ee9-6ed0-1bd0-85b9-09bdfcf729f1.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で出してくれたら使いやすいんだけどなぁ(ﾁﾗｯﾁﾗｯ」と言われてもドヤ顔でやったりましょう。