Tạo file PDF dùng Laravel

Tổng quan

Như tất cả chúng ta đều biết, tài liệu dưới định dạng pdf giờ khá là phổ biến. Vì sao nó lại phổ biến thì theo tôi tìm hiểu thì vì do các lý do sau: 1/ Tính phổ cập: Tức là các file pdf này dù mở ở hệ điều hành nào ta đều đọc được mà không phụ thuộc đã có font đó hay chưa. 2/ Tạo ra file rất dễ dàng: Từ các phần mềm như Word, Excel, Power Point, ... rất dễ dàng trong việc chuyển sang pdf. Có lẽ cái này liệu đúng trong lập trình của chúng ta? 3/ Dung lượng file giảm: Có lẽ đây là 1 trong ưu điểm nổi bật, đó chính là dung lượng của 1 file pdf giảm đi khá nhiều so với văn bản gốc. Chính vì sự phổ biến và hữu ích như vậy, khi chúng ta làm dự án không ít khách hàng yêu cầu chúng ta phải tạo ra 1 file pdf. Vậy với Laravel, chúng ta sẽ tạo ra như thế nào. Hay cùng tôi đi tiếp nhé.

Các thư viên tạo PDF

Chỉ cần lên google tìm kiếm với từ khóa Laravel pdf, chúng ta sẽ nhận thấy có rất nhiều kết quả cho việc tạo Pdf từ laravel. Đứng đầu trong kết quả tìm kiếm hiện tại chính là DomPDF. Trong bài viết này chúng ta sẽ sử dụng DomPDF để cùng tạo ra file Pdf nhé.

Tạo các file sample

