Zum Inhalt springen

Produktiver Einsatz mit CodeIgniter 4

Controller im Produktivbetrieb sind von konkreten NextPDF-Diensten abhängig. Sie behandeln die dokumentierte Exception-Hierarchie explizit und geben Observability-Signale aus. Lang laufende PDF-Arbeit wird über die CodeIgniter 4 Queue aus dem Request ausgelagert.

CodeIgniter 4 löst die Dienste des Pakets über seinen Locator auf. Beim Service-Locator-Muster wird einem Objekt ein Container übergeben, damit das Objekt seine eigenen Abhängigkeiten daraus ziehen kann. Von diesem Muster wird abgeraten (PSR-11 §1.3, modales SHOULD NOT). Um dieser Empfehlung zu folgen, lösen Sie jeden NextPDF-Dienst einmal an der Controller-Grenze auf und reichen das konkrete Objekt nach innen weiter. Übergeben Sie weder die Services-Klasse noch einen Container an Ihren Domänencode.

Jedes PHP-Beispiel deklariert declare(strict_types=1); in einer eigenen Zeile (PSR-12 §x1.x3.p34).

Produktiv-AspektVerifizierte Oberfläche
Dienste auflösenServices::pdf(false), Services::pdfDocument(false), Services::documentFactory()
Response aufbauenPdfResponse::download() / inline()DownloadResponse
Fehler abfangenNextPDF\Exception\NextPdfException (Basistyp des Ökosystems)
Asynchrone GenerierungGeneratePdfJob registriert in Config\Queue::$jobHandlers
Schutz für Pfad / CallableGeneratePdfJob wirft InvalidArgumentException

Produktiv-Controller — Fehlerbehandlung und Observability

Abschnitt betitelt „Produktiv-Controller — Fehlerbehandlung und Observability“

Die Core-Engine wirft Exceptions, die alle NextPDF\Exception\NextPdfException erweitern. Das Abfangen dieses einen Typs deckt Fehler aus Core und Erweiterungen ab. Der catch-Block in diesem Beispiel protokolliert mit Kontext und gibt eine definierte Fehler-Response zurück; ein leerer catch-Block wird niemals verwendet.

<?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) gibt bei jedem Aufruf eine neue Bibliotheksinstanz und ein neues zugrunde liegendes Dokument zurück. Gleichzeitige Requests teilen sich deshalb niemals den Dokumentzustand. Die funktionalen Tests des Pakets stellen dieses Verhalten sicher.

Die Schriftarten- und Bild-Registries sind absichtlich Singletons mit Prozesslebensdauer. Die Schriftarten-Registry wird einmal vorgewärmt und gesperrt. Die Bild-Registry ist ein begrenzter Least-Recently-Used-Cache (LRU). In einem langlebigen Worker (CodeIgniter spark server, Runner im RoadRunner-Stil oder ein Queue-Worker) ist das genau das gewünschte Verhalten: Die teuren Registries bleiben bestehen, während jedes Dokument neu ist. Fordern Sie in Request- oder Jobcode kein geteiltes Dokument an (Services::pdfDocument(true)); es existiert nur für den Test-Reset und würde Inhalte über Requests hinweg teilen.

GeneratePdfJob führt die PDF-Generierung über codeigniter4/queue außerhalb des Requests aus. Die Queue-Laufzeit erzwingt zwei Vorgaben, die Sie beide korrekt konfigurieren müssen.

1. Registrieren Sie den Job-Handler über seinen Namen

Abschnitt betitelt „1. Registrieren Sie den Job-Handler über seinen Namen“

Die Queue löst einen Job über einen Namensschlüssel auf, nicht über einen Klassenstring. Der Queue-Handler validiert den gepushten Job-Namen gegen die Schlüssel von Config\Queue::$jobHandlers. Er weist einen unbekannten Namen mit CodeIgniter\Queue\Exceptions\QueueException ab. Registrieren Sie den Job in 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,
];
}

Pushen Sie den Job mit dem registrierten Namen als zweitem Argument. Das erste Argument ist der Queue-Name. Das dritte Argument ist das Job-Daten-Array.

<?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]);
}
}

3. Implementieren Sie den Builder unter App\PdfBuilders

Abschnitt betitelt „3. Implementieren Sie den Builder unter App\PdfBuilders“

Der Job beschränkt Builder-Callables auf den Namespace App\PdfBuilders und begrenzt Ausgabepfade auf WRITEPATH/pdfs/. Der Builder ist eine statische Methode. Er erhält ein neues Document und das Kontext-Array und gibt das Dokument zurück.

<?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-Fenster
php spark queue:work pdf-queue

Jeder Joblauf startet über Services::pdfDocument() mit einem neuen Dokument. Er wendet den Builder an und speichert anschließend in den validierten Pfad. Die Tests des Pakets verifizieren, dass zwei aufeinanderfolgende Jobläufe keinen Dokumentzustand teilen.

  • Die Queue weist GeneratePdfJob::class als Job-Namen beim Pushen ab, weil dies nicht der registrierte Schlüssel 'generate-pdf' ist. Pushen Sie immer den jobHandlers-Schlüssel.
  • Der Builder-String muss exakt App\PdfBuilders\<Class>::<method> entsprechen. Funktionen, andere Namespaces oder prefixed/suffixed Payloads lösen InvalidArgumentException aus, bevor Code ausgeführt wird.
  • Der Ausgabepfad muss innerhalb von WRITEPATH/pdfs/ aufgelöst werden und auf .pdf enden (ohne Beachtung der Groß-/Kleinschreibung). Traversal- und Geschwister-Präfix-Pfade werden abgelehnt.
  • codeigniter4/queue ist eine reine Entwicklungsabhängigkeit des Pakets. Nehmen Sie es als Abhängigkeit in die Anwendung auf, die Worker ausführt.

Die Registries werden einmal pro Workerprozess erstellt. Die Kosten für den Dokumentaufbau skalieren mit dem Inhalt, nicht mit dem Adapter. Bevorzugen Sie für große Batch-Jobs den Queue-Pfad, damit Request-Worker reaktionsfähig bleiben. Setzen Sie in jedem Recipe ein performance_budget mit messbarem Ziel.

Der Queue-Job bildet die Oberfläche mit dem höchsten Risiko. Queue-Payloads sind von Angreifern beeinflussbar, wenn der Broker erreichbar ist. Die Callable-Allowlist und die Pfadbegrenzung werden in /integrations/codeigniter/security-and-operations/ zusammen mit den verifizierten Ablehnungsfällen behandelt.

  • Controller erhalten konkrete Dienste, keinen Container, im Einklang mit der Service-Locator-Empfehlung von PSR-11 §1.3.

NextPDF Core ist Apache-2.0. Signierte Ausgabe und PDF/A-Ausgabe in Queue-Jobs setzen voraus, dass NextPDF Pro oder Enterprise in der Worker-Umgebung installiert ist. Das CodeIgniter-Paket stellt die entsprechenden Dienstmethoden bereit. Diese geben null zurück, bis das passende Premium-Paket installiert ist. Siehe </get-license/?intent=codeigniter-async-signing>.

  • /integrations/codeigniter/quickstart/ — die Minimalversion dieser Controller.
  • /integrations/codeigniter/configuration/ — Signierung, TSA und Pfadkonfiguration.
  • /integrations/codeigniter/security-and-operations/ — Bedrohungsmodell und Härtung der Queue.
  • /integrations/codeigniter/troubleshooting/ — Fehlermodi von Queue und Discovery.
  • /integrations/codeigniter/integration/ — Verdrahtungsreferenz und Smoke-Test.