[CakePHP] Sử dụng thư viện mPDF

Trong công việc của một developer, tôi nghĩ có khả năng bạn đã từng gặp dự án nào đó mà khách hàng yêu cầu trang web của họ có một hoặc nhiều chỗ có chức năng xuất file excel, csv, pdf ... để phục vụ cho mục đích nghiệp vụ của họ. Hoặc cũng có thể trong tương lai bạn sẽ gặp những dự án có chức năng dạng như vậy. Tôi chưa từng làm qua dự án nào như thế nên cũng muốn tìm hiểu để có thêm kiến thức, đồng thời cũng coi như là sự chuẩn bị cho các dự án tương lai. Trước hết, do đã có tìm hiểu được một chút về thư viện mPDF hỗ trợ khá nhiều cho việc kết xuất file PDF với PHP, mà cụ thể trong bài này và các bài sau tôi sẽ dùng CakePHP để chia sẻ những kiến thức tôi tìm hiểu được. về mPDF.

Giới thiệu mPDF

mPDF là một thư viện có khả năng kết xuất ra file dưới định dạng PDF từ HTML được encode UTF-8. Nó được xây dựng và phát triển thêm dựa trên FPDF và HTML2FPDF.

Với rất nhiều chức năng (Watermark, barcode, CSS, Protection, Tables, Page headers & footers ...), tôi nghĩ mPDF là một thư viện tuyệt vời nếu bạn đi tìm những chức năng hỗ trợ đến từng chi tiết trong việc kết xuất PDF. mPDF hỗ trợ rất nhiều ngôn ngữ khác nhau (bao gồm cả tiếng Trung, Hàn, Nhật hay Ả Rập).

Tất nhiên, để sử dụng được thư viện này bạn cần biết nó có những yêu cầu cần thiết nào. Hiện tại mPDF đang có phiên bản mới nhất là 5.7.4 nhưng trong loạt bài với CakePHP 2.x thì bản tôi dùng sẽ là 5.7.3. Với mPDF từ bản 5 trở lên, bạn hãy lưu ý :

  • PHP tối thiểu là 4.3.10 và 5.0.3
  • Từ bản 5.7.1 thì tối thiểu bạn phải dùng PHP 5.5
  • Hãy enable gói mb_string, tuy nhiên mPDF ko tương thích với mbstring.func_overload.
  • Bạn cần thư viện zlib (dùng trong việc nén) nếu dùng FPDF

Tích hợp mPDF với CakePHP 2.x

Tác giả Segy trong link github dưới đây đã viết sẵn một component cho CakePHP 2.x để tích hợp mPDF nên tôi sẽ dùng component này để gọi và sử dụng mPDF trong các bài viết của mình.

https://github.com/segy/Mpdf

Việc tích hợp rất đơn giản, bạn hãy clone hoặc download theo link trên và đưa vào các thư mục có trong đó theo đường dẫn tương ứng.

  • Thư mục mpdf để trong app/Vendor/
  • File MpdfComponent.php để trong app/Controller/Component

OK, bạn đã xong một nửa, việc còn lại là thiết lập quyền 600 hoặc 700 cho các thư mục nằm trong thư mục mpdf dưới đây :

  • /ttfontdata/ - thư mục dùng để cache dữ liệu font giúp tăng performance (bắt buộc)
  • /tmp/ - dùng để thực hiện các xử lý tạm thời (bắt buộc)
  • _/graph_cache/ _- nếu bạn dùng JpGraph kết hợp với mPDF

Xây dựng demo

1) Vài thiết lập ban đầu

Chú ý : Phiên bản CakePHP tôi dùng là 2.4.7 nên có thể có sự sai khác với phiên bản bạn đang có.

Bạn đã có Mpdf component cũng như thư viện mpdf rồi, tiếp theo chúng ta sẽ cùng làm một demo sử dụng một số chức năng để hiểu hơn về mpdf.

Trước tiên bạn cần tạo một DB có một bảng đơn giản, trong bài tôi sẽ sử dụng bài hướng dẫn về tạo web blog trong CakePHP CookBook. Nhưng tôi sẽ lược bỏ đi các phần code về add, edit, delete và chỉ để lại phần show list các bài blog cho đơn giản và cũng dễ nhìn hơn 😃. Chức năng thêm vào sẽ là một link dùng để gọi phương thức exportToPdf() , đây sẽ là phương thức chính cần quan tâm của chúng ta.