Việc cài đặt Laravel thì các bạn có thể vào trực tiếp trang chủ để thực hiện cài đặt. Thêm vào nữa các bạn cài thêm (Laravel Collective)[https://laravelcollective.com/]. Sau bước cài đặt thì trong bài này tôi tạo ra 2 file sample, 1 file đơn giản chỉ có text và 1 file tôi lấy giao diện có đầy đủ css.

File base.blade.php

Ở đây file này tôi chỉ có nọi dung đoạn text vào tăng số lượng dòng để xem việc tạo ra các trang trong văn bản nó sẽ như thế nào. Source của nó đơn giản như sau

<html>
    <head>
        <title>Tạo PDF bằng Laravel - Viblo Asia</title>
        <meta charset="utf-8">
    </head>
    <body>
        <h1>Tiêu đề H1</h1>
        <h2>Tiêu đề H2</h2>
        <h3>Tiêu đề H3</h3>
        <h4>Tiêu đề H4</h4>
        <h5>Tiêu đề H5</h5>
        <h6>Tiêu đề H6</h6>
        <table>
            <thead>
                <tr>
                    <th>Số TT</th>
                    <th>Nội dung</th>
                </tr>
            </thead>
            <tbody>
                @foreach (range(1, 100) as $count)
                <tr>
                    <td>{{ $count }}</td>
                    <td>Nôi dụng của dòng thứ {{ $count }}</td>
                </tr>
                @endforeach
            </tbody>
        </table>
    </body>
</html>

Sample.blade.php

Ở đây tôi thử lấy 1 bộ giao diện boostrap để thử thêm css vào giao diện. Nguồn (New Age - One page boostrap)[https://startbootstrap.com/template-overviews/new-age/] Source file như sau

<!DOCTYPE html>
<html lang="en">

<head>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>Tạo PDF bằng Laravel - Viblo Asia</title>

    <!-- Bootstrap Core CSS -->
    {!! Html::style('lib/bootstrap/css/bootstrap.min.css') !!}
    {!! Html::style('https://fonts.googleapis.com/css?family=Lato') !!}
    {!! Html::style('https://fonts.googleapis.com/css?family=Catamaran:100,200,300,400,500,600,700,800,900') !!}
    {!! Html::style('https://fonts.googleapis.com/css?family=Muli') !!}
    {!! Html::style('lib/font-awesome/css/font-awesome.min.css') !!}
    {!! Html::style('lib/simple-line-icons/css/simple-line-icons.css') !!}
    {!! Html::style('lib/device-mockups/device-mockups.min.css') !!}
    {!! Html::style('css/new-age.min.css') !!}

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
        <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
    <![endif]-->

</head>

<body id="page-top">

    <nav id="mainNav" class="navbar navbar-default navbar-fixed-top">
        <div class="container">
            <!-- Brand and toggle get grouped for better mobile display -->
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                    <span class="sr-only">Toggle navigation</span> Menu <i class="fa fa-bars"></i>
                </button>
                <a class="navbar-brand page-scroll" href="#page-top">Start Bootstrap</a>
            </div>

            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="nav navbar-nav navbar-right">
                    <li>
                        <a class="page-scroll" href="#download">Download</a>
                    </li>
                    <li>
                        <a class="page-scroll" href="#features">Features</a>
                    </li>
                    <li>
                        <a class="page-scroll" href="#contact">Contact</a>
                    </li>
                </ul>
            </div>
            <!-- /.navbar-collapse -->
        </div>
        <!-- /.container-fluid -->
    </nav>

    <header>
        <div class="container">
            <div class="row">
                <div class="col-sm-7">
                    <div class="header-content">
                        <div class="header-content-inner">
                            <h1>New Age is an app landing page that will help you beautifully showcase your new mobile app, or anything else!</h1>
                            <a href="#download" class="btn btn-outline btn-xl page-scroll">Start Now for Free!</a>
                        </div>
                    </div>
                </div>
                <div class="col-sm-5">
                    <div class="device-container">
                        <div class="device-mockup iphone6_plus portrait white">
                            <div class="device">
                                <div class="screen">
                                    <!-- Demo image for screen mockup, you can put an image here, some HTML, an animation, video, or anything else! -->
                                    <img src="img/demo-screen-1.jpg" class="img-responsive" alt="">
                                </div>
                                <div class="button">
                                    <!-- You can hook the "home button" to some JavaScript events or just remove it -->
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </header>

    <section id="download" class="download bg-primary text-center">
        <div class="container">
            <div class="row">
                <div class="col-md-8 col-md-offset-2">
                    <h2 class="section-heading">Discover what all the buzz is about!</h2>
                    <p>Our app is available on any mobile device! Download now to get started!</p>
                    <div class="badges">
                        <a class="badge-link" href="#"><img src="img/google-play-badge.svg" alt=""></a>
                        <a class="badge-link" href="#"><img src="img/app-store-badge.svg" alt=""></a>
                    </div>
                </div>
            </div>
        </div>
    </section>

    <section id="features" class="features">
        <div class="container">
            <div class="row">
                <div class="col-lg-12 text-center">
                    <div class="section-heading">
                        <h2>Unlimited Features, Unlimited Fun</h2>
                        <p class="text-muted">Check out what you can do with this app theme!</p>
                        <hr>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-md-4">
                    <div class="device-container">
                        <div class="device-mockup iphone6_plus portrait white">
                            <div class="device">
                                <div class="screen">
                                    <!-- Demo image for screen mockup, you can put an image here, some HTML, an animation, video, or anything else! -->
                                    <img src="img/demo-screen-1.jpg" class="img-responsive" alt=""> </div>
                                <div class="button">
                                    <!-- You can hook the "home button" to some JavaScript events or just remove it -->
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="col-md-8">
                    <div class="container-fluid">
                        <div class="row">
                            <div class="col-md-6">
                                <div class="feature-item">
                                    <i class="icon-screen-smartphone text-primary"></i>
                                    <h3>Device Mockups</h3>
                                    <p class="text-muted">Ready to use HTML/CSS device mockups, no Photoshop required!</p>
                                </div>
                            </div>
                            <div class="col-md-6">
                                <div class="feature-item">
                                    <i class="icon-camera text-primary"></i>
                                    <h3>Flexible Use</h3>
                                    <p class="text-muted">Put an image, video, animation, or anything else in the screen!</p>
                                </div>
                            </div>
                        </div>
                        <div class="row">
                            <div class="col-md-6">
                                <div class="feature-item">
                                    <i class="icon-present text-primary"></i>
                                    <h3>Free to Use</h3>
                                    <p class="text-muted">As always, this theme is free to download and use for any purpose!</p>
                                </div>
                            </div>
                            <div class="col-md-6">
                                <div class="feature-item">
                                    <i class="icon-lock-open text-primary"></i>
                                    <h3>Open Source</h3>
                                    <p class="text-muted">Since this theme is MIT licensed, you can use it commercially!</p>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>

    <section class="cta">
        <div class="cta-content">
            <div class="container">
                <h2>Stop waiting.<br>Start building.</h2>
                <a href="#contact" class="btn btn-outline btn-xl page-scroll">Let's Get Started!</a>
            </div>
        </div>
        <div class="overlay"></div>
    </section>

    <section id="contact" class="contact bg-primary">
        <div class="container">
            <h2>We <i class="fa fa-heart"></i> new friends!</h2>
            <ul class="list-inline list-social">
                <li class="social-twitter">
                    <a href="#"><i class="fa fa-twitter"></i></a>
                </li>
                <li class="social-facebook">
                    <a href="#"><i class="fa fa-facebook"></i></a>
                </li>
                <li class="social-google-plus">
                    <a href="#"><i class="fa fa-google-plus"></i></a>
                </li>
            </ul>
        </div>
    </section>

    <footer>
        <div class="container">
            <p>&copy; 2016 Start Bootstrap. All Rights Reserved.</p>
            <ul class="list-inline">
                <li>
                    <a href="#">Privacy</a>
                </li>
                <li>
                    <a href="#">Terms</a>
                </li>
                <li>
                    <a href="#">FAQ</a>
                </li>
            </ul>
        </div>
    </footer>

    <!-- jQuery -->
    <script src="lib/jquery/jquery.min.js"></script>

    <!-- Bootstrap Core JavaScript -->
    <script src="lib/bootstrap/js/bootstrap.min.js"></script>

    <!-- Plugin JavaScript -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js"></script>

    <!-- Theme JavaScript -->
    <script src="js/new-age.min.js"></script>

</body>

</html>

Sử dụng DomPDF

Để cài đặt và sử dụng các bạn có thể qua (github)[https://github.com/barryvdh/laravel-dompdf] để xem cách cài đặt. Việc cài đặt khá đơn giản. Sau khi cài đặt xong thì giờ tôi tạo lệnh command để tạo công việc tạo 2 file pdf dựa vào 1 giao diện trên. Để tạo command thì ta dùng lệnh php artisan make:command MakePDF Sau khi chạy lệnh này thì ta có 1 file MakePDF.php trong folder app\console\command. Việc tiếp theo giờ ta tiến hành việc tạo file trong file này..

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use PDF;

class MakePDF extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'dompdf:make_pdf';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Make pdf use dompdf with template';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $pdf = PDF::loadView('pdf.sample');//Load view
        $pdf->save(public_path('sample.pdf'));//Tạo file
        $pdf = PDF::loadView('pdf.base');
        $pdf->save(public_path('base.pdf'));
    }
}

Sau đó để tạo 2 file này ta chạy lệnh sau ```php artisan dompdf:make_pdf` Tất nhiên nếu như nó có thông bao không có lệnh này thì ta cần khai báo thêm class này trong app\console\Kernel.php

    protected $commands = [
        MakePDF::class,
    ];

Vậy sau khi chạy lệnh xong ta sẽ có 2 file pdf trong folder public là sample.pdf và base.pdf. Chúng ta xem kết quả như thế nào nhé. Với file sample.pdf thì hơi thất vọng vì nó không ra được giao diện như trên web. Có lẽ phần vì DomPDF chưa hỗ trợ nhiều và việc gọi css qua file khác không hiệu quả. Với file base.pdf thì khả quan hơn và cũng có 1 sự bất ngờ đấy là khi làm table, nếu như là thẻ thead thì ở các trang sau luôn có header chính là nội dung trên thead này. Đây cũng là điểm khá hay của thư viện này. Nhưng việc tạo file pdf từ DomPDF vẫn có nhiều bất cập khó khăn như việc load css phải viết trực tiếp lên file chứ không load dược từ file khác. Hay phần tùy chỉnh header và footer khá là khó khăn