Bỏ qua để đến nội dung

Sử dụng gói NextPDF Laravel trong production

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.

Terminal window
composer require nextpdf/laravel
php artisan vendor:publish --tag=nextpdf-config

Hã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.

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.

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”
resource: NextPDF\Contracts\PdfDocumentInterface + src/Laravel/Http/PdfResponse.php
<?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.

resource: src/Laravel/Jobs/GeneratePdfJob.php
<?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()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.

Thuộc tínhMặc địnhKhóa cấu hình
tries3không điều khiển qua cấu hình; tạo lớp con để thay đổi
timeout120nextpdf.queue.timeout
backoff10không điều khiển qua cấu hình; tạo lớp con để thay đổi
tên hàng đợipdfnextpdf.queue.queue
kết nốidefaultnextpdf.queue.connection

triesbackoff 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.

  • 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 SignerInterface trả về null trừ 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ết tries. Thất bại ở từng lần thử không gọi onFailure cho đến khi queue runner xác nhận thất bại sau cù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.

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/.

Tuyên bốNguồnĐiều khoảnreference_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.

Đầ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.

  • /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