Pular para o conteúdo

NextPDF Symfony em produção

Use o bundle em runtimes PHP de longa duração. Ele cria documentos não compartilhados, bloqueia o registro de fontes após o aquecimento e redefine o cache de imagens entre requisições. Faça streaming de arquivos grandes em Portable Document Format (PDF) e delegue tarefas pesadas a workers do Messenger.

Runtimes de longa duração mantêm o contêiner ativo entre requisições; portanto, o estado específico de cada requisição deve permanecer isolado. FrankenPHP, RoadRunner e workers do Messenger seguem esse modelo. O services.php do bundle define o ciclo de vida abaixo, verificado em relação às definições de serviço:

  • Document — não compartilhado. O nextpdf.document (e os aliases PdfDocumentInterface / Document) é resolvido como uma nova instância em cada resolução. Sob o PSR-11 (PHP Standard Recommendation 11), um contêiner pode legitimamente retornar um valor diferente em cada get() para o mesmo id (PSR-11 §1.1.2). Resolva um documento por requisição. Nunca o mantenha entre requisições.
  • FontRegistry — compartilhado e bloqueado. O registro é um singleton durante toda a vida do processo. Após warmup() (quando preload_fonts não está vazio), o compiler pass chama lock(). O bloqueio impede a mutação em tempo de execução e a contaminação do estado das fontes entre requisições.
  • ImageRegistry — compartilhado, redefinido por requisição. O cache de imagens limitado, do tipo least recently used (LRU), é compartilhado, mas recebe a marcação kernel.reset com o método reset, então o Symfony o limpa entre requisições em runtimes que respeitam kernel.reset.
  • Contratos EInvoice — não compartilhados. Quando há implementações Premium presentes, os serviços de embedder, validator, profile e schematron são registrados como não compartilhados. O contexto do parser permanece restrito a cada chamada e nunca vaza entre requisições.

Injete PdfFactory, um objeto compartilhado e sem estado que mantém a configuração, e chame create() por requisição:

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

Não injete Document nem nextpdf.document em um serviço compartilhado mantido entre requisições. Em vez disso, resolva-o dentro do método com escopo de requisição.

PdfResponse::streamDownload() e streamInline() retornam um StreamedResponse. O callback envia o corpo do PDF em blocos de 64 KB e faz flush do buffer após cada bloco, o que limita o buffer de resposta para documentos grandes. Os comportamentos abaixo são verificados em relação ao PdfResponse:

  • As variantes em streaming omitem Content-Length intencionalmente, porque o objeto de resposta não conhece o tamanho do corpo de antemão. Barras de progresso de download e alguns proxies funcionam melhor com um tamanho conhecido. Use os métodos sem streaming download() ou inline() quando o documento for pequeno o suficiente para ser mantido em memória e um tamanho de conteúdo for desejável.
  • As variantes em streaming emitem os mesmos cabeçalhos de segurança e o mesmo Cache-Control: private, max-age=0, must-revalidate que as variantes com buffer.

Escolha o streaming para relatórios de vários megabytes e exportações em lote. Escolha as variantes com buffer para respostas pequenas e sensíveis à latência.

Delegue a geração ao Messenger quando as requisições precisarem retornar rapidamente ou quando a renderização exigir muito processamento.

  1. Implemente PdfBuilderInterface para cada tipo de documento.
  2. Registre os builders em um container.service_locator e conecte esse locator ao GeneratePdfHandler na propriedade $builderLocator.
  3. Roteie GeneratePdfMessage para um transporte durável.
  4. Execute os workers com tempos de vida limitados.

Recicle os workers para impedir que uma alocação vazada em uma dependência de terceiros cresça sem limite:

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

As chaves de configuração messenger.timeout e messenger.retries do bundle registram o timeout por mensagem e o orçamento pretendido de novas tentativas. Imponha o mesmo comportamento por meio da estratégia de novas tentativas do Symfony e das flags dos workers.

GeneratePdfMessage valida o caminho de saída no momento da construção. GeneratePdfHandler o revalida em tempo de execução, antes de gravar em disco. Essa verificação em duas etapas é importante para o trabalho assíncrono. Uma mensagem pode ficar em uma fila entre o despacho e o consumo; portanto, o handler não confia cegamente no caminho enfileirado. Restrinja as permissões de sistema de arquivos dos workers ao diretório de saída pretendido como defesa em profundidade.

Os serviços FontRegistry e ImageRegistry aceitam um Psr\Log\LoggerInterface opcional (vinculado com nullOnInvalid()). Quando a aplicação fornece um logger, esses registros podem emitir diagnósticos por meio dele. O logger é um colaborador opcional e substituível no contrato de logger PSR-3 (PSR-3). Para visibilidade no nível da requisição, registre logs em torno de PdfFactory::create() e do handler do Messenger no código da aplicação. Use messenger:consume -vv durante a triagem de incidentes.

  • Fixe uma versão major de nextpdf/core no composer.json da aplicação (o bundle aceita ^3.0 || ^5.2).
  • Certifique-se de que ext-mbstring e ext-zlib estejam habilitados na imagem PHP implantada (caso contrário, o bundle falha imediatamente na inicialização).
  • Defina previamente preload_fonts com as fontes que os documentos usam, para que o registro aqueça e bloqueie na inicialização, em vez de na primeira requisição.
  • Aponte cache_path para um local gravável e persistente se você depende de artefatos em cache entre implantações. Caso contrário, o padrão %kernel.cache_dir% é adequado.
  • Execute php bin/console cache:warmup durante a implantação para que o contêiner compilado (incluindo as sondas de extensões opcionais) seja construído antes do tráfego.
  • Use um transporte durável do Messenger (não sync) para o trabalho assíncrono em produção e recicle os workers com --limit / --memory-limit / --time-limit.
  • Respostas em streaming atrás de um proxy com buffer — Um proxy que armazena o corpo inteiro em buffer elimina o ganho de memória. Configure o proxy para fazer streaming de respostas PDF ou use respostas com buffer nesse caso.
  • kernel.reset não respeitado — Sob um runtime que não chama kernel.reset, o cache de imagens é limitado por image_cache_mb, mas não é limpo entre requisições; dimensione o limite adequadamente.
  • Manter um documento entre requisições — Um Document capturado de uma requisição anterior carregará estado obsoleto. Sempre resolva por requisição via PdfFactory.

Cada linha é uma afirmação normativa nesta página, vinculada a um reference_id completo de 64 caracteres hexadecimais do corpus restrito da standards development organization (SDO). A procedência do manifesto do corpus e do transporte de recuperação está em _sidecars/rag-citations.yaml.

EspecificaçãoCláusulareference_idAfirmação
PSR-11psr_11_container#1.1.2.p3.bServiço não compartilhado: valor distinto por resolução
PSR-3psr_3_logger#x3.p17Colaborador de logger opcional
  • /integrations/symfony/configuration/ — ciclo de vida do serviço e parâmetros.
  • /integrations/symfony/security-and-operations/ — cabeçalhos de resposta, validação de caminho, tratamento de chaves.
  • /integrations/symfony/troubleshooting/ — diagnósticos de inicialização e tempo de execução.
  • /integrations/symfony/quickstart/ — a configuração assíncrona mínima.