Ir al contenido

Ejecutar compat-legacy en producción

El adaptador se puede ejecutar de forma segura en manejadores HTTP, procesos de cola y procesos de larga duración. Es más seguro que el TCPDF heredado 6.2.13 porque elimina los dos riesgos de producción más habituales: la salida directa al búfer y die() ante un error. Esta página explica cómo usarlo correctamente en producción.

Requisito previo: completar la auditoría de modo estricto en /integrations/tcpdf-compat/migration/ y desplegar con el modo estricto desactivado.

Gestión de la salida en procesos y manejadores

Sección titulada «Gestión de la salida en procesos y manejadores»

El método Output() del TCPDF heredado escribe directamente en el búfer de salida activo. Eso corrompe las respuestas en los frameworks HTTP y rompe los procesos de cola. En cambio, el adaptador redirige la salida a través de un puente de destino seguro.

Usar el destino que corresponda al llamador:

ContextoDestinoPor qué
Proceso de cola que escribe en el almacenamientoOutput($path, 'F')Escribe el archivo y devuelve una cadena vacía, sin interactuar con el búfer.
Generar y luego adjuntar/subirOutput($name, 'S')Devuelve los bytes del PDF; la aplicación controla su destino.
Adjunto de correo electrónicoOutput($name, 'E')Devuelve un cuerpo MIME en base64 con Content-Type: application/pdf.
Respuesta HTTP controlada por la aplicaciónOutput($name, 'S')Obtener los bytes y después definir sus propias cabeceras y cuerpo.
examples/production-worker.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\Exception\TcpdfNotImplementedException;
use NextPDF\Compat\Tcpdf\TCPDF;
/**
* Render an invoice in a queue worker. Returns the storage path.
*
* @throws \RuntimeException on a render failure (Error() throws, not die()).
*/
function renderInvoiceJob(array $invoice, string $storageDir): string
{
$pdf = new TCPDF('P', 'mm', 'A4');
$pdf->SetFont('helvetica', '', 12);
$pdf->AddPage();
$pdf->Cell(0, 10, 'Invoice ' . $invoice['number'], 0, 1);
$path = $storageDir . '/invoice-' . $invoice['number'] . '.pdf';
try {
$pdf->Output($path, 'F'); // writes file, no buffer pollution
} catch (TcpdfNotImplementedException $e) {
// Only reachable if strict mode is on — it must NOT be in production.
throw new \RuntimeException('Adapter strict-mode gap in production: ' . $e->getMessage(), 0, $e);
} catch (\RuntimeException $e) {
// Error() throws RuntimeException instead of die().
throw new \RuntimeException('PDF render failed: ' . $e->getMessage(), 0, $e);
}
return $path;
}

En un manejador HTTP, conviene preferir 'S' y definir las cabeceras directamente:

examples/production-http.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\TCPDF;
$pdf = new TCPDF();
$pdf->AddPage();
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 10, 'Report');
$bytes = $pdf->Output('report.pdf', 'S');
header('Content-Type: application/pdf');
header('Content-Length: ' . strlen($bytes));
header('Content-Disposition: inline; filename="report.pdf"');
echo $bytes;

Error() lanza RuntimeException; nunca llama a die(). Es el cambio operativo más importante respecto al TCPDF heredado.

  • Envolver cada punto de entrada de renderizado en try/catch.
  • Asignar la excepción al contrato de errores de la aplicación (HTTP 5xx, trabajo fallido, reintento, dead-letter).
  • No suponer que el proceso termina ante un fallo de renderizado — no lo hace.

Si se ejecuta un trabajo de CI periódico en modo estricto (recomendado), una TcpdfNotImplementedException allí es un hallazgo real: una ruta de código depende de un parámetro de TCPDF no admitido. Debe tratarse como una tarea de migración, no como una prueba inestable.

  • Los bytes del PDF se construyen de forma diferida en la primera llamada de salida. Close() es opcional; llamarlo almacena los bytes en caché. Open() es una operación nula segura.
  • endPage() no hace nada — NextPDF gestiona el ciclo de vida de las páginas. Conviene quitarlo de los bucles intensivos; no aporta ningún valor.
  • Dejar que PHP recolecte la basura del adaptador entre trabajos. _destroy() restablece los datos en caché del adaptador, pero no es necesario llamarlo explícitamente en los bucles de procesos habituales.
  • Construir un adaptador nuevo por documento. No reutilice una misma instancia del adaptador entre documentos no relacionados en un proceso de larga duración; el estado del documento es por instancia.
  • El adaptador es una capa de delegación ligera; el coste está dominado por el motor, no por el adaptador.
  • Definir las constantes heredadas una sola vez en el arranque. LegacyDefaults::register() y LegacyBootstrap::enableAliases() son idempotentes y están protegidos, por lo que las llamadas repetidas son económicas. Definir las constantes en cada solicitud es trabajo desperdiciado.
  • Preferir Output(..., 'S') o 'F' en lugar de 'I'/'D' en contextos sin navegador. Las rutas inline/download producen una salida independiente del framework que normalmente no se desea en un proceso.
  • Para la generación de gran volumen, perfilar el motor, no el adaptador. El presupuesto de sobrecarga propia del adaptador por página es pequeño en comparación con el renderizado.
  • Cada instancia del adaptador es independiente y mantiene su propio estado de documento. La concurrencia a nivel de proceso o de worker es segura siempre que cada unidad de trabajo use su propia instancia del adaptador.
  • Las protecciones de idempotencia de LegacyBootstrap y LegacyDefaults usan estado estático local al proceso; son seguras bajo los modelos típicos de PHP per-request/per-worker. No están diseñadas para compartir estado mutable entre hilos.
  • Auditoría de modo estricto completada; el entorno de producción se ejecuta con el modo estricto desactivado.
  • Todos los puntos de entrada de renderizado están envueltos en try/catch para RuntimeException (sin depender de die()).
  • Los workers usan Output(..., 'F') o 'S', nunca la ruta inline.
  • Constantes heredadas definidas una sola vez en el arranque, antes de la primera construcción.
  • Existe un trabajo de CI periódico en modo estricto para detectar regresiones.
  • Aserciones de prueba a nivel de byte recalibradas (consulte /integrations/tcpdf-compat/migration/).
  • /integrations/tcpdf-compat/security-and-operations/ — cifrado, postura de firma, endurecimiento
  • /integrations/tcpdf-compat/troubleshooting/ — patrones de fallo en producción y soluciones
  • /integrations/tcpdf-compat/configuration/ — modo estricto e higiene de constantes
  • /integrations/tcpdf-compat/migration/ — auditoría que debe preceder a la producción