Messenger 整合¶
nextpdf/symfony 透過 Symfony Messenger 元件提供非同步 PDF 生成能力。GeneratePdfMessage 是純粹的 DTO(Data Transfer Object),GeneratePdfHandler 負責實際的文件生成與後處理。
PHP Compatibility
This example uses PHP 8.5 syntax. If your environment runs PHP 8.1 or 7.4, use NextPDF Backport for a backward-compatible build.
架構概覽¶
Controller/Service
└─▶ MessageBusInterface::dispatch(GeneratePdfMessage)
└─▶ [Transport: Redis/AMQP/Doctrine]
└─▶ GeneratePdfHandler::__invoke()
├─▶ PdfFactory::create()
├─▶ 生成 PDF bytes
└─▶ 儲存 + 通知
GeneratePdfMessage DTO¶
<?php
declare(strict_types=1);
namespace NextPDF\Symfony\Messenger;
final readonly class GeneratePdfMessage
{
public function __construct(
public readonly string $templateKey,
/** @var array<string, mixed> */
public readonly array $parameters = [],
public readonly ?string $notifyEmail = null,
public readonly ?string $storageKey = null,
) {}
}
派送訊息¶
<?php
declare(strict_types=1);
namespace App\Controller;
use NextPDF\Symfony\Messenger\GeneratePdfMessage;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Attribute\Route;
final class ReportController
{
public function __construct(
private readonly MessageBusInterface $bus,
) {}
#[Route('/reports/{id}/generate', methods: ['POST'])]
public function generate(int $id): JsonResponse
{
$this->bus->dispatch(new GeneratePdfMessage(
templateKey: 'annual_report',
parameters: ['reportId' => $id, 'year' => 2026],
notifyEmail: '[email protected]',
storageKey: "reports/annual-{$id}.pdf",
));
return new JsonResponse(['status' => 'queued', 'reportId' => $id]);
}
}
GeneratePdfHandler¶
Handler 由 Bundle 自動注冊,透過 #[AsMessageHandler] 屬性聲明:
<?php
declare(strict_types=1);
namespace NextPDF\Symfony\Messenger;
use NextPDF\Symfony\Service\PdfFactory;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
final class GeneratePdfHandler
{
public function __construct(
private readonly PdfFactory $factory,
private readonly PdfTemplateRegistry $templateRegistry,
private readonly PdfStorageInterface $storage,
private readonly PdfNotifierInterface $notifier,
) {}
public function __invoke(GeneratePdfMessage $message): void
{
// 從 templateKey 取得模板建構邏輯
$builder = $this->templateRegistry->get($message->templateKey);
// 生成 PDF
$document = $this->factory->create();
$builder->build(document: $document, parameters: $message->parameters);
$pdfBytes = $document->output();
// 儲存
if ($message->storageKey !== null) {
$this->storage->store(key: $message->storageKey, content: $pdfBytes);
}
// 通知
if ($message->notifyEmail !== null) {
$this->notifier->notify(email: $message->notifyEmail, storageKey: $message->storageKey);
}
}
}
模板 Registry¶
透過服務標籤(Service Tag)注冊自訂 PDF 模板建構器:
// services.yaml
services:
App\Pdf\Templates\InvoiceTemplate:
tags:
- { name: 'nextpdf.template', key: 'invoice' }
App\Pdf\Templates\AnnualReportTemplate:
tags:
- { name: 'nextpdf.template', key: 'annual_report' }
<?php
declare(strict_types=1);
namespace App\Pdf\Templates;
use NextPDF\Core\Document;
use NextPDF\Symfony\Contract\PdfTemplateInterface;
final class InvoiceTemplate implements PdfTemplateInterface
{
/** @param array<string, mixed> $parameters */
public function build(Document $document, array $parameters): void
{
$invoice = Invoice::find($parameters['invoiceId']);
$document->addPage();
$document->text("Invoice #{$invoice->id}", x: 20, y: 30, fontSize: 20);
$document->text($invoice->customerName, x: 20, y: 50);
// ... 更多內容
}
}
重試與失敗策略¶
# config/integrations/messenger.yaml
framework:
messenger:
transports:
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
retry_strategy:
max_retries: 3
delay: 1000 # 初始延遲(毫秒)
multiplier: 2 # 指數退避倍數
max_delay: 30000 # 最長延遲(毫秒)
failed:
dsn: 'doctrine://default?queue_name=failed'
routing:
'NextPDF\Symfony\Messenger\GeneratePdfMessage': async
failure_transport: failed
消費者(Worker)啟動¶
# 啟動 Messenger Worker(消費 async 傳輸的訊息)
php bin/console messenger:consume async --limit=100 --memory-limit=256M
# 使用 Supervisor 管理 Worker 程序
php bin/console messenger:consume async --time-limit=3600
測試¶
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Messenger\Transport\InMemoryTransport;
final class GeneratePdfHandlerTest extends KernelTestCase
{
public function testHandlerGeneratesPdf(): void
{
$kernel = static::bootKernel();
// 使用 in-memory 傳輸測試
$this->assertNotNull($kernel->getContainer());
$bus = static::getContainer()->get('messenger.default_bus');
$bus->dispatch(new GeneratePdfMessage(
templateKey: 'invoice',
parameters: ['invoiceId' => 1],
));
/** @var InMemoryTransport $transport */
$transport = static::getContainer()->get('messenger.transport.async');
$this->assertCount(1, $transport->getSent());
}
}
參見¶
- Symfony 套件總覽 — Bundle 架構概覽
- Bundle 設定 — Messenger 傳輸設定
- PdfFactory — Handler 使用的工廠服務