Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

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

インストール

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

エイリアス登録

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 artisan vendor:publish --provider="Barryvdh\Snappy\ServiceProvider"

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

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
resources/views/print.blade.php
<p style="background: red">hello world</p>
Controller
app/Http/Controller/TestController.php
    public function getPrint()
    {
        $pdf = \PDF::loadView('print');
        return $pdf->download('print.pdf');
    }
ルーティング

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

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

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

POSTの値を印刷

必要ファイルの準備
touch resources/views/index.blade.php
View
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できると思うけど

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
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');
    }
ルーティング
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だと苦労したのに。

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とかはいらないので削除

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
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 は特に変更なし

resources/views/complete.blade.php
<div>
    <p>印刷完了</p>
    <p><a href="/pdf/{{ $filename }}">ダウンロードはこちら</a></p>
</div>
ルーティング
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
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away