Ir al contenido

Uso en producción del paquete de NextPDF para Laravel

En producción, resolver el contrato del documento mediante inyección por constructor. Gestionar los fallos de escritura de PDF con una excepción específica. Mover la generación pesada o por lotes a GeneratePdfJob y conectar retrollamadas explícitas de éxito y de fallo.

Ventana de terminal
composer require nextpdf/laravel
php artisan vendor:publish --tag=nextpdf-config

Configurar la conexión de cola en config/nextpdf.php. Definir queue.connection, queue.queue y queue.timeout. A continuación, asegurarse de que haya un worker en ejecución para la conexión configurada.

El contenedor expone NextPDF\Contracts\PdfDocumentInterface como un enlazado de fábrica. Cada resolución produce un nuevo NextPDF\Core\Document. PSR-11 permite que un contenedor devuelva valores distintos en llamadas sucesivas a get(), según la estrategia de enlazado (PSR-11 §1.1.2). Aquí, el paquete usa un enlazado de fábrica para que el estado mutable con alcance de petición nunca se comparta entre peticiones. Los registros de fuentes y de imágenes son singletons. Esto mantiene el contrato de que un identificador enlazado se resuelve a su entrada registrada (PSR-11 §1.1.2), al tiempo que comparte los recursos costosos en todo el worker.

Preferir la inyección por constructor frente a la facade en el código de producción. Esto hace explícita la dependencia y mantiene el controlador apto para pruebas unitarias sin arrancar la raíz de la facade.

Controlador con inyección de dependencias y manejo tipado de errores

Sección titulada «Controlador con inyección de dependencias y manejo tipado de errores»
resource: NextPDF\Contracts\PdfDocumentInterface + src/Laravel/Http/PdfResponse.php
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;
use NextPDF\Contracts\PdfDocumentInterface;
use NextPDF\Laravel\Http\PdfResponse;
use Psr\Log\LoggerInterface;
use Throwable;
final class InvoiceController extends Controller
{
public function __construct(
private readonly PdfDocumentInterface $document,
private readonly LoggerInterface $logger,
) {}
public function show(int $invoiceId): Response
{
try {
$this->document->addPage();
$this->document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true);
$this->document->cell(0, 10, 'Thank you for your business.');
return PdfResponse::download(
$this->document,
"invoice-{$invoiceId}.pdf",
);
} catch (Throwable $exception) {
// Rethrow as an HTTP-meaningful failure; never swallow.
$this->logger->error('Invoice PDF generation failed', [
'invoice_id' => $invoiceId,
'exception' => $exception::class,
]);
return new Response('Could not generate the invoice PDF.', 500);
}
}
}

Inyectar PdfDocumentInterface, no el Document concreto, para que el enlazado siga siendo intercambiable en las pruebas. El contenedor devuelve un documento nuevo en cada instanciación del controlador. No reutilizar la misma instancia de controlador para dos documentos sin relación dentro de un mismo proceso.

El bloque catch registra la clase de la excepción y devuelve un error HTTP definido en lugar de filtrar una traza de pila. Usar Psr\Log\LoggerInterface, que el contenedor resuelve como el logger del framework. PSR-3 deja el escape de marcadores de posición en manos de quien implementa e indica a quienes llaman que no escapen previamente los valores de contexto (PSR-3 §1.2). Pasar contexto estructurado, no cadenas interpoladas.

Generación encolada con retrollamadas de éxito y de fallo

Sección titulada «Generación encolada con retrollamadas de éxito y de fallo»

GeneratePdfJob es un trabajo ShouldQueue. De forma predeterminada, usa tres intentos, un tiempo de espera de 120 segundos y un retroceso de 10 segundos. Es posible anular los tres mediante config/nextpdf.php. El closure constructor recibe el documento resuelto por el contenedor y debe devolver un documento configurado.

resource: src/Laravel/Jobs/GeneratePdfJob.php
<?php
declare(strict_types=1);
namespace App\Jobs;
use NextPDF\Contracts\PdfDocumentInterface;
use NextPDF\Laravel\Jobs\GeneratePdfJob;
use Psr\Log\LoggerInterface;
use Throwable;
final class DispatchMonthlyStatement
{
public function __construct(private readonly LoggerInterface $logger) {}
public function __invoke(int $accountId): void
{
// Dispatchable::dispatch() is `public static`: it constructs the
// job from the arguments it receives and returns a PendingDispatch.
// Pass every constructor argument — including the callbacks — to
// the static call. Building an instance and then calling
// `$job->dispatch(...)` would discard that instance (and its
// callbacks) and queue a different job from only the static args.
GeneratePdfJob::dispatch(
storage_path("app/statements/{$accountId}.pdf"),
static fn (PdfDocumentInterface $document): PdfDocumentInterface => $document
->addPage()
->cell(0, 10, "Statement for account {$accountId}", newLine: true),
function (string $path) use ($accountId): void {
$this->logger->info('Statement PDF written', [
'account_id' => $accountId,
'path' => $path,
]);
},
function (Throwable $exception) use ($accountId): void {
$this->logger->error('Statement PDF failed', [
'account_id' => $accountId,
'exception' => $exception::class,
]);
},
);
}
}

