Pular para o conteúdo

Uso em produção com CodeIgniter 4

Os controllers de produção recebem serviços concretos do NextPDF. Eles tratam explicitamente a hierarquia documentada de exceções e emitem sinais de observabilidade. Mova o processamento demorado de Portable Document Format (PDF) para fora do ciclo da requisição usando a Queue do CodeIgniter 4.

O CodeIgniter 4 resolve os serviços do pacote por meio do próprio locator. No padrão service-locator, um objeto recebe um container e o usa para obter suas dependências. A orientação da PHP Standard Recommendation (PSR) desaconselha esse padrão (PSR-11 §1.3, modal SHOULD NOT). Para seguir essa orientação, resolva cada serviço do NextPDF uma única vez na fronteira do controller e, em seguida, passe o objeto concreto para as camadas internas. Não passe a classe Services nem um container para o código de domínio.

Cada exemplo de PHP coloca declare(strict_types=1); em uma linha própria (PSR-12 §x1.x3.p34).

Aspecto de produçãoSuperfície verificada
Resolver serviçosServices::pdf(false), Services::pdfDocument(false), Services::documentFactory()
Construir respostaPdfResponse::download() / inline()DownloadResponse
Capturar falhasNextPDF\Exception\NextPdfException (tipo base do ecossistema)
Geração assíncronaGeneratePdfJob registrado em Config\Queue::$jobHandlers
Validações de path / callableGeneratePdfJob lança InvalidArgumentException

Controller de produção — tratamento de erros e observabilidade

Seção intitulada “Controller de produção — tratamento de erros e observabilidade”

As exceções lançadas pelo motor do core estendem NextPDF\Exception\NextPdfException. Capture esse único tipo para cobrir falhas do core e das extensões. Esse bloco catch registra o contexto em log e retorna uma resposta de erro bem definida, nunca um catch vazio.

<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\DownloadResponse;
use CodeIgniter\HTTP\ResponseInterface;
use NextPDF\CodeIgniter\Config\Services;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final class InvoiceController extends BaseController
{
public function download(int $id): DownloadResponse|ResponseInterface
{
/** @var LoggerInterface $logger */
$logger = \service('logger');
$start = \hrtime(true);
try {
$pdf = Services::pdf(false);
$pdf->document()->addPage();
$pdf->document()->cell(0, 10, "Invoice #{$id}");
$response = $pdf->download("invoice-{$id}.pdf");
$logger->info('pdf.invoice.generated', [
'invoice_id' => $id,
'elapsed_ms' => (\hrtime(true) - $start) / 1_000_000,
]);
return $response;
} catch (NextPdfException $e) {
$logger->error('pdf.invoice.failed', [
'invoice_id' => $id,
'exception' => $e::class,
'message' => $e->getMessage(),
]);
return $this->response
->setStatusCode(ResponseInterface::HTTP_INTERNAL_SERVER_ERROR)
->setJSON(['error' => 'pdf_generation_failed', 'invoice_id' => $id]);
}
}
}

Services::pdf(false) retorna uma nova biblioteca e um novo documento subjacente a cada chamada. Requisições concorrentes nunca compartilham o estado do documento. Os testes funcionais do pacote garantem esse comportamento.

Os registries de fontes e imagens são, por design, singletons com tempo de vida do processo. O registry de fontes é aquecido e travado uma única vez. O registry de imagens é um cache limitado do tipo least recently used (LRU). Em um worker de longa duração (servidor spark do CodeIgniter, runner no estilo RoadRunner ou worker de fila), isso é intencional: registries caros persistem, enquanto cada documento é novo. Não solicite um documento compartilhado (Services::pdfDocument(true)) no código de requisição ou de job; ele existe apenas para reset de teste e compartilharia conteúdo entre requisições.

GeneratePdfJob executa a geração de PDF fora da requisição por meio de codeigniter4/queue. O runtime da fila exige duas configurações. Ajuste ambas corretamente.

A fila resolve um job por uma chave de nome, não por uma string de classe. O handler da fila valida o nome do job enviado em relação às chaves de Config\Queue::$jobHandlers. Ele rejeita nomes desconhecidos com CodeIgniter\Queue\Exceptions\QueueException. Registre o job em app/Config/Queue.php:

<?php
declare(strict_types=1);
namespace Config;
use CodeIgniter\Queue\Config\Queue as BaseQueue;
use NextPDF\CodeIgniter\Jobs\GeneratePdfJob;
final class Queue extends BaseQueue
{
/** @var array<string, class-string> */
public array $jobHandlers = [
'generate-pdf' => GeneratePdfJob::class,
];
}

Envie o job usando o nome registrado como segundo argumento. O primeiro argumento é o nome da fila. O terceiro argumento é o array de dados do job.

<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\ResponseInterface;
final class InvoiceController extends BaseController
{
public function queueInvoice(int $id): ResponseInterface
{
\service('queue')->push('pdf-queue', 'generate-pdf', [
'builder' => 'App\\PdfBuilders\\InvoiceBuilder::build',
'outputPath' => WRITEPATH . 'pdfs/invoice-' . $id . '.pdf',
'context' => ['invoice_id' => $id],
]);
return $this->response
->setStatusCode(ResponseInterface::HTTP_ACCEPTED)
->setJSON(['status' => 'queued', 'invoice_id' => $id]);
}
}

O job permite callables de builder apenas no namespace App\PdfBuilders e restringe os caminhos de saída a WRITEPATH/pdfs/. Implemente o builder como um método estático. Ele recebe um novo Document e o array de contexto e, em seguida, retorna o documento.

<?php
declare(strict_types=1);
namespace App\PdfBuilders;
use NextPDF\Core\Document;
final class InvoiceBuilder
{
/** @param array<string, mixed> $context */
public static function build(Document $document, array $context): Document
{
$invoiceId = (int) ($context['invoice_id'] ?? 0);
$document->addPage();
$document->cell(0, 10, "Invoice #{$invoiceId}");
return $document;
}
}
Terminal window
php spark queue:work pdf-queue

Cada execução de job começa com um novo documento de Services::pdfDocument(). Ele aplica o builder e, em seguida, salva no caminho validado. Os testes do pacote verificam que duas execuções consecutivas de job não compartilham o estado do documento.

  • A fila rejeita GeneratePdfJob::class como nome do job no momento do push porque ele não é a chave registrada 'generate-pdf'. Sempre envie a chave de jobHandlers.
  • A string do builder deve corresponder exatamente a App\PdfBuilders\<Class>::<method>. Funções, outros namespaces ou payloads com prefixo ou sufixo geram InvalidArgumentException antes da execução de qualquer código.
  • O caminho de saída deve ser resolvido dentro de WRITEPATH/pdfs/ e terminar em .pdf (sem distinção entre maiúsculas e minúsculas). Caminhos com traversal e com prefixo de diretório irmão são rejeitados.
  • codeigniter4/queue é uma dependência apenas de desenvolvimento do pacote. Inclua-a como dependência na aplicação que executa workers.

Os registries são criados uma única vez por processo de worker. O custo de construção do documento escala com o conteúdo, não com o adaptador. Para jobs em lote grandes, use o caminho da fila para manter os workers de requisição responsivos. Defina um performance_budget em qualquer receita que tenha uma meta mensurável.

O job de fila é a superfície de maior risco. Quando o broker está acessível, os payloads da fila podem ser influenciados por um atacante. A allowlist de callables e o confinamento de caminhos são tratados em /integrations/codeigniter/security-and-operations/ com os casos de rejeição verificados.

  • Os controllers recebem serviços concretos, não um container, em conformidade com a orientação sobre service-locator da PSR-11 §1.3.

O core do NextPDF é Apache-2.0. Para produzir saída assinada e PDF/A em jobs de fila, instale o NextPDF Pro ou Enterprise no ambiente do worker. O pacote do CodeIgniter expõe os métodos de serviço correspondentes. Eles retornam null até que o respectivo pacote Premium seja instalado. Consulte </get-license/?intent=codeigniter-async-signing>.

  • /integrations/codeigniter/quickstart/ — versão mínima desses controllers.
  • /integrations/codeigniter/configuration/ — assinatura, Time Stamping Authority (TSA) e configuração de caminhos.
  • /integrations/codeigniter/security-and-operations/ — modelo de ameaças da fila e hardening.
  • /integrations/codeigniter/troubleshooting/ — modos de falha de fila e de discovery.
  • /integrations/codeigniter/integration/ — referência de wiring e smoke test.