Laravel5でLaravel-Snappy(wkhtmltopdf)を使用してPDF出力機能を実装してみる

### はじめに

LaravelでPDFを出力するものでググると、まずTCPDFを使用する方法が出てきます。
私も試しました。すごく簡単にPDF出力ができることに驚きました。
しかし、CSSの制約が厳しすぎる！！
CSSが効いているかを試すのによく使うであろう ``` background: red; ``` が使用できないんですよ。まじか。
TCPDFは以下のCSSしかサポートしていません。えぇ……

* font-family
* font-size
* font-weight
* font-style
* color
* background-color
* text-decoration
* width
* height
* text-align

ソースは以下
http://stackoverflow.com/questions/11395171/why-does-tcpdf-ignore-my-inline-css

そこで、普段フロント画面作成に使用するような
普通のCSSをサポートしているPDF出力ライブラリを探して見つけたのがコレ、
Laravel-Snappyです。

### 商用利用可能か？

可能です。MITライセンス。やったぜ。
ソースは以下、Licenseの項目を参照
https://github.com/barryvdh/laravel-snappy

### 実装

必要ライブラリの準備

これはPDF出力機能の根幹となる wkhtmltopdf が必要とするライブラリをインストールしています。
実際のところ、ためしに出力しようとしたら ``` error while loading shared libraries: libXrender.so.1 ``` みたいなのが出てきたんで、
そのエラーがなくなるまで ``` yum install libXrender.so.1 ``` みたいなことをした次第です。
多分この部分はもっと良い方法があると思う。
TCPDFならサクッと一発でいけたんですけどねえ…

```
yum install libXext libXext.i686 libXrender libXrender.so.1 fontconfig  libfontconfig.so.1 zlib.i686 libstdc++.i686
```

20171010 追記

64bit版の場合はこれ一つでいけます。
なお、こちらでする場合は以下の操作で記述されている「i386」の部分は「amd64」で読み替えて下さい。

```
yum install wkhtmltopdf
```

インストール

```php
composer require h4cc/wkhtmltopdf-i386 0.12.x
composer require h4cc/wkhtmltoimage-i386 0.12.x
composer require barryvdh/laravel-snappy
```

エイリアス登録

```php:config/app.php

    'providers' => [
        Barryvdh\Snappy\ServiceProvider::class,
    ];

    'aliases' => [
        'PDF' => Barryvdh\Snappy\Facades\SnappyPdf::class,
        'SnappyImage' => Barryvdh\Snappy\Facades\SnappyImage::class,
    ];

```

次にconfigディレクトリにsnappy.phpというのを用意します。

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

``` /vendor/barryvdh/laravel-snappy/config/snappy.php ``` というところに
snappyの設定ファイルがあり、wkhtmltopdfというものを ``` /usr/local/bin/wkhtmltopdf ``` から読み込んでいるのですが、
その読み込み先をcomposerでインストールしたディレクトリからにするようオーバーライドしています。

```php:config/snappy.php

<?php

return array(


    'pdf' => array(
        'enabled' => true,
        'binary'  => base_path('vendor/h4cc/wkhtmltopdf-i386/bin/wkhtmltopdf-i386'), // ここを変更しています
        'timeout' => false,
        'options' => array(),
        'env'     => array(),
    ),
    'image' => array(
        'enabled' => true,
        'binary'  => base_path('vendor/h4cc/wkhtmltoimage-i386/bin/wkhtmltoimage-i386'), // ここを変更しています
        'timeout' => false,
        'options' => array(),
        'env'     => array(),
    ),


);

```

公式ドキュメントでは wkhtmltopdf-amd64 みたいに書いていると思いますが、
それは64bit版をインストール（ ``` composer require h4cc/wkhtmltopdf-amd64 0.12.x ``` ）した場合の話です。
私の場合は32bit版なので、i386になります。単純ですが少し詰まった。

### Hello World

さて、ここまでできればいよいよ出力ができます。
まずはViewファイルにHello Worldと書いたものを用意し、Controllerで呼び出して印刷してみます。

##### 必要ファイルの準備

```
php artisan make:controller TestController
touch resources/views/print.blade.php
```

##### View

```html+php:resources/views/print.blade.php
<p style="background: red">hello world</p>
```

##### Controller

```php:app/Http/Controller/TestController.php
    public function getPrint()
    {
        $pdf = \PDF::loadView('print');
        return $pdf->download('print.pdf');
    }
```

##### ルーティング

5.2だったら app/Http/routes.php

