Laravel 7とSnappyでPDFを出力する

## はじめに

LaravelでPDFを出力する方法です。

完成版のソースは以下に置いてあります。
https://github.com/naga3/snappy-sample

## Laravelで使えるPDF出力ライブラリ

- Laravel Browsershot: https://github.com/verumconsilium/laravel-browsershot
  - [Puppeteer](https://pptr.dev/)を使う
  - 正確さでは一番か？
- Laravel DOMPDF: https://github.com/barryvdh/laravel-dompdf
  - [DOMPDF](https://github.com/dompdf/dompdf)のLaravelラッパー
  - PHPだけあれば動くのでお手軽
- Laravel Snappy: https://github.com/barryvdh/laravel-snappy
  - Qt WebKitレンダリングエンジンを使ってPDFを出力するツールである[wkhtmltopdf](https://wkhtmltopdf.org/)のPHPラッパーである[Snappy](https://github.com/KnpLabs/snappy)のLaravelラッパー（ややこしい）
  - Laravel DOMPDFより高速

今回はバランスを考えてLaravel Snappyを選択しました。

## Laravelプロジェクト作成

https://readouble.com/laravel/7.x/ja/installation.html

コチラを参考に適当なプロジェクトを作成します。[Composer](https://getcomposer.org/)がインストールされていれば簡単に作成できます。

```sh
composer create-project --prefer-dist laravel/laravel snappy-sample
cd snappy-sample
```

## Laravel Snappyのインストール

Laravel Snappyの[ドキュメント](https://github.com/barryvdh/laravel-snappy/blob/master/readme.md)を見て進めて行きます。

まずwkhtmltopdfをインストールしますが、こちらもComposerで簡単に導入できます。WindowsやMacなど、バイナリが適合しない場合は上記ドキュメントを参照してください。

```sh
composer require h4cc/wkhtmltopdf-amd64 0.12.x
```

次にLaravel Snappy本体をインストールします。

```sh
composer require barryvdh/laravel-snappy
```

configファイルを作ります。

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

configファイルにwkhtmltopdfの場所を書きます。上記のようにComposerからインストールした場合は

```php:config/snappy.php
'binary' => base_path('vendor/h4cc/wkhtmltopdf-amd64/bin/wkhtmltopdf-amd64'),
```

## PDF出力のテスト

正常にインストールできたかチェックしてみます。

```php:routes/web.php
Route::get('/hello', function () {
    return PDF::loadHTML('<h1>Hello!</h1>')->inline();
});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32030/d00fe64a-e4ad-08c8-5726-da5bab6015e7.png)

　大丈夫そうですね。

## Bladeを使ってPDFを出力する。

Snappyは、BladeにそのままHTMLを書いて出力できるので大変楽です。CSSも2くらいまでは正確に効きます。

まずRouteの部分です。

```php:routes/web.php
Route::get('/members', function () {
    $member = Faker\Factory::create('ja_JP');
    return PDF::loadView('members', compact('member'))->inline();
});
```

Fakerでランダムなデータを生成してviewに渡しています。

次にテンプレートの部分です。

```php:resources/views/members.blade.php
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <title>名簿</title>
  <style>
    table {
      border-collapse: collapse;
    }

    tr {
      page-break-inside: avoid;
    }

    th,
    td {
      border: 1px solid black;
    }
  </style>
</head>

<body>
  <h1>名簿</h1>
  <table>
    <tr>
      <th>No.</th>
      <th>氏名</th>
      <th>住所</th>
      <th>電話番号</th>
      <th>備考</th>
      @for ($no = 1; $no <= 50; $no++) <tr>
        <td>{{ $no }}</td>
        <td style="white-space: nowrap">{{ $member->name }}</td>
        <td>{{ $member->address }}</td>
        <td>{{ $member->phoneNumber }}</td>
        <td>{{ $member->realText }}</td>
    </tr>
    @endfor
  </table>
</body>

</html>
```

ブラウザで確認してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32030/e88de6d5-a31a-5b43-72e1-d4fff9a14334.png)

良さそうですね。

tr要素に`page-break-inside: avoid;`を適用することによって、セルの途中で改ページされなくなります。

こんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32030/758cb09e-ef84-eef0-b0c6-62ec1ea3f63d.png)