Về bảng chứa các blog thì bạn có thể tham khảo script tạo bên dưới.

CREATE TABLE IF NOT EXISTS `blogs`.`posts` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(50) NULL DEFAULT NULL,
  `body` TEXT NULL DEFAULT NULL,
  `created` DATETIME NULL DEFAULT NULL,
  `modified` DATETIME NULL DEFAULT NULL,
  PRIMARY KEY (`id`))

Về code của chức năng show list các blog thì hãy tham khảo code trên github. Nó rất đơn giản nên tôi lược bỏ và không post lên đây.

2) Gọi Mpdf component

Nếu các bạn đọc readme.md trên github của tác giả Segy thì bạn sẽ thấy khai báo sau :

public $components = array('Mpdf.Mpdf');

Bạn hãy sửa lại như sau (trong PostsController.php)

public $components = array('Mpdf');

Nhưng tôi chắc bạn sẽ bị lỗi không tìm thấy mpdf, nên hãy sửa lại dòng sau trong _MpdfComponent.php _

App::import('Vendor', 'Mpdf.mpdf/mpdf');

thành

App::import('Vendor', 'mpdf/mpdf');

3) Xuất ra danh sách các blog

Đầu tiên, bạn cần khởi tạo mPDF để sử dụng, sau đó là đặt tên file và cuối cùng là xuất ra PDF.

    // Khởi tạo mPDF
    $this->Mpdf->init();
    // Đặt tên file
    $this->Mpdf->setFilename('demo.pdf');
    // Xuất ra PDF
    $this->Mpdf->setOutput('D');

Nếu bạn chạy demo bây giờ thì sẽ được thông báo không tồn tại view nên hãy tạo file này giống với nội dung file index.php, đặt tên là export_to_pdf.ctp. Lý do rất đơn giản vì mPDF kết xuất PDF từ nội dung HTML mà bạn chưa có view tức là chưa có HTML. Bạn cần kết xuất ra nội dung gì thì chỉnh sửa thêm bớt trong file view này. Trong demo này tôi sẽ giữ nội dung hệt như bên index.php.

Giờ hãy chạy thử, bạn sẽ download được một file PDF với tên demo.pdf, nhưng sẽ không có nội dung giống như view trên màn hình danh sách các blog. Danh sách này lấy từ DB ra nên bạn cũng cần làm tương tự giống như phương thức index(), hãy add dòng sau vào đầu phương thức exportToPdf() :

$this->set('posts', $this->Post->find('all'));

4) init() và setOutput()

Bên trên bạn thấy tôi có gọi đến phương thức init() trong Mpdf component, đây là phương thức cài đặt những thông số ban đầu, chung nhất cho PDF của bạn, như là margin, font, format ... Ở đây bạn có thêm nhưng cài đặt của mình vào hoặc chỉnh sửa cho phù hợp nhu cầu dự án. Khi đó hãy đưa vào đối số $configuration của riêng mình.

Ngoài ra, còn có setOutput() là cần giải thích một chút.

    public function setOutput($output) {
        if (in_array($output, array('I', 'D', 'F', 'S')))
            $this->_output = $output;
    }
  • I : bạn sẽ view file PDF ngay trên trình duyệt (nếu đã cài plug-in đọc PDF. Tên file sẽ được dùng nếu bạn download thông qua plug-in.
  • D : download PDF về máy
  • F : lưu file local
  • S : trả vê document như là string (tên file sẽ bị bỏ qua nếu được set)

Nếu bạn để nguyên code và chỉ thay đổi D thành F

$this->Mpdf->setOutput('F');

Bạn sẽ bị báo lỗi không tạo được output file, lý do là vì khi lưu phía local mPDF khả năng sẽ bị lỗi về quyền. Bạn có thể lưu vào thư mục do bạn chỉ định mà đã đảm bảo mPDF có quyền (ít nhất hãy set CHMOD 755), ví dụ tôi đưa vào app/temp/, thay đổi như sau :

$this->Mpdf->setFilename('../tmp/demo.pdf');

5) Metadata cho file PDF xuất ra