```php:routes/web.php
Route::get('/print', 'TestController@getPrint')->name('getprint');
```

##### 実行

ここまでできたら、 example.com/print にアクセス。PDFがダウンロードされましたでしょうか？
されたら次は、フォームの値を変数に格納し、それを表示してみます。

### POSTの値を印刷

##### 必要ファイルの準備

```
touch resources/views/index.blade.php
```

##### View

```html+php:resources/views/index.blade.php
<div>
	<p>PDF出力検証</p>
	<form action="print" method="post">
		{{ csrf_field() }}
		<input type="text" name="test" value="テスト" />
		<input type="submit" value="送信" />
	</form>
</div>
```

ちゃんとHTML全部書かないと日本語が表示されないみたいです。
まあHTMLの部分はincludeできると思うけど

```html+php:resources/views/print.blade.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<meta name="Keywords" content="" />
<meta name="Description" content="" />
<title></title>
</head>
<body>
<p style="background: red">{{ $data['test'] }}</p>
</body>
</html>
```

##### Controller

```php:app/Http/Controller/TestController.php
    public function index()
    {
        return view('index');
    }

    public function getPrint()
    {
        return redirect('/');
    }

    public function postPrint()
    {
        $data = \Request::all();
        $pdf = \PDF::loadView('print', ['data' => $data]);
        return $pdf->download('print.pdf');
    }
```

##### ルーティング

```php:routes/web.php
Route::get('/', 'TestController@index')->name('index');
Route::get('/print', 'TestController@getPrint')->name('getprint');
Route::post('/print', 'TestController@postPrint')->name('postprint');
```

##### 実行

ここまでできたら example.com/ にアクセス。
フォームに既に「テスト」とか値が入ってると思うので、送信ボタンをクリック。
PDFがダウンロードされ、「テスト」と表示されていることがわかると思います。
意外にも、日本語が普通に表示されるんですね。TCPDFの時はイチイチipaフォントのインストールとか必要だったのに。

### 以上

おわり。

### 追記

なお、いきなりダウンロードじゃなくて、PDFのプレビュー画面を表示するには次のようにします。驚くほど簡単ですね。TCPDFだと苦労したのに。

```php:app/Http/Controller/TestController.php
    public function postPrint()
    {
        $data = \Request::all();
        $pdf = \PDF::loadView('print', ['data' => $data]);
        //return $pdf->download('print.pdf');
        return $pdf->inline('print.pdf');
    }
```

### さらに追記

PDFをプレビューさせるのではなく、指定のディレクトリにアップロードして、
そのファイルにアクセスできるようにするみたいな。
また、その際のファイル名は「test_20161116140000.pdf」みたいにする。
今回はpublicディレクトリに置くことにします。

##### ディレクトリ作成

```
mkdir public/pdf
```

##### 権限付与

```
chown -R apache:apache public/pdf
```

##### 必要ファイルの準備

```
touch resources/views/complete.blade.php
```

##### Controller

postPrintとかはいらないので削除

```php:app/Http/Controller/TestController.php
class TestController extends Controller
{
    public function index()
    {
        return view('index');
    }

    public function complete()
    {
        $data = \Request::all();
        $location = public_path() . '/pdf/'; // example.com/pdf/test_20161116140000.pdf とかに置く
        $filename = 'test_' . date('YmdHis') . '.pdf';
        $pdf = \PDF::loadView('print', ['data' => $data]);
        $pdf -> save($location . $filename);
        return view('complete') -> with(['filename' => $filename]);
    }
}
```

##### View

```html+php:resources/views/index.blade.php
<div>
	<p>PDF出力検証</p>
	<form action="complete" method="post">
		{{ csrf_field() }}
		<input type="text" name="test" value="テスト" />
		<input type="submit" value="送信" />
	</form>
</div>
```

resources/views/print.blade.php は特に変更なし

```html+php:resources/views/complete.blade.php
<div>
	<p>印刷完了</p>
	<p><a href="/pdf/{{ $filename }}">ダウンロードはこちら</a></p>
</div>
```

##### ルーティング

```php:routes/web.php
Route::get('/', 'TestController@index')->name('index');
Route::get('/complete', function () {
    return redirect('/');
});
Route::post('/complete', 'TestController@complete')->name('complete');
```

以上

##### その他

20171010 追記

もし文字化けする場合、日本語フォントを入れると解決するかもしれません。

```
yum install ipa-gothic-fonts ipa-mincho-fonts ipa-pgothic-fonts ipa-pmincho-fonts
```