Ir al contenido

Artisan en producción

En producción, conviene inyectar un renderer configurado y un logger PSR-3, reutilizar el proceso de Chrome activo entre renderizados, proporcionar alturas explícitas para documentos con varios elementos y acotar la ruta de renderizado con un tiempo de espera aguas arriba.

BrowserPool mantiene activo un proceso de Chrome (keepAlive: true) y lo reinicia cada 100 renderizados para acotar el crecimiento de memoria, un patrón de acumulación conocido en los clientes CDP de larga duración. Para un worker que renderiza muchos documentos, conviene usar un único renderer de larga duración, no uno por solicitud, de modo que el costo de arranque de Chrome se pague pocas veces.

render-service.php
<?php
declare(strict_types=1);
use NextPDF\Artisan\ChromeHtmlRenderer;
use NextPDF\Artisan\ChromeRendererConfig;
use NextPDF\Artisan\Exception\ChromeNotAvailableException;
use NextPDF\Artisan\Exception\ChromeRenderException;
use Psr\Log\LoggerInterface;
final class ReportRenderer
{
private ChromeHtmlRenderer $renderer;
public function __construct(LoggerInterface $logger)
{
$config = ChromeRendererConfig::fromArray([
'chrome_binary' => getenv('CHROME_BINARY') ?: null,
'render_timeout' => 45,
'max_html_size' => 2_000_000,
'no_sandbox' => (bool) getenv('CHROME_NO_SANDBOX'),
]);
$this->renderer = new ChromeHtmlRenderer($config, $logger);
}
public function render(string $html, float $widthPt, float $heightPt = 0.0): string
{
try {
return $this->renderer->render($html, $widthPt, $heightPt)->getPdfData();
} catch (ChromeNotAvailableException $e) {
// Deployment fault: Chrome runtime missing. Page the on-call owner.
throw $e;
} catch (ChromeRenderException $e) {
// Render-time fault: timeout, crash, empty output. Retryable once.
throw $e;
}
}
public function shutdown(): void
{
$this->renderer->close();
}
}

El renderer se construye una sola vez y se reutiliza. close() se llama al apagar el worker para liberar el proceso de Chrome de forma determinista, en lugar de esperar al destructor. Las dos ramas catch separan un fallo de despliegue (runtime ausente) de un fallo durante el renderizado (reintentar). No se usan bloques catch vacíos.

Conectarlo a un contenedor como singleton:

$container->singleton(ReportRenderer::class, fn ($c) =>
new ReportRenderer($c->get(Psr\Log\LoggerInterface::class)));

Cuando se omite la altura, el puente mide la altura del contenido en Chrome (el max de las alturas de scroll y offset de body/document), la convierte a puntos y añade un búfer de seguridad de ~0.2 pulgadas (~14.4 pt). El búfer absorbe la diferencia entre la disposición en el viewport de Chrome y su recomposición para impresión. Sin él, printToPDF puede desbordarse a una segunda página que PageImporter (solo la página 0) recortaría. Se impone una altura de papel mínima de 0.1 pulgadas. Las pruebas ChromeHtmlRendererTest::renderUsesAutoFitHeightByDefault, ::renderAutoFitBufferIsAddedNotSubtracted y ::renderAppliesMinimumHeightOf0Point1InchForTinyExplicitHeight verifican este comportamiento.

Para documentos de disposición fija (facturas, certificados), pasar una altura explícita en puntos. No se añade ningún búfer cuando la altura es explícita, y la salida coincide exactamente con el tamaño de papel solicitado (verificado por ::renderHonorsExplicitHeightWithoutAutoBuffer).

  • Construir un renderer por worker y reutilizarlo. BrowserPool reutiliza el navegador activo y se reinicia automáticamente en el límite de 100 renderizados.
  • Llamar a close() al apagar el worker y entre lotes grandes si se quiere un proceso de Chrome nuevo antes del límite de 100 renderizados.
  • El destructor llama a close(), pero un close() explícito es determinista y preferible en procesos de larga duración.
  • Los avisos de reinicio se registran en el nivel notice con el recuento de renderizados; configurar alertas ante una tasa de reinicio elevada, que indica documentos más pesados de lo esperado.

Inyectar un logger PSR-3. Eventos emitidos y sus niveles:

EventoNivelContexto
Inicio de renderizadodebugsize, width, height
Renderizado completodebugpdfSize, contentHeight
Lanzamiento del navegadorinfobinary
Reinicio del navegadornoticecount
Cierre del navegadordebugrenderCount

No se registra HTML, bytes de PDF ni texto extraído, en consonancia con la guía de NIST SP 800-92 sobre mantener las cargas útiles fuera de los registros operativos. Construir los SLO de latencia a partir del par start/complete y una alerta de tasa de reinicio a partir de los eventos notice.

  • Chrome como sidecar: ejecutar Chrome en el mismo contenedor que el worker de PHP; fijar chrome_binary. Aprovisionar un contenedor con capacidad de sandbox; consultar /integrations/artisan/chrome-renderer-setup/.
  • Sin contenedor / CLI: Artisan no tiene contenedor de inyección de dependencias. Usar EInvoiceServiceFactory para los contratos de factura electrónica de Premium en los ejecutores de CLI; consultar /integrations/artisan/boot-and-discovery/.
  • Acotación de recursos: combinar render_timeout con un presupuesto de solicitud aguas arriba y un cgroup/ulimit del host. Consultar el modelo de amenazas en /integrations/artisan/security-and-operations/.
  • Un renderer interrumpido a mitad del renderizado igualmente cierra la página de Chrome (finally); el pool permanece utilizable.
  • No se admite reutilizar un único renderer entre threads/processes; cada renderer posee su propio proceso de Chrome.
  • El reinicio cada 100 renderizados es fijo; dimensiona los lotes teniéndolo en cuenta para que los picos de latencia sean predecibles.

El costo en estado estable es la disposición del contenido en Chrome más printToPDF, no la sobrecarga del puente. El costo de arranque se amortiza mediante keepAlive. Esperar un pico de latencia cada 100 renderizados (reinicio del proceso); reflejarlo en los SLO en lugar de tratarlo como un incidente.

Las rutas de producción son el punto al que llega el HTML no confiable. Volver a leer /integrations/artisan/security-and-operations/. Las barreras de red se mantienen con independencia de la configuración, pero no_sandbox: true elimina el aislamiento del proceso de Chrome y aumenta el nivel de confianza requerido para la entrada.

En los workers sin contenedor, EInvoiceServiceFactory devuelve null cuando Premium no está instalado, por lo que la ruta de renderizado de código abierto se ejecuta sin cambios; instalar Pro/Enterprise para habilitar la incrustación y la validación de factura electrónica en el documento renderizado.

  • /integrations/artisan/quickstart/
  • /integrations/artisan/configuration/
  • /integrations/artisan/security-and-operations/
  • /integrations/artisan/chrome-renderer-setup/
  • /integrations/artisan/troubleshooting/