Salta ai contenuti

NextPDF Symfony in produzione

Il bundle è progettato per runtime PHP a lunga esecuzione. I documenti non vengono condivisi, il registro dei font viene bloccato dopo il warmup e la cache delle immagini viene reimpostata tra una richiesta e l’altra. Usare lo streaming per i PDF di grandi dimensioni e delegare le operazioni onerose ai worker di Messenger.

I runtime a lunga esecuzione mantengono attivo il container tra le richieste; di conseguenza, lo stato relativo alla singola richiesta non deve propagarsi. I worker di FrankenPHP, RoadRunner e Messenger seguono tutti questo modello. Il file services.php del bundle codifica il ciclo di vita riportato di seguito, verificato rispetto alle definizioni dei servizi:

  • Document — non condiviso. nextpdf.document (e gli alias PdfDocumentInterface / Document) viene risolto ogni volta in una nuova istanza. In base a PSR-11, un container può legittimamente restituire un valore diverso a ogni get() per lo stesso id (PSR-11 §1.1.2). Risolvere un documento per ogni richiesta. Non conservarne mai uno tra una richiesta e l’altra.
  • FontRegistry — condiviso e bloccato. Il registro è un singleton con durata pari a quella del processo. Dopo warmup() (quando preload_fonts non è vuoto) il compiler pass chiama lock(). Il blocco impedisce la mutazione a runtime e, quindi, la contaminazione dello stato dei font tra le richieste.
  • ImageRegistry — condiviso, reimpostato a ogni richiesta. La cache delle immagini con limite e politica least-recently-used (LRU) è condivisa, ma contrassegnata con il tag kernel.reset e il metodo reset, in modo che Symfony la svuoti tra le richieste con i runtime che rispettano kernel.reset.
  • Contratti EInvoice — non condivisi. Quando sono presenti le implementazioni Premium, i servizi embedder, validator, profile e schematron vengono registrati come non condivisi. Il contesto del parser relativo alla singola chiamata non si propaga mai tra una richiesta e l’altra.

Iniettare PdfFactory — un contenitore di configurazione condiviso e privo di stato — e chiamare create() per ogni richiesta:

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

Non iniettare Document o nextpdf.document in un servizio a sua volta condiviso e mantenuto tra le richieste. Risolverlo invece all’interno del metodo con ambito di richiesta.

PdfResponse::streamDownload() e streamInline() restituiscono una StreamedResponse. Il relativo callback emette il corpo del PDF in blocchi da 64 KB ed esegue il flush dopo ciascun blocco. In questo modo si limita il buffer della risposta per i documenti di grandi dimensioni. Entrambi i compromessi riportati di seguito sono verificati rispetto a PdfResponse:

  • Le varianti in streaming omettono intenzionalmente Content-Length (l’oggetto risposta non conosce in anticipo la dimensione del corpo). Le barre di avanzamento dei download e alcuni proxy funzionano meglio con una lunghezza nota. Utilizzare le versioni non in streaming download() o inline() quando il documento è abbastanza piccolo da poter essere tenuto in memoria ed è preferibile disporre di una lunghezza del contenuto.
  • Le varianti in streaming emettono gli stessi header di sicurezza e lo stesso Cache-Control: private, max-age=0, must-revalidate delle varianti con buffer.

Scegliere lo streaming per report di diversi megabyte ed esportazioni in batch. Scegliere le varianti con buffer per risposte di piccole dimensioni e sensibili alla latenza.

Delegare la generazione a Messenger quando le richieste devono rispondere rapidamente o quando il rendering richiede molta CPU.

  1. Implementare PdfBuilderInterface per ogni tipo di documento.
  2. Registrare i builder in un container.service_locator e collegarlo a GeneratePdfHandler come suo $builderLocator.
  3. Instradare GeneratePdfMessage verso un transport durevole.
  4. Eseguire i worker con durata di vita limitata.

Riciclare i worker affinché una perdita di memoria in una dipendenza di terze parti non possa crescere senza limiti:

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

