Retornar um PDF gerado por um controller
Visão geral
Seção intitulada “Visão geral”Gere um arquivo Portable Document Format (PDF) em uma action de controller e retorne-o como resposta Hypertext Transfer Protocol (HTTP). Cada integração de framework inclui um helper PdfResponse que monta o objeto de resposta, define Content-Type: application/pdf, anexa os cabeçalhos de segurança e sanitiza o nome do arquivo. Este guia cobre os três modos de entrega para Laravel, Symfony e CodeIgniter 4: visualização inline, download de arquivo e entrega via stream.
Confira primeiro estes pré-requisitos, para que o caminho do controller esteja pronto antes de você começar:
- O core do NextPDF está instalado.
- Uma integração de framework está instalada, e o respectivo service provider, bundle ou service já foi descoberto. Confirme a descoberta na página de instalação do framework antes de começar.
- O modo via stream não precisa de pacotes adicionais. Todas as integrações incluem a variante via stream junto com a variante com buffer.
Este é um guia prático (how-to). Ele pressupõe que você já sabe como rotear uma requisição até um controller no seu framework. Para ver um primeiro exemplo executável em cada framework, leia o quickstart do framework referenciado em Veja também.
Instalação
Seção intitulada “Instalação”Instale a integração do seu framework. Execute um dos comandos a seguir.
composer require nextpdf/laravelcomposer require nextpdf/symfonycomposer require nextpdf/codeigniterNo Laravel, publique a configuração após a instalação.
php artisan vendor:publish --tag=nextpdf-configO Symfony registra o bundle via Flex, e o CodeIgniter descobre o service automaticamente. Confirme a descoberta na página de instalação do framework antes de continuar.
Visão conceitual
Seção intitulada “Visão conceitual”Toda integração de framework segue a mesma estrutura em três partes: você obtém um documento novo, escreve conteúdo nele e o passa para uma factory PdfResponse, que retorna uma resposta HTTP. A API do documento (addPage(), cell(), setFont()) é a superfície do motor do core, e ela é idêntica em todos os frameworks. A factory de resposta difere apenas na classe de resposta retornada, porque cada framework tem seu próprio tipo de resposta HTTP.
PdfResponse oferece três modos de entrega. Inline define um cabeçalho Content-Disposition: inline, de modo que o navegador renderize o PDF em uma aba de visualização. Download define Content-Disposition: attachment, de modo que o navegador salve o arquivo. Streamed emite o corpo do PDF em blocos de tamanho fixo em vez de manter o documento inteiro em buffer na memória. Escolha-o para documentos grandes quando o pico de memória for mais importante do que um Content-Length conhecido.
Obtenha o documento pelo caminho de resolução habitual do seu framework:
- Laravel — resolva
NextPDF\Contracts\DocumentFactoryInterfacea partir do container comapp(...)e chamecreate(), que retorna um novoNextPDF\Core\Document— o tipo concreto que as factoriesPdfResponseaceitam. - Symfony — injete
NextPDF\Symfony\Service\PdfFactorye chamecreate(), que retorna um novoNextPDF\Core\Documentcom os padrões de documento configurados já aplicados. - CodeIgniter 4 — resolva a biblioteca
Pdfpor meio deServices::pdf()(ou do helperpdf()), ou obtenha um documento simples por meio depdf_document().
Superfície da API
Seção intitulada “Superfície da API”| Aspecto | Laravel | Symfony | CodeIgniter 4 |
|---|---|---|---|
| Documento novo | app(DocumentFactoryInterface::class)->create() | PdfFactory::create() | pdf_document() / Services::pdf()->document() |
| Resposta inline | PdfResponse::inline($doc, $name) | PdfResponse::inline($doc, $name) | $pdf->inline($name) / PdfResponse::inline($doc, $name) |
| Resposta de download | PdfResponse::download($doc, $name) | PdfResponse::download($doc, $name) | $pdf->download($name) / PdfResponse::download($doc, $name) |
| Inline via stream | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) |
| Download via stream | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) |
| Tipo retornado | Illuminate\Http\Response (via stream: StreamedResponse) | Symfony\Component\HttpFoundation\Response (via stream: StreamedResponse) | CodeIgniter\HTTP\DownloadResponse |
O PdfResponse do Laravel fica em NextPDF\Laravel\Http\PdfResponse, o do Symfony em NextPDF\Symfony\Http\PdfResponse, e o do CodeIgniter em NextPDF\CodeIgniter\Http\PdfResponse. A página de segurança e operações de cada integração documenta o comportamento completo de resposta daquele pacote: conjunto de cabeçalhos, regras de disposition e sanitização do nome do arquivo. Essas páginas estão listadas em Veja também.
Exemplo de código — Início rápido
Seção intitulada “Exemplo de código — Início rápido”Aqui está a action mínima de download em cada framework. As chamadas ao documento usam a mesma superfície do core. Apenas a estrutura do controller muda.
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;use NextPDF\Contracts\DocumentFactoryInterface;use NextPDF\Laravel\Http\PdfResponse;
final class ReportController extends Controller{ public function download(): Response { $document = app(DocumentFactoryInterface::class)->create(); $document->addPage(); $document->cell(0, 10, 'Monthly report', newLine: true);
return PdfResponse::download($document, 'report.pdf'); }}<?php
declare(strict_types=1);
namespace App\Controller;
use NextPDF\Symfony\Http\PdfResponse;use NextPDF\Symfony\Service\PdfFactory;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Routing\Attribute\Route;
final class ReportController{ #[Route('/report', name: 'report_pdf')] public function download(PdfFactory $pdf): Response { $document = $pdf->create(); $document->addPage(); $document->cell(0, 10, 'Monthly report', newLine: true);
return PdfResponse::download($document, 'report.pdf'); }}<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\DownloadResponse;use NextPDF\CodeIgniter\Config\Services;
final class ReportController extends BaseController{ public function download(): DownloadResponse { $pdf = Services::pdf(); $pdf->document()->addPage(); $pdf->document()->cell(0, 10, 'Monthly report');
return $pdf->download('report.pdf'); }}Para visualizar no navegador em vez de baixar, troque a chamada download(...) por inline(...) no Laravel e no Symfony, ou por $pdf->inline('report.pdf') no CodeIgniter. A disposition muda para inline, e todos os demais cabeçalhos permanecem iguais.
Exemplo de código — Produção
Seção intitulada “Exemplo de código — Produção”Uma action de produção injeta suas dependências, captura a exceção mais específica documentada pela integração, registra a classe da falha sem vazar um trace e retorna um erro HTTP definido. O exemplo abaixo usa injeção via construtor do Laravel. Os equivalentes em Symfony e CodeIgniter seguem a mesma estrutura e aparecem na página de uso em produção de cada integração.
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;use NextPDF\Contracts\DocumentFactoryInterface;use NextPDF\Laravel\Http\PdfResponse;use Psr\Log\LoggerInterface;use Throwable;
final class InvoiceController extends Controller{ public function __construct( private readonly DocumentFactoryInterface $documents, private readonly LoggerInterface $logger, ) {}
public function show(int $invoiceId): Response { try { $document = $this->documents->create(); $document->addPage(); $document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true);
return PdfResponse::download( $document, "invoice-{$invoiceId}.pdf", ); } catch (Throwable $exception) { // Log the exception class, never the message or a stack trace, // so internal detail does not leak into the log sink. $this->logger->error('Invoice PDF generation failed', [ 'invoice_id' => $invoiceId, 'exception' => $exception::class, ]);
return new Response('Could not generate the invoice PDF.', 500); } }}Injete DocumentFactoryInterface e chame create() em cada action. Isso retorna um novo NextPDF\Core\Document — o tipo concreto que as factories PdfResponse do Laravel aceitam. Resolver um documento novo por requisição mantém a factory substituível em testes. Não reutilize uma instância de controller para dois documentos sem relação dentro de um único processo worker de longa duração.
Para documentos muito grandes, substitua a factory com buffer por uma via stream para limitar o pico de memória. A variante via stream retorna um StreamedResponse (Laravel e Symfony) e emite o corpo em blocos de tamanho fixo. Ela omite deliberadamente Content-Length, de modo que barras de progresso de download e proxies sensíveis ao tamanho não veem um tamanho conhecido. Prefira o download() / inline() com buffer para respostas pequenas e sensíveis à latência.
$document = $this->documents->create();// ... emit content onto $document ...return PdfResponse::streamDownload($document, 'annual-report.pdf');Casos extremos e pegadinhas
Seção intitulada “Casos extremos e pegadinhas”- Documento novo por chamada. Nas três integrações, o documento é um produto de factory, novo a cada resolução. Não mantenha em cache um documento resolvido entre documentos lógicos, nem entre requisições em um worker de longa duração. O estado de conteúdo obsoleto é levado adiante.
- Nome de arquivo vazio. Um nome de arquivo vazio passado para uma factory
PdfResponserecai sobre um nome padrão (document.pdf) em vez de produzir uma disposition em branco. Passe um nome de arquivo explícito e significativo. - Nomes de arquivo não-ASCII. A resposta do Laravel adiciona automaticamente um parâmetro RFC 5987
filename*=para nomes não-ASCII, e nomes ASCII usam o parâmetro simples. Não codifique o nome do arquivo manualmente. - Respostas via stream atrás de um proxy com buffer. Um proxy que mantém o corpo inteiro em buffer anula o benefício de memória do streaming. Configure o proxy para fazer streaming das respostas PDF, ou use uma resposta com buffer nesse caminho.
- Callback via stream do Symfony. A variante via stream do Symfony retorna um
StreamedResponsecujo callback faz o flush da saída. Não escreva no corpo da resposta por conta própria depois de devolvê-la.
Desempenho
Seção intitulada “Desempenho”A geração síncrona dentro de um controller bloqueia a requisição durante toda a construção do PDF. Um documento de página única costuma caber bem em um orçamento típico de requisição. Para saídas em múltiplas páginas ou em lote, mova a geração para fora da thread da requisição com um job enfileirado — veja Gerar um PDF em um job enfileirado. As variantes via stream reduzem o pico de memória para documentos grandes ao custo de um Content-Length desconhecido. Escolha-as quando a memória for a restrição e uma barra de progresso não for necessária.
Notas de segurança
Seção intitulada “Notas de segurança”- As factories
PdfResponseaplicam um conjunto fixo de cabeçalhos de fortalecimento da resposta e sanitizam o nome do arquivo de download em todas as integrações. Não adicione esses cabeçalhos por conta própria. - Nunca interpole entrada de usuário não validada diretamente em um nome de arquivo que você passa para a factory. Passe um valor que você controla e deixe a factory sanitizá-lo como uma segunda camada.
- No bloco catch, registre a classe da exceção e um identificador de correlação, não a mensagem da exceção nem o trace. Um trace bruto em um destino de log representa vazamento de informação.
- Nunca escreva um bloco
catchvazio. Cada exemplo aqui registra em log e retorna uma resposta de erro definida.
A página de segurança e operações de cada integração documenta o modelo de ameaças daquela integração: conjunto de cabeçalhos, regras de sanitização do nome do arquivo e o tempo de vida do binding do documento.
Conformidade
Seção intitulada “Conformidade”Este guia não faz nenhuma alegação normativa de conformidade com padrões. Cada chamada de API mostrada é a superfície pública verificada da integração citada, conferida contra as páginas de quickstart e de uso em produção de cada pacote. As páginas de uso em produção a montante referenciadas em Veja também documentam a semântica dos cabeçalhos e o comportamento de binding no container dos quais as integrações dependem, junto com suas citações PSR. Esta página de cookbook reapresenta o uso e delega as citações normativas a essas páginas.
Veja também
Seção intitulada “Veja também”- Gerar um PDF em um job enfileirado — mova este trabalho para fora da thread da requisição.
- Uso em produção do Laravel — controller com DI, conjunto de cabeçalhos e o contrato de binding PSR-11.
- Quickstart do Symfony — controller, inline, via stream e o modelo de resposta.
- Quickstart do CodeIgniter —
Services::pdf(), o helperpdf()ePdfResponse. - Escolher uma integração — escolha o pacote de framework certo.