Aller au contenu

Artisan en production

En production, injecte un renderer configuré et un logger PSR-3, réutilise le processus Chrome actif d’un rendu à l’autre, fournis des hauteurs explicites pour les documents à plusieurs éléments et encadre le chemin de rendu avec un timeout en amont.

BrowserPool maintient un seul processus Chrome actif (keepAlive: true) et le redémarre tous les 100 rendus pour borner la croissance mémoire — un comportement d’accumulation connu chez les clients CDP à longue durée de vie. Pour un worker qui rend de nombreux documents, tu veux un seul renderer de longue durée, plutôt qu’un par requête, afin de ne payer que rarement le coût de démarrage de Chrome.

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();
}
}

Le renderer est construit une seule fois, puis réutilisé. close() est appelé à l’arrêt du worker pour libérer le processus Chrome de façon déterministe, plutôt que d’attendre le destructeur. Les deux branches catch séparent une faute de déploiement (runtime manquant) d’une faute au moment du rendu (réessayable). Aucun bloc catch vide n’est utilisé.

Câble-le dans un conteneur comme singleton :

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

Quand la hauteur est omise, le pont mesure dans Chrome la hauteur du contenu (max des hauteurs scroll et offset de body/document), la convertit en points et ajoute une marge de sécurité d’environ 0,2 pouce (~14,4 pt). La marge absorbe la différence entre la mise en page du viewport de Chrome et son reflow en mode impression. Sans elle, printToPDF risque de déborder sur une deuxième page que PageImporter (page 0 uniquement) couperait. Une hauteur de papier minimale de 0,1 pouce est appliquée. Les tests ChromeHtmlRendererTest::renderUsesAutoFitHeightByDefault, ::renderAutoFitBufferIsAddedNotSubtracted et ::renderAppliesMinimumHeightOf0Point1InchForTinyExplicitHeight vérifient cela.

Pour les documents à mise en page fixe (factures, certificats), passe une hauteur explicite en points. Aucune marge n’est ajoutée quand la hauteur est explicite, et la sortie correspond exactement à la taille de papier demandée (vérifié par ::renderHonorsExplicitHeightWithoutAutoBuffer).

  • Construis un seul renderer par worker ; réutilise-le. BrowserPool réutilise le navigateur actif et redémarre automatiquement à la limite des 100 rendus.
  • Appelle close() à l’arrêt du worker et entre deux gros lots si tu veux obtenir un processus Chrome neuf avant la limite des 100 rendus.
  • Le destructeur appelle close(), mais un close() explicite est déterministe et préférable dans les processus à longue durée d’exécution.
  • Les notifications de redémarrage sont journalisées au niveau notice avec le nombre de rendus — déclenche une alerte sur un taux de redémarrage élevé, qui signale des documents plus lourds que prévu.

Injecte un logger PSR-3. Événements émis et niveaux associés :

ÉvénementNiveauContexte
Début du rendudebugsize, width, height
Rendu terminédebugpdfSize, contentHeight
Lancement du navigateurinfobinary
Redémarrage du navigateurnoticecount
Fermeture du navigateurdebugrenderCount

Aucun HTML, octet PDF ni texte extrait n’est journalisé, conformément aux recommandations NIST SP 800-92 qui visent à maintenir les charges utiles hors des logs opérationnels. Construis des SLO de latence à partir de la paire start/complete et une alerte de taux de redémarrage à partir des événements notice.

  • Chrome en sidecar : exécute Chrome dans le même conteneur que le worker PHP ; épingle chrome_binary. Provisionne un conteneur compatible avec le sandbox — voir /integrations/artisan/chrome-renderer-setup/.
  • Sans conteneur / CLI : Artisan n’a pas de conteneur d’injection de dépendances. Utilise EInvoiceServiceFactory pour les contrats de facture électronique Premium dans les runners CLI ; voir /integrations/artisan/boot-and-discovery/.
  • Bornage des ressources : associe render_timeout à un budget de requête en amont et à un cgroup/ulimit hôte. Consulte le modèle de menace sur /integrations/artisan/security-and-operations/.
  • Un renderer interrompu en plein rendu ferme tout de même la page Chrome (finally) ; le pool reste utilisable.
  • Réutiliser un même renderer entre threads/processes n’est pas pris en charge ; un renderer possède un seul processus Chrome.
  • Le redémarrage tous les 100 rendus est fixe ; dimensionne tes lots en conséquence pour des pics de latence prévisibles.

En régime stable, le coût vient de la mise en page Chrome de l’entrée plus printToPDF, pas du surcoût du pont. Le coût de démarrage est amorti par keepAlive. Attends-toi à un pic de latence à chaque centième rendu (redémarrage du processus) — fais-le apparaître dans tes SLO plutôt que de le traiter comme un incident.

C’est par les chemins de production qu’arrive le HTML non fiable. Relis /integrations/artisan/security-and-operations/. Les barrières réseau restent en place quelle que soit la configuration, mais no_sandbox: true supprime l’isolation du processus Chrome et renforce l’exigence de confiance envers l’entrée.

Dans les workers sans conteneur, EInvoiceServiceFactory renvoie null quand Premium n’est pas installé, donc le chemin de rendu open-source continue de s’exécuter sans changement ; installe Pro/Enterprise pour activer l’intégration et la validation de facture électronique sur le document rendu.

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