Le chiavi di configurazione messenger.timeout e messenger.retries del bundle registrano il timeout e il budget di retry previsti per ogni messaggio. Applicare il comportamento corrispondente tramite la strategia di retry di Symfony e i flag dei worker.

GeneratePdfMessage convalida il percorso di output al momento della costruzione. GeneratePdfHandler lo riconvalida poi in fase di esecuzione, prima di scrivere su disco. Questo controllo in due fasi è importante per il lavoro asincrono. Un messaggio può rimanere in coda tra l’invio e il consumo; pertanto, l’handler non si fida ciecamente del percorso accodato. Come difesa in profondità, limitare le autorizzazioni del filesystem dei worker alla directory di output prevista.

I servizi FontRegistry e ImageRegistry accettano un Psr\Log\LoggerInterface facoltativo (associato con nullOnInvalid()). Quando l’applicazione fornisce un logger, i registri possono emettere informazioni diagnostiche tramite quel logger. Il logger è un collaboratore facoltativo e sostituibile in base al contratto del logger PSR-3 (PSR-3). Per la visibilità a livello di richiesta, registrare i log attorno a PdfFactory::create() e all’handler di Messenger nel codice dell’applicazione. Utilizzare messenger:consume -vv durante il triage degli incidenti.

  • Bloccare un’unica major di nextpdf/core nel composer.json dell’applicazione (il bundle accetta ^3.0 || ^5.2).
  • Verificare che ext-mbstring e ext-zlib siano abilitate nell’immagine PHP distribuita (in caso contrario, il bundle si arresta immediatamente all’avvio).
  • Pre-popolare preload_fonts con i font utilizzati dai documenti, in modo che il registro venga scaldato e bloccato all’avvio anziché alla prima richiesta.
  • Impostare cache_path su una posizione scrivibile e persistente se si fa affidamento su artefatti memorizzati nella cache tra un deployment e l’altro. In caso contrario, il valore predefinito %kernel.cache_dir% va bene.
  • Eseguire php bin/console cache:warmup durante il deployment, in modo che il container compilato (incluse le sonde delle estensioni facoltative) venga creato prima del traffico.
  • Utilizzare un transport Messenger durevole (non sync) per il lavoro asincrono in produzione e riciclare i worker con --limit / --memory-limit / --time-limit.
  • Risposte in streaming dietro un proxy con buffering — un proxy che memorizza nel buffer l’intero corpo annulla il vantaggio in termini di memoria. Configurare il proxy affinché esegua lo streaming delle risposte PDF oppure utilizzare risposte con buffer in quel contesto.
  • kernel.reset non rispettato — con un runtime che non chiama kernel.reset, la cache delle immagini resta limitata da image_cache_mb ma non viene svuotata tra le richieste; dimensionare il limite di conseguenza.
  • Conservare un documento tra le richieste — un Document catturato da una richiesta precedente porterà con sé stato obsoleto. Risolverlo sempre per ogni richiesta tramite PdfFactory.

Ogni riga è un’affermazione normativa formulata in questa pagina, ancorata a un reference_id completo di 64 cifre esadecimali proveniente dal corpus SDO ad accesso riservato. La provenienza, ovvero il manifest del corpus e il transport di recupero, è disponibile in _sidecars/rag-citations.yaml.

SpecificaClausolareference_idAffermazione
PSR-11psr_11_container#1.1.2.p3.bServizio non condiviso: valore distinto a ogni risoluzione
PSR-3psr_3_logger#x3.p17Collaboratore logger facoltativo
  • /integrations/symfony/configuration/ — ciclo di vita dei servizi e parametri.
  • /integrations/symfony/security-and-operations/ — header delle risposte, convalida dei percorsi, gestione delle chiavi.
  • /integrations/symfony/troubleshooting/ — diagnostica di avvio e runtime.
  • /integrations/symfony/quickstart/ — configurazione asincrona minima.