GeneratePdfJob::dispatch() reenvía sus argumentos directamente al constructor (string $outputPath, callable $builder, ?callable $onSuccess, ?callable $onFailure). Así, las retrollamadas de éxito y de fallo quedan conectadas al mismo trabajo encolado. Esto coincide con la forma posicional GeneratePdfJob::dispatch($path, $builder) de /integrations/laravel/quickstart/. La retrollamada de éxito recibe la ruta de salida y la retrollamada de fallo recibe el Throwable. El trabajo también expone setters fluidos then() y catch(), que devuelven el trabajo para permitir el encadenamiento. Usar esos setters solo cuando se conserve y se despache esa misma instancia, por ejemplo mediante el helper dispatch(). El trabajo también expone un método failed(), que el ejecutor de cola invoca ante un fallo terminal. Las retrollamadas se envuelven en closures serializables para que sobrevivan al transporte de la cola.

PropiedadPredeterminadoClave de configuración
tries3no controlado por configuración; crear una subclase para cambiarlo
timeout120nextpdf.queue.timeout
backoff10no controlado por configuración; crear una subclase para cambiarlo
nombre de la colapdfnextpdf.queue.queue
conexiónpredeterminadanextpdf.queue.connection

tries y backoff son propiedades públicas que se leen desde la instancia del trabajo. El trabajo proporcionado no las controla desde la configuración. Para anularlas cuando la política de reintentos sea distinta, se debe crear una subclase de GeneratePdfJob.

  • El closure constructor debe devolver un PdfDocumentInterface. El trabajo guarda ese valor de retorno, no la instancia resuelta originalmente. La prueba del trabajo verifica explícitamente este contrato.
  • Resolver SignerInterface devuelve null a menos que la firma esté habilitada, haya un certificado configurado y nextpdf/premium esté instalado. Comprobar siempre si es null antes de firmar.
  • Los workers de larga duración (Octane/RoadRunner/Swoole) comparten el registro de fuentes bloqueado. Configurar preload_fonts para que el calentamiento ocurra una sola vez al arrancar el worker y no en la primera petición.
  • Un trabajo fallido invoca failed() tras agotar tries. El fallo de un intento individual no llama a onFailure hasta que el ejecutor de cola declara un fallo terminal.

La generación síncrona en el controlador bloquea la petición durante toda la construcción del PDF. Para salidas de varias páginas o por lotes, despachar GeneratePdfJob y responder de inmediato. Los registros singleton amortizan el análisis de fuentes y la decodificación de imágenes a lo largo de la vida del worker. Así, el coste por petición se limita a la construcción del documento y a la emisión del contenido.

El controlador con inyección de dependencias registra la clase de la excepción, no su mensaje ni su traza, para evitar exponer detalles internos en los logs. GeneratePdfJob valida la ruta de salida en el worker para mitigar cargas serializadas manipuladas durante el transporte de la cola. La cobertura completa se encuentra en /integrations/laravel/security-and-operations/.

AfirmaciónFuenteCláusulareference_id
El identificador enlazado se resuelve a su entrada registradaContenedor PSR-11§1.1.2
Las resoluciones sucesivas pueden diferir según la estrategia de enlazado (enlazado de fábrica)Contenedor PSR-11§1.1.2

La guía de logging de PSR-3 está documentada en la especificación PSR-3. Allí se establece que el escape de marcadores de posición es responsabilidad de quien implementa y que quienes llaman pasan contexto estructurado. Consultar el documento psr_3_logger §1.2.

La salida firmada PAdES B-B y el archivado PDF/A mediante nextpdf/premium usan la misma superficie de inyección de dependencias. Se trata de una capacidad opcional de Enterprise. El paquete Core que se documenta aquí no necesita cambios de código para adoptarla. Consultar https://nextpdf.dev/get-license/?intent=laravel-signing.

  • /integrations/laravel/quickstart/ — primer ejemplo mínimo
  • /integrations/laravel/configuration/ — claves de cola, de firma y de fuentes
  • /integrations/laravel/security-and-operations/ — modelo de amenazas y endurecimiento
  • /integrations/laravel/troubleshooting/ — fallos comunes en producción