Zum Inhalt springen

NextPDF Symfony in der Produktion

Das Bundle ist auf langlebige PHP-Laufzeiten ausgelegt. Dokumente werden nicht geteilt, die Font-Registry wird nach dem Warmup gesperrt, und der Image-Cache wird zwischen den Requests zurückgesetzt. Streamen Sie große PDFs und lagern Sie rechenintensive Jobs an Messenger-Worker aus.

Langlebige Laufzeiten halten den Container über mehrere Requests hinweg am Leben; deshalb darf kein Zustand aus einem einzelnen Request durchsickern. FrankenPHP, RoadRunner und Messenger-Worker arbeiten alle nach diesem Prinzip. Die services.php des Bundles bildet den folgenden Lebenszyklus ab, geprüft anhand der Service-Definitionen:

  • Document — nicht geteilt. nextpdf.document (und die Aliase PdfDocumentInterface / Document) werden bei jeder Auflösung als frische Instanz bereitgestellt. Unter PSR-11 darf ein Container für dieselbe ID bei jedem get() legitim einen anderen Wert zurückgeben (PSR-11 §1.1.2). Lösen Sie pro Request ein Dokument auf. Halten Sie niemals eines über mehrere Requests hinweg.
  • FontRegistry — geteilt und gesperrt. Die Registry ist ein Singleton für die Prozesslebensdauer. Nach warmup() (wenn preload_fonts nicht leer ist) ruft der Compiler-Pass lock() auf. Die Sperre verhindert Änderungen zur Laufzeit und damit eine Verunreinigung des Font-Zustands über Request-Grenzen hinweg.
  • ImageRegistry — geteilt, pro Request zurückgesetzt. Der begrenzte Least-recently-used-Image-Cache (LRU) wird geteilt, ist aber mit kernel.reset für die Methode reset getaggt, sodass Symfony ihn in Laufzeiten, die kernel.reset berücksichtigen, zwischen den Requests leert.
  • EInvoice-Contracts — nicht geteilt. Wenn Premium-Implementierungen vorhanden sind, werden die Embedder-, Validator-, Profil- und Schematron-Services nicht geteilt registriert. Der Parser-Kontext eines einzelnen Aufrufs sickert dadurch niemals über Requests hinweg durch.

Injizieren Sie PdfFactory — einen geteilten, zustandslosen Konfigurationshalter — und rufen Sie create() pro Request auf:

public function __construct(private readonly PdfFactory $pdf) {}
public function action(): Response
{
$doc = $this->pdf->create(); // fresh, disposable
// ... build ...
return PdfResponse::inline($doc, 'document.pdf');
}

Injizieren Sie Document oder nextpdf.document nicht in einen Service, der selbst geteilt ist und über mehrere Requests hinweg gehalten wird. Lösen Sie es stattdessen innerhalb der Request-gebundenen Methode auf.

PdfResponse::streamDownload() und streamInline() geben einen StreamedResponse zurück. Der Callback gibt den PDF-Body in 64-KB-Chunks aus und führt nach jedem Chunk einen Flush aus. Das begrenzt den Response-Puffer bei großen Dokumenten. Die folgenden beiden Trade-offs wurden anhand von PdfResponse geprüft:

  • Die gestreamten Varianten lassen Content-Length bewusst weg (das Response-Objekt kennt die Body-Größe nicht im Voraus). Fortschrittsbalken beim Download und manche Proxys bevorzugen eine bekannte Länge. Verwenden Sie das nicht gestreamte download() oder inline(), wenn das Dokument klein genug ist, um im Speicher gehalten zu werden, und eine Content-Länge wünschenswert ist.
  • Die gestreamten Varianten geben dieselben Security-Header und dasselbe Cache-Control: private, max-age=0, must-revalidate aus wie die gepufferten Varianten.

Wählen Sie Streaming für mehrere Megabyte große Reports und Batch-Exporte. Wählen Sie die gepufferten Varianten für kleine, latenzkritische Responses.

Lagern Sie die Generierung an Messenger aus, wenn Requests schnell beantwortet werden müssen oder wenn das Rendern CPU-intensiv ist.

  1. Implementieren Sie PdfBuilderInterface für jeden Dokumenttyp.
  2. Registrieren Sie Builder in einem container.service_locator und verdrahten Sie diesen als den GeneratePdfHandler-$builderLocator.
  3. Routen Sie GeneratePdfMessage an einen dauerhaften Transport.
  4. Betreiben Sie Worker mit begrenzter Lebensdauer.

Recyceln Sie Worker, damit eine nicht freigegebene Allokation in einer Drittanbieterabhängigkeit nicht unbegrenzt anwachsen kann:

Terminal-Fenster
php bin/console messenger:consume async \
--limit=200 \
--memory-limit=256M \
--time-limit=3600

Die Konfigurationsschlüssel messenger.timeout und messenger.retries des Bundles legen das vorgesehene Timeout pro Nachricht und das Retry-Budget fest. Erzwingen Sie das passende Verhalten über die Retry-Strategie und die Worker-Flags von Symfony.

GeneratePdfMessage validiert den Ausgabepfad bei der Konstruktion. Anschließend validiert GeneratePdfHandler ihn zur Ausführungszeit erneut, bevor auf die Festplatte geschrieben wird. Diese zweistufige Prüfung ist für asynchrone Arbeit wichtig. Eine Nachricht kann zwischen Dispatch und Konsum in einer Queue liegen, daher vertraut der Handler dem in der Queue liegenden Pfad nicht blind. Beschränken Sie die Dateisystemberechtigungen der Worker als Defense-in-Depth auf das vorgesehene Ausgabeverzeichnis.

Die Services FontRegistry und ImageRegistry akzeptieren ein optionales Psr\Log\LoggerInterface (gebunden mit nullOnInvalid()). Wenn die Anwendung einen Logger bereitstellt, können die Registries Diagnosemeldungen ausgeben. Der Logger ist unter dem PSR-3-Logger-Contract (PSR-3) ein optionaler, austauschbarer Kollaborator. Für Sichtbarkeit auf Request-Ebene loggen Sie rund um PdfFactory::create() und den Messenger-Handler in Ihrem Anwendungscode. Verwenden Sie messenger:consume -vv während der Incident-Triage.

  • Fixieren Sie eine einzige nextpdf/core-Major-Version in der composer.json der Anwendung (das Bundle akzeptiert ^3.0 || ^5.2).
  • Stellen Sie sicher, dass ext-mbstring und ext-zlib im deployten PHP-Image aktiviert sind (andernfalls schlägt das Bundle beim Boot frühzeitig fehl).
  • Befüllen Sie preload_fonts vorab mit den Fonts, die Ihre Dokumente verwenden, damit die Registry beim Boot statt beim ersten Request aufgewärmt und gesperrt wird.
  • Richten Sie cache_path auf einen beschreibbaren, persistenten Ort, wenn Sie sich auf gecachte Artefakte über Deployments hinweg verlassen. Andernfalls ist der Standard %kernel.cache_dir% in Ordnung.
  • Führen Sie php bin/console cache:warmup beim Deployment aus, damit der kompilierte Container (einschließlich der Probes für optionale Erweiterungen) vor dem Traffic aufgebaut wird.
  • Verwenden Sie für produktive asynchrone Arbeit einen dauerhaften Messenger-Transport (nicht sync) und recyceln Sie Worker mit --limit / --memory-limit / --time-limit.
  • Gestreamte Responses hinter einem puffernden Proxy — ein Proxy, der den gesamten Body puffert, hebt den Speichervorteil auf. Konfigurieren Sie den Proxy so, dass er PDF-Responses streamt, oder verwenden Sie dort gepufferte Responses.
  • kernel.reset wird nicht berücksichtigt — in einer Laufzeit, die kernel.reset nicht aufruft, ist der Image-Cache durch image_cache_mb begrenzt, wird aber nicht zwischen den Requests geleert; dimensionieren Sie die Obergrenze entsprechend.
  • Ein Dokument über mehrere Requests hinweg halten — ein festgehaltenes Document aus einem vorherigen Request trägt veralteten Zustand mit sich. Lösen Sie es immer pro Request über PdfFactory auf.

Jede auf dieser Seite getroffene normative Aussage ist an eine vollständige 64-stellige hexadezimale reference_id aus dem zugangsbeschränkten SDO-Korpus gebunden. Die Provenienz, also das Korpus-Manifest und der Retrieval-Transport, befindet sich in _sidecars/rag-citations.yaml.

SpecKlauselreference_idAussage
PSR-11psr_11_container#1.1.2.p3.bNicht geteilter Service: bei jeder Auflösung ein eigener Wert
PSR-3psr_3_logger#x3.p17Optionaler Logger-Kollaborator
  • /integrations/symfony/configuration/ — Servicelebenszyklus und Parameter.
  • /integrations/symfony/security-and-operations/ — Response-Header, Pfadvalidierung, Schlüsselhandhabung.
  • /integrations/symfony/troubleshooting/ — Boot- und Laufzeitdiagnostik.
  • /integrations/symfony/quickstart/ — das minimale asynchrone Setup.