12/08/2018, 16:53

[Laravel5] Xuất ra file PDF sử dụng Laravel-Snappy(wkhtmltopdf)

Việc xuất ra file PDF trong Laravel bạn có thể search Google ra rất nhiều kết quả và có lẽ TCPDF sẽ xuất hiện nhiều nhất trên các trang tut. Tôi cũng đã thử nó, thực sự là rất dễ dàng để đưa ra được PDF. Nhưng mà nó có mặt hạn chế về CSS. Ví dụ khi tôi thư background: red; thì không có hiệu quả. ...

Việc xuất ra file PDF trong Laravel bạn có thể search Google ra rất nhiều kết quả và có lẽ TCPDF sẽ xuất hiện nhiều nhất trên các trang tut. Tôi cũng đã thử nó, thực sự là rất dễ dàng để đưa ra được PDF. Nhưng mà nó có mặt hạn chế về CSS. Ví dụ khi tôi thư background: red; thì không có hiệu quả. Thật là ... !

TCPDF nó chỉ có su bóp những CSS sau mà thôi :

  • font-family
  • font-size
  • font-weight
  • font-style
  • color
  • background-color
  • text-decoration
  • awidth
  • height
  • text-align

Nguồn tham khảo : http://stackoverflow.com/questions/11395171/why-does-tcpdf-ignore-my-inline-css

Và tôi đã tìm kiếm thư viện không những xuất ra được PDF mà còn phải hỗ trợ tốt những CSS bình thường, tôi đã thấy - chính là Laravel-Snappy !

Nhưng thư viện này liệu có dùng cho các trang thương mại ko? Có chứ. Bởi nó là MIT license, tuyệt ! Bạn có thể tham khảo tại đây !

Chuẩn bị thư viện cần thiết

Thư viện cơ bẩn nhất cần thiết là wkhtmltopdf.

Khi tôi cài thực tế thì bị báo lỗi error while loading shared libraries: libXrender.so.1, và để mà giải quyết thì cần thực hiện yum install libXrender.so.1. Đoạn này thì phức tạp hơn TCPDF, nhưng có lẽ có cách tốt hơn mà tôi chưa tìm ra thôi.

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

Còn nếu là bản 64bit thì chỉ cần thế này là ngon ! Nhưng cần chú ý phần dưới sẽ cần thay chỗ 「i386」thành 「amd64」.

yum install wkhtmltopdf

Cài đặt

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

Đăng kí alias

config/app.php

    'providers' => [
        BarryvdhSnappyServiceProvider::class,
    ];

    'aliases' => [
        'PDF' => BarryvdhSnappyFacadesSnappyPdf::class,
        'SnappyImage' => BarryvdhSnappyFacadesSnappyImage::class,
    ];

Tiếp theo sẽ chuẩn bị trong thư mục config file snappy.php

php artisan vendor:publish --provider="BarryvdhSnappyServiceProvider"

Ở chỗ /vendor/barryvdh/laravel-snappy/config/snappy.php có file setting của snappy, và đọc wkhtmltopdf từ /usr/local/bin/wkhtmltopdf. Nhưng tôi sẽ override lại :

config/snappy.php

<?php
return array(
    'pdf' => array(
        'enabled' => true,
        'binary'  => base_path('vendor/h4cc/wkhtmltopdf-i386/bin/wkhtmltopdf-i386'), // Tôi thay đổi chỗ này
        'timeout' => false,
        'options' => array(),
        'env'     => array(),
    ),
    'image' => array(
        'enabled' => true,
        'binary'  => base_path('vendor/h4cc/wkhtmltoimage-i386/bin/wkhtmltoimage-i386'), // và chỗ này
        'timeout' => false,
        'options' => array(),
        'env'     => array(),
    ),
);

Ở trong docs của nó có ghi là wkhtmltopdf-amd64 nhưng mà đó là trường hợp bạn cài đặt bản 64bit ( composer require h4cc/wkhtmltopdf-amd64 0.12.x ). Còn trường hợp tôi cài là bản 32bit nên sẽ là i386.

Nào, đến đây thì đã có thể chuẩn bị để xuất ra file PDF được rồi đó ! Đầu tiên là sẽ chuẩn bị file View có chữ Hello World, gọi bằng Controller để export ra.

Chuẩn bị file cần thiết

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');
    }

Routing

Nếu như tôi đang dùng thì 5.2 sẽ là file app/Http/routes.php.

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

Run

Nếu khi bạn access vào example.com/print mà PDF được download thì đã ok, việc tiếp theo sẽ là lưu các giá trị của forrm vào biến và hiển thị nó ra.

Chuẩn bị file cần thiết

touch resources/views/index.blade.php

View

resources/views/index.blade.php
<div>
    <p>Exporting PDF Test</p>
    <form action="print" method="post">
        {{ csrf_field() }}
        <input type="text" name="test" value="テスト" />
        <input type="submit" value="Send" />
    </form>
</div>
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');
    }

Routing

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

Run

Khi mà truy cập vào example.com/ thì bạn chắc sẽ thấy chữ 「テスト」đã có sẵn trong form do đã chỉ định value rồi, nếu click nút Send, thì bạn sẽ thấy PDF được download về và có nội dung ghi 「テスト」. Nếu trường hợp mà bạn không nhìn thấy được tiếng Nhật hiển thị đúng thì có lẽ cần phải cài thêm font :

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

Nếu như mà không muốn download về mà preview trước thì cũng rất đơn giản không phức tạp như 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');
    }

Vậy là xong !

More

Giờ tôi không muốn preview nữa mà sẽ upload lên thư mục chỉ định (tôi dùng public) để có thể truy cập vào được, và còn muốn đặt tên file là dạng kiểu 「test_20161116140000.pdf」.

Tạo thư mục

mkdir public/pdf

Gán quyền

chown -R apache:apache public/pdf

Chuẩn bị file cần thiết

touch resources/views/complete.blade.php

Controller

Do postPrint không cần nữa nên tôi xoá đi.

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/'; // sẽ kiểu là path public : 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>Exporting PDF Test</p>
    <form action="complete" method="post">
        {{ csrf_field() }}
        <input type="text" name="test" value="テスト" />
        <input type="submit" value="Send" />
    </form>
</div>

Riêng resources/views/print.blade.php thì tôi không đổi gì cả.

resources/views/complete.blade.php
<div>
    <p>Export Successfully !</p>
    <p><a href="/pdf/{{ $filename }}">Download here</a></p>
</div>

Routing

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

Vậy là xong demo đơn giản với background : red;, nhưng bạn hoàn toàn có thể thử những style khác ! Hy vọng bài viết đã giúp ích cho bạn             </div>
            
            <div class=

0