Sử dụng gói NextPDF Laravel trong production
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”Trong môi trường production, hãy phân giải contract của tài liệu bằng cách tiêm qua constructor. Xử lý lỗi ghi PDF bằng một exception cụ thể. Chuyển các tác vụ tạo PDF nặng hoặc theo lô sang GeneratePdfJob, đồng thời nối rõ ràng các callback thành công và thất bại.
Cài đặt
Phần tiêu đề “Cài đặt”composer require nextpdf/laravelphp artisan vendor:publish --tag=nextpdf-configHãy cấu hình kết nối hàng đợi trong config/nextpdf.php. Thiết lập queue.connection, queue.queue, và queue.timeout. Sau đó, bảo đảm có một worker đang chạy trên kết nối đã cấu hình.
Tổng quan khái niệm
Phần tiêu đề “Tổng quan khái niệm”Container cung cấp NextPDF\Contracts\PdfDocumentInterface dưới dạng một factory binding. Mỗi lần phân giải sẽ tạo một NextPDF\Core\Document mới. PSR-11 cho phép container trả về các giá trị khác nhau qua những lần gọi get() liên tiếp, tùy theo chiến lược binding (PSR-11 §1.1.2). Gói này dùng factory binding để trạng thái có thể thay đổi trong phạm vi một request không bao giờ lẫn sang request khác. Registry phông chữ và hình ảnh là singleton. Cách này vẫn giữ đúng contract rằng một định danh đã bind sẽ phân giải về entry đã đăng ký của nó (PSR-11 §1.1.2), đồng thời dùng chung các tài nguyên tốn kém trên toàn worker.
Trong mã production, hãy ưu tiên tiêm qua constructor thay vì dùng facade. Cách này làm cho các phụ thuộc rõ ràng hơn và giúp controller có thể kiểm thử đơn vị mà không cần khởi động facade root.
Mã ví dụ — Production
Phần tiêu đề “Mã ví dụ — Production”Controller được nối qua DI với xử lý lỗi có kiểu
Phần tiêu đề “Controller được nối qua DI với xử lý lỗi có kiểu”<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;use NextPDF\Contracts\PdfDocumentInterface;use NextPDF\Laravel\Http\PdfResponse;use Psr\Log\LoggerInterface;use Throwable;
final class InvoiceController extends Controller{ public function __construct( private readonly PdfDocumentInterface $document, private readonly LoggerInterface $logger, ) {}
public function show(int $invoiceId): Response { try { $this->document->addPage(); $this->document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true); $this->document->cell(0, 10, 'Thank you for your business.');
return PdfResponse::download( $this->document, "invoice-{$invoiceId}.pdf", ); } catch (Throwable $exception) { // Rethrow as an HTTP-meaningful failure; never swallow. $this->logger->error('Invoice PDF generation failed', [ 'invoice_id' => $invoiceId, 'exception' => $exception::class, ]);
return new Response('Could not generate the invoice PDF.', 500); } }}Hãy tiêm PdfDocumentInterface, không phải lớp cụ thể Document, để có thể thay binding trong các bài kiểm thử. Container trả về một tài liệu mới cho mỗi lần khởi tạo controller. Đừng tái sử dụng cùng một instance controller cho hai tài liệu không liên quan trong cùng một tiến trình.
Khối catch ghi log lớp của exception và trả về một lỗi HTTP đã xác định thay vì làm lộ stack trace. Hãy dùng Psr\Log\LoggerInterface; container sẽ phân giải interface này về logger của framework. PSR-3 giao việc escape placeholder cho bên triển khai và yêu cầu bên gọi không escape trước các giá trị context (PSR-3 §1.2). Hãy truyền context có cấu trúc, không truyền chuỗi đã nội suy.
Tạo qua hàng đợi với callback thành công và thất bại
Phần tiêu đề “Tạo qua hàng đợi với callback thành công và thất bại”GeneratePdfJob là một job ShouldQueue. Mặc định, job này dùng ba lần thử, timeout 120 giây, và backoff 10 giây. Bạn có thể ghi đè cả ba trong config/nextpdf.php. Closure builder nhận tài liệu do container phân giải và phải trả về một tài liệu đã được cấu hình.
<?php
declare(strict_types=1);
namespace App\Jobs;
use NextPDF\Contracts\PdfDocumentInterface;use NextPDF\Laravel\Jobs\GeneratePdfJob;use Psr\Log\LoggerInterface;use Throwable;
final class DispatchMonthlyStatement{ public function __construct(private readonly LoggerInterface $logger) {}
public function __invoke(int $accountId): void { // Dispatchable::dispatch() is `public static`: it constructs the // job from the arguments it receives and returns a PendingDispatch. // Pass every constructor argument — including the callbacks — to // the static call. Building an instance and then calling // `$job->dispatch(...)` would discard that instance (and its // callbacks) and queue a different job from only the static args. GeneratePdfJob::dispatch( storage_path("app/statements/{$accountId}.pdf"), static fn (PdfDocumentInterface $document): PdfDocumentInterface => $document ->addPage() ->cell(0, 10, "Statement for account {$accountId}", newLine: true), function (string $path) use ($accountId): void { $this->logger->info('Statement PDF written', [ 'account_id' => $accountId, 'path' => $path, ]); }, function (Throwable $exception) use ($accountId): void { $this->logger->error('Statement PDF failed', [ 'account_id' => $accountId, 'exception' => $exception::class, ]); }, ); }}GeneratePdfJob::dispatch() chuyển tiếp các tham số của nó trực tiếp đến constructor (string $outputPath, callable $builder, ?callable $onSuccess, ?callable $onFailure). Nhờ vậy, callback thành công và thất bại được nối vào đúng job được đưa vào hàng đợi. Điều này khớp với dạng theo vị trí GeneratePdfJob::dispatch($path, $builder) trong /integrations/laravel/quickstart/. Callback thành công nhận đường dẫn đầu ra, còn callback thất bại nhận Throwable. Job cũng cung cấp các setter fluent then() và catch() trả về chính job để nối chuỗi. Chỉ dùng các setter đó khi bạn giữ và dispatch cùng instance đó, ví dụ thông qua helper dispatch(). Job cũng cung cấp phương thức failed(), được queue runner gọi khi thất bại sau cùng. Các callback được gói trong những closure có thể tuần tự hóa để chúng tồn tại qua quá trình truyền của hàng đợi.
Tinh chỉnh hành vi hàng đợi
Phần tiêu đề “Tinh chỉnh hành vi hàng đợi”| Thuộc tính | Mặc định | Khóa cấu hình |
|---|---|---|
tries | 3 | không điều khiển qua cấu hình; tạo lớp con để thay đổi |
timeout | 120 | nextpdf.queue.timeout |
backoff | 10 | không điều khiển qua cấu hình; tạo lớp con để thay đổi |
| tên hàng đợi | pdf | nextpdf.queue.queue |
| kết nối | default | nextpdf.queue.connection |
tries và backoff là các thuộc tính public được đọc từ instance của job. Job được phát hành không đọc các giá trị này từ cấu hình. Nếu chính sách thử lại của bạn khác, hãy tạo lớp con của GeneratePdfJob để ghi đè chúng.
Trường hợp biên và điểm cần lưu ý
Phần tiêu đề “Trường hợp biên và điểm cần lưu ý”- Closure builder phải trả về một
PdfDocumentInterface. Job lưu giá trị trả về đó, không phải instance được phân giải ban đầu. Bài kiểm thử job xác nhận rõ contract này. - Phân giải
SignerInterfacetrả vềnulltrừ khi việc ký được bật, một chứng chỉ được cấu hình, vànextpdf/premiumđược cài đặt. Hãy luôn kiểm tra null trước khi ký. - Các worker chạy lâu dài (Octane/RoadRunner/Swoole) dùng chung registry phông chữ đã khóa. Hãy cấu hình
preload_fontsđể quá trình khởi động ấm chạy một lần khi worker khởi động thay vì ở request đầu tiên. - Một job thất bại gọi
failed()sau khi đã dùng hếttries. Thất bại ở từng lần thử không gọionFailurecho đến khi queue runner xác nhận thất bại sau cùng.
Hiệu năng
Phần tiêu đề “Hiệu năng”Việc tạo đồng bộ trong controller sẽ chặn request trong suốt quá trình dựng PDF. Với đầu ra nhiều trang hoặc theo lô, hãy dispatch GeneratePdfJob và trả về ngay lập tức. Các registry singleton phân bổ chi phí phân tích phông chữ và giải mã hình ảnh trên suốt vòng đời của worker. Khi đó, chi phí trên mỗi request chỉ giới hạn ở việc dựng tài liệu và phát ra nội dung.
Ghi chú bảo mật
Phần tiêu đề “Ghi chú bảo mật”Controller dùng tiêm phụ thuộc ghi log lớp của exception, không ghi thông điệp hay trace của nó, để tránh làm lộ chi tiết nội bộ vào log. GeneratePdfJob kiểm tra đường dẫn đầu ra trên worker để giảm thiểu rủi ro payload đã tuần tự hóa bị giả mạo trong quá trình truyền qua hàng đợi. Nội dung đầy đủ nằm trong /integrations/laravel/security-and-operations/.
Tính tuân thủ
Phần tiêu đề “Tính tuân thủ”| Tuyên bố | Nguồn | Điều khoản | reference_id |
|---|---|---|---|
| Định danh đã bind phân giải về entry đã đăng ký của nó | PSR-11 Container | §1.1.2 | |
| Các lần phân giải liên tiếp có thể khác nhau tùy theo chiến lược binding (factory binding) | PSR-11 Container | §1.1.2 |
Hướng dẫn ghi log PSR-3 nằm trong đặc tả PSR-3. Hướng dẫn đó giao việc escape placeholder cho bên triển khai và yêu cầu bên gọi truyền context có cấu trúc. Xem tài liệu psr_3_logger §1.2.
Bối cảnh thương mại
Phần tiêu đề “Bối cảnh thương mại”Đầu ra PAdES B-B đã ký và lưu trữ PDF/A thông qua nextpdf/premium dùng cùng bề mặt tiêm phụ thuộc (DI). Đây là một khả năng Enterprise tùy chọn. Gói Core được mô tả ở đây không cần thay đổi mã để sử dụng khả năng này. Xem https://nextpdf.dev/get-license/?intent=laravel-signing.
Xem thêm
Phần tiêu đề “Xem thêm”- /integrations/laravel/quickstart/ — ví dụ đầu tiên tối giản
- /integrations/laravel/configuration/ — các khóa hàng đợi, chữ ký và phông chữ
- /integrations/laravel/security-and-operations/ — mô hình mối đe dọa và tăng cường bảo mật
- /integrations/laravel/troubleshooting/ — các lỗi production thường gặp