Tôi chắc hẳn bạn sẽ cần set những thông tin cần thiết cho file PDF, ví dụ như bạn làm dự án cho khách hàng. Những file PDF đương nhiên thuộc quyền sở hữu của họ nên khi view chúng thì thông tin metadata (properties) như tiêu đề, người tạo, tác giả ... là rất hữu ích.

  • SetTitle($title) : Hàm này set tiêu đề hiển thị ra khi view PDF, bạn chỉ việc truyền vào giá trị cho đối số $title

  • SetAuthor($author): Set tên tác giả của file PDF.

  • SetCreator($creator) : Set tên người tạo file PDF.

  • SetSubject($subject) : Set subject cho file sẽ kết xuất.

  • SetKeywords($keywords) : Set các keywords cho PDF.

	$this->Mpdf->SetTitle('This is a demo title');
	$this->Mpdf->SetAuthor("Nguyen Van Huong");
	$this->Mpdf->SetCreator("Demo project");
	$this->Mpdf->SetSubject("Demo subject");
	$this->Mpdf->SetKeywords("Demo keyword");

6) Watermark (text và ảnh)

6.1) Chèn text watermark vào PDF

Với những file PDF xuất ra, rất có thể khách hàng của bạn sẽ yêu cầu chèn vào chúng một text nào đó làm watermark, mục đích có thể khác nhau như muốn note rằng đó là những file draft hay đánh dấu bản quyền với chúng ... mPDF hỗ trợ tốt cho yêu cầu này. Bạn chỉ cần gọi đến hàm SetWatermarkText($watermarkHere). Và bạn muốn text đó hiển thị thì đồng thời phải set true cho thuộc tính showWatermarkText như sau :

	$this->Mpdf->SetWatermarkText("this is is a demo water mark");
	$this->Mpdf->showWatermarkText = true;

Ngoài ra, mPDF còn hỗ trợ bạn set được độ trong suốt của watermark thông qua đối số thứ hai. Giá trị mặc định là 0.2 (tức 20%), và bạn có thể set trong khoảng từ 0 đến 1 :

$this->Mpdf->SetWatermarkText("this is a demo water mark", 0.5);
Nếu bạn để trống đối số thứ nhất thì giá trị mặc định là không có gì nên cũng sẽ không có watermark.

6.2) Image watermark

Hoặc nếu bạn cần chèn một ảnh nào đó vào làm watermark hoặc khách hàng yêu cầu chèn ảnh con dấu của công ty họ chẳng hạn thì bạn cần đến hàm _SetWatermarkImage() _.

SetWatermarkImage ( string $src [, float $alpha [, mixed $size [, mixed $position ]]])

Hàm này nhận vào các đối số sau :

  • src : Đây là đối số chỉ ra đường dẫn của file ảnh. Nó có thể là UR hoặc là đường dẫn tương đối.

  • alpha : Độ trong suốt. cũng giống như text thì với ảnh bạn cũng có thể set trong khoảng từ 0 đến 1, giá trị mặc định là 0.2

  • size : Đối số này có thể nhận vào chuỗi được định nghĩa sẵn bên dưới hoặc số nguyên hoặc mảng chỉ rõ độ rộng và chiều cao ảnh. Mặc định là D.

  • D: Nghĩa là size mặc định

  • P: Size sẽ được co lại cho vừa với full trang PDF (giữ nguyên ratio)

  • F: Giống P nhưng nó chỉ vừa mới trang PDF có margin.

  • Số nguyên: Sẽ resize ảnh với full trang PDF trừ đi margin là số nguyên được truyền vào. Giữ nguyên ratio.

  • array($width, $height): chỉ định độ rộng, chiều cao ảnh.

  • position : Đối số này có thể nhận vào chuỗi được định nghĩa sẵn bên dưới hoặc mảng chỉ rõ vị trí của ảnh được đặt. Mặc định là P.

  • P: Căn ảnh giữa file PDF

  • F: Căn giữa nhưng theo margin.

  • array($x, $y): Chỉ rõ vị trí đặt ảnh.

	$this->Mpdf->SetWatermarkImage('../webroot/img/cakepdf.jpeg', 0.1, array(50, 35), array(150, 250));
	$this->Mpdf->showWatermarkImage = true;

Và đây là kết quả chạy thử :

demo.png

Đến đây, các bạn đã có được vài kiến thức hữu ích cơ bản mà mPDF cung cấp. Trong các bài sau tôi sẽ tìm hiểu và chia sẻ về những phần khác như bảo vệ file, header footer, nén ...

code on github


All Rights Reserved