Trả về tệp PDF được tạo trong controller
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”Tạo tệp Portable Document Format (PDF) trong một controller action rồi trả về dưới dạng phản hồi Hypertext Transfer Protocol (HTTP). Mỗi tích hợp framework đều có helper PdfResponse để dựng đối tượng phản hồi, đặt Content-Type: application/pdf, gắn các header bảo mật và làm sạch tên tệp. Hướng dẫn này bao gồm ba chế độ phân phối cho Laravel, Symfony và CodeIgniter 4: xem trước inline, tải xuống và phân phối theo luồng.
Trước hết, hãy kiểm tra các điều kiện tiên quyết này để đường dẫn controller sẵn sàng trước khi bạn bắt đầu:
- Lõi NextPDF đã được cài đặt.
- Một tích hợp framework đã được cài đặt, đồng thời service provider, bundle hoặc service của nó đã được phát hiện. Hãy xác minh trạng thái phát hiện trên trang cài đặt cho framework của bạn trước khi bắt đầu.
- Chế độ theo luồng không cần thêm gói nào. Mọi tích hợp đều có biến thể theo luồng bên cạnh biến thể được đệm.
Đây là hướng dẫn cách làm. Hướng dẫn giả định rằng bạn đã biết cách định tuyến một yêu cầu đến controller trong framework của mình. Để xem ví dụ đầu tiên có thể chạy trong từng framework, hãy đọc phần quickstart của framework được liên kết trong mục Xem thêm.
Cài đặt
Phần tiêu đề “Cài đặt”Cài đặt tích hợp cho framework của bạn. Hãy chạy một trong các lệnh sau.
composer require nextpdf/laravelcomposer require nextpdf/symfonycomposer require nextpdf/codeigniterVới Laravel, hãy xuất bản cấu hình sau khi cài đặt.
php artisan vendor:publish --tag=nextpdf-configSymfony đăng ký bundle thông qua Flex, còn CodeIgniter tự động phát hiện service. Hãy xác nhận trạng thái phát hiện trên trang cài đặt của framework trước khi bạn tiếp tục.
Tổng quan khái niệm
Phần tiêu đề “Tổng quan khái niệm”Mọi tích hợp framework đều theo cùng một cấu trúc ba phần: bạn lấy một tài liệu mới, ghi nội dung vào tài liệu đó, rồi truyền tài liệu cho factory PdfResponse để trả về phản hồi HTTP. API tài liệu (addPage(), cell(), setFont()) là bề mặt cốt lõi của engine và giống nhau trên mọi framework. Factory phản hồi chỉ khác nhau ở lớp phản hồi mà nó trả về, vì mỗi framework có kiểu phản hồi HTTP riêng.
PdfResponse cung cấp ba chế độ phân phối. Inline đặt header Content-Disposition: inline để trình duyệt hiển thị PDF trong một tab xem tài liệu. Download đặt Content-Disposition: attachment để trình duyệt lưu tệp. Streamed phát phần thân PDF theo từng khối cố định thay vì đệm toàn bộ tài liệu trong bộ nhớ. Hãy chọn chế độ này cho các tài liệu lớn khi bộ nhớ đỉnh quan trọng hơn một Content-Length đã biết.
Hãy lấy tài liệu qua đường dẫn phân giải thông thường của framework:
- Laravel — phân giải
NextPDF\Contracts\DocumentFactoryInterfacetừ container bằngapp(...)rồi gọicreate(), hàm này trả về mộtNextPDF\Core\Documentmới — kiểu cụ thể mà các factoryPdfResponsechấp nhận. - Symfony — inject
NextPDF\Symfony\Service\PdfFactoryrồi gọicreate(), hàm này trả về mộtNextPDF\Core\Documentmới với các giá trị mặc định của tài liệu đã cấu hình được áp dụng sẵn. - CodeIgniter 4 — phân giải thư viện
Pdfthông quaServices::pdf()(hoặc helperpdf()), hoặc lấy một tài liệu thuần thông quapdf_document().
Bề mặt API
Phần tiêu đề “Bề mặt API”| Nhu cầu | Laravel | Symfony | CodeIgniter 4 |
|---|---|---|---|
| Tài liệu mới | app(DocumentFactoryInterface::class)->create() | PdfFactory::create() | pdf_document() / Services::pdf()->document() |
| Phản hồi inline | PdfResponse::inline($doc, $name) | PdfResponse::inline($doc, $name) | $pdf->inline($name) / PdfResponse::inline($doc, $name) |
| Phản hồi tải xuống | PdfResponse::download($doc, $name) | PdfResponse::download($doc, $name) | $pdf->download($name) / PdfResponse::download($doc, $name) |
| Inline theo luồng | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) |
| Tải xuống theo luồng | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) |
| Kiểu trả về | Illuminate\Http\Response (theo luồng: StreamedResponse) | Symfony\Component\HttpFoundation\Response (theo luồng: StreamedResponse) | CodeIgniter\HTTP\DownloadResponse |
Lớp PdfResponse của Laravel nằm tại NextPDF\Laravel\Http\PdfResponse, của Symfony tại NextPDF\Symfony\Http\PdfResponse, và của CodeIgniter tại NextPDF\CodeIgniter\Http\PdfResponse. Trang security-and-operations của mỗi tích hợp mô tả đầy đủ hành vi phản hồi cho gói đó: tập header, quy tắc disposition và quy trình làm sạch tên tệp. Các trang đó được liên kết trong mục Xem thêm.
Mẫu mã — khởi đầu nhanh
Phần tiêu đề “Mẫu mã — khởi đầu nhanh”Dưới đây là download action tối thiểu trong mỗi framework. Các lệnh gọi tài liệu dùng cùng một bề mặt cốt lõi. Chỉ phần khung controller thay đổi.
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;use NextPDF\Contracts\DocumentFactoryInterface;use NextPDF\Laravel\Http\PdfResponse;
final class ReportController extends Controller{ public function download(): Response { $document = app(DocumentFactoryInterface::class)->create(); $document->addPage(); $document->cell(0, 10, 'Monthly report', newLine: true);
return PdfResponse::download($document, 'report.pdf'); }}<?php
declare(strict_types=1);
namespace App\Controller;
use NextPDF\Symfony\Http\PdfResponse;use NextPDF\Symfony\Service\PdfFactory;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Routing\Attribute\Route;
final class ReportController{ #[Route('/report', name: 'report_pdf')] public function download(PdfFactory $pdf): Response { $document = $pdf->create(); $document->addPage(); $document->cell(0, 10, 'Monthly report', newLine: true);
return PdfResponse::download($document, 'report.pdf'); }}<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\DownloadResponse;use NextPDF\CodeIgniter\Config\Services;
final class ReportController extends BaseController{ public function download(): DownloadResponse { $pdf = Services::pdf(); $pdf->document()->addPage(); $pdf->document()->cell(0, 10, 'Monthly report');
return $pdf->download('report.pdf'); }}Để xem trước trong trình duyệt thay vì tải xuống, hãy đổi lệnh gọi download(...) thành inline(...) trong Laravel và Symfony, hoặc $pdf->inline('report.pdf') trong CodeIgniter. Disposition sẽ đổi thành inline, còn mọi header khác giữ nguyên.
Mẫu mã — Production
Phần tiêu đề “Mẫu mã — Production”Một production action sẽ inject các phụ thuộc của nó, bắt ngoại lệ cụ thể nhất mà tích hợp ghi lại, ghi log lớp lỗi mà không làm lộ stack trace, và trả về một phản hồi lỗi HTTP đã xác định. Ví dụ bên dưới sử dụng constructor injection của Laravel. Các phiên bản tương đương của Symfony và CodeIgniter cùng theo mô thức này và xuất hiện trên trang production-usage của mỗi tích hợp.
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;use NextPDF\Contracts\DocumentFactoryInterface;use NextPDF\Laravel\Http\PdfResponse;use Psr\Log\LoggerInterface;use Throwable;
final class InvoiceController extends Controller{ public function __construct( private readonly DocumentFactoryInterface $documents, private readonly LoggerInterface $logger, ) {}
public function show(int $invoiceId): Response { try { $document = $this->documents->create(); $document->addPage(); $document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true);
return PdfResponse::download( $document, "invoice-{$invoiceId}.pdf", ); } catch (Throwable $exception) { // Log the exception class, never the message or a stack trace, // so internal detail does not leak into the log sink. $this->logger->error('Invoice PDF generation failed', [ 'invoice_id' => $invoiceId, 'exception' => $exception::class, ]);
return new Response('Could not generate the invoice PDF.', 500); } }}Inject DocumentFactoryInterface và gọi create() trong mỗi action. Hàm này trả về một NextPDF\Core\Document mới — kiểu cụ thể mà các factory PdfResponse của Laravel chấp nhận. Việc phân giải tài liệu mới cho từng yêu cầu giúp bạn thay thế factory trong kiểm thử. Đừng tái sử dụng cùng một instance controller cho hai tài liệu không liên quan bên trong một tiến trình worker chạy lâu dài.
Với các tài liệu rất lớn, hãy thay factory được đệm bằng factory theo luồng để giới hạn bộ nhớ đỉnh. Biến thể theo luồng trả về một StreamedResponse (Laravel và Symfony) và phát phần thân theo từng khối cố định. Nó cố tình bỏ qua Content-Length, nên các thanh tiến trình tải xuống và các proxy nhạy với độ dài sẽ không thấy kích thước đã biết. Hãy ưu tiên download() / inline() được đệm cho các phản hồi nhỏ, nhạy với độ trễ.
$document = $this->documents->create();// ... emit content onto $document ...return PdfResponse::streamDownload($document, 'annual-report.pdf');Trường hợp đặc biệt & điều cần lưu ý
Phần tiêu đề “Trường hợp đặc biệt & điều cần lưu ý”- Tài liệu mới cho mỗi lệnh gọi. Trong cả ba tích hợp, tài liệu là sản phẩm của factory và mới ở mỗi lần phân giải. Đừng cache một tài liệu đã phân giải cho nhiều tài liệu logic, hoặc qua nhiều yêu cầu trong một worker chạy lâu dài. Trạng thái nội dung cũ sẽ bị mang theo.
- Tên tệp rỗng. Một tên tệp rỗng được truyền cho factory
PdfResponsesẽ dùng tên mặc định (document.pdf) thay vì tạo ra một disposition trống. Hãy truyền một tên tệp tường minh, có ý nghĩa. - Tên tệp không phải ASCII. Phản hồi Laravel tự động thêm tham số RFC 5987
filename*=cho tên không phải ASCII, còn tên ASCII dùng tham số thông thường. Đừng tự mã hóa tên tệp bằng tay. - Phản hồi theo luồng đứng sau một proxy có đệm. Một proxy đệm toàn bộ phần thân sẽ triệt tiêu lợi ích về bộ nhớ của việc truyền theo luồng. Hãy cấu hình proxy để truyền theo luồng các phản hồi PDF, hoặc dùng phản hồi được đệm trên đường dẫn đó.
- Callback theo luồng của Symfony. Biến thể theo luồng của Symfony trả về một
StreamedResponsemà callback của nó xả output. Đừng tự ghi vào phần thân phản hồi sau khi đã trả nó về.
Hiệu năng
Phần tiêu đề “Hiệu năng”Việc tạo đồng bộ bên trong một controller sẽ chặn yêu cầu trong suốt quá trình dựng đầy đủ PDF. Một tài liệu một trang thường nằm gọn trong ngân sách yêu cầu điển hình. Với output nhiều trang hoặc theo lô, hãy chuyển việc tạo ra khỏi luồng yêu cầu bằng một queued job — xem Tạo một tệp PDF trong một queued job. Các biến thể theo luồng giảm bộ nhớ đỉnh cho các tài liệu lớn, đổi lại là một Content-Length không xác định. Hãy chọn chúng khi bộ nhớ là ràng buộc và không cần thanh tiến trình.
Lưu ý bảo mật
Phần tiêu đề “Lưu ý bảo mật”- Các factory
PdfResponseáp dụng một tập header tăng cường phản hồi cố định và làm sạch tên tệp tải xuống trong mọi tích hợp. Đừng tự thêm các header này. - Đừng bao giờ chèn trực tiếp dữ liệu đầu vào của người dùng chưa được xác thực vào tên tệp mà bạn truyền cho factory. Hãy truyền một giá trị bạn kiểm soát, và để factory làm sạch nó như lớp thứ hai.
- Trong khối catch, hãy ghi log lớp ngoại lệ và một định danh tương quan, chứ không phải thông điệp hay trace của ngoại lệ. Một trace thô trong log sink là một nguồn rò rỉ thông tin.
- Đừng bao giờ viết một khối
catchrỗng. Mỗi ví dụ ở đây đều ghi log và trả về một phản hồi lỗi đã xác định.
Trang security-and-operations của mỗi tích hợp ghi lại mô hình mối đe dọa của tích hợp đó: tập header, quy tắc làm sạch tên tệp và vòng đời gắn kết tài liệu.
Sự phù hợp
Phần tiêu đề “Sự phù hợp”Hướng dẫn này không đưa ra tuyên bố tiêu chuẩn quy phạm nào. Mọi lệnh gọi API được trình bày đều là bề mặt công khai đã được xác minh của tích hợp được nêu tên, được đối chiếu chéo với các trang quickstart và production-usage của mỗi gói. Các trang production-usage thượng nguồn được liên kết trong mục Xem thêm ghi lại ngữ nghĩa header và hành vi gắn kết container mà các tích hợp dựa vào, cùng với các trích dẫn PSR của chúng. Trang cookbook này chỉ trình bày lại cách sử dụng và nhường các trích dẫn quy phạm cho những trang đó.
Xem thêm
Phần tiêu đề “Xem thêm”- Tạo một tệp PDF trong một queued job — chuyển công việc này ra khỏi luồng yêu cầu.
- Cách dùng Laravel trong production — controller được nối DI, tập header và hợp đồng gắn kết PSR-11.
- Quickstart Symfony — controller, inline, theo luồng và mô hình phản hồi.
- Quickstart CodeIgniter —
Services::pdf(), helperpdf(), vàPdfResponse. - Chọn một tích hợp — chọn đúng gói framework.