Générer des factures PDF professionnelles avec Laravel et dompdf — et un petit outil de debug qui m’a sauvé la mise
Quand j’ai commencé à ajouter la génération de factures à mon SaaS, je pensais que ça me prendrait un après-midi. En réalité, ça m’a pris beaucoup plus de temps.
La génération de PDF en PHP a toujours été un peu pénible. Mais après avoir apprivoisé les bizarreries de dompdf dans un projet Laravel, j’ai trouvé une configuration qui fonctionne de manière fiable — et au passage j’ai créé un petit outil de debug que j’utilise maintenant sur absolument tous les templates PDF que j’écris.
Dans cet article, je montre comment je l’ai mis en place, ce qui m’a posé problème, et le composant Blade qui rend le placement des éléments dans dompdf beaucoup moins frustrant.
Pourquoi dompdf
Il existe plusieurs options pour générer des PDF avec Laravel : dompdf, TCPDF, mPDF, et plus récemment Chrome sans interface via des outils comme Browsershot. J’ai choisi dompdf parce que :
- Il s’installe simplement via Composer (
barryvdh/laravel-dompdf) - Il rend du HTML et du CSS standard — pas de syntaxe propriétaire
- Il gère correctement les exigences d’une facture en français (UTF‑8, symbole euro, accents)
- Il est suffisamment rapide pour une génération “à la demande” dans une requête HTTP
Pour un SaaS qui génère les factures une par une à la demande de l’utilisateur, c’est le bon compromis entre simplicité et qualité de rendu.
Mise en place de base
composer require barryvdh/laravel-dompdf
Dans votre contrôleur :
use Barryvdh\\DomPDF\\Facade\\Pdf;
public function download(Invoice $invoice)
{
$pdf = Pdf::loadView('pdf.invoice', [
'invoice' => $invoice,
]);
return $pdf->download("facture-{$invoice->number}.pdf");
}
Le template Blade peut se trouver dans resources/views/pdf/invoice.blade.php et reste du HTML classique — aucune syntaxe spéciale n’est nécessaire.
Structure du template de facture
Ce qu’il faut bien comprendre avec dompdf, c’est qu’il ne prend pas en charge tout CSS. Flexbox et Grid ne sont pas disponibles. Vous écrivez du HTML comme en 2005 : tableaux pour la mise en page, positionnement absolu pour les éléments à placer précisément, et styles inline partout.
Voici le “shell” de base que j’utilise :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: DejaVu Sans, sans-serif;
}
body {
font-size: 11px;
color: #1a1a2e;
}
.page {
position: relative;
width: 794px;
padding: 40px 40px 40px 60px;
}
</style>
</head>
<body>
<div class="page">
<x-pdf-ruler />
{{-- En-tête : infos société + numéro de facture --}}
{{-- Bloc client --}}
{{-- Tableau des lignes --}}
{{-- Totaux --}}
{{-- Pied de page légal --}}
</div>
</body>
</html>
Quelques points importants :
Utilisez DejaVu Sans comme police. Elle est fournie avec dompdf et gère bien les accents en français, le symbole euro et la plupart des caractères spéciaux, sans avoir besoin d’intégrer une police personnalisée. Si vous voulez vraiment une autre police, c’est possible, mais cela ajoute de la complexité et alourdit le fichier.
Définissez une largeur explicite sur votre élément racine. J’utilise 794px, ce qui correspond à un A4 en 96 dpi. Sans ça, dompdf peut avoir des comportements imprévisibles avec les largeurs en pourcentage.
Appliquez position: relative sur le conteneur de page. C’est nécessaire pour tous les éléments en position absolue — y compris la règle de debug.
La partie dont personne ne parle : le placement au pixel près
Une fois que la mise en page de base fonctionne, c’est là que la vraie frustration commence. Vous devez placer certains éléments à des positions très précises — un logo en haut à droite, un tampon “PAYÉ” en diagonale, un pied de page collé en bas de la page. Et dompdf ne rend pas exactement comme un navigateur.
L’approche standard, c’est : générer le PDF, le regarder, deviner que quelque chose est à ~120 px du haut, changer la valeur, regénérer, revérifier. Pour un template complexe, cette boucle peut se répéter 20 à 30 fois.
Du coup j’ai créé une règle de debug.
Le composant Blade “règle de debug”
L’idée est simple : une fine bande jaune collée au bord gauche de la page, avec des valeurs en pixels tous les 10 px. Vous l’insérez dans le template pendant que vous travaillez, vous l’utilisez pour lire les positions exactes, puis elle disparaît automatiquement en production.
La classe du composant — app/View/Components/PdfRuler.php :
<?php
namespace App\\View\\Components;
use Illuminate\\View\\Component;
class PdfRuler extends Component
{
public function __construct(
public int $max = 297,
public int $step = 10,
public int $width = 30,
public string $side = 'left',
) {}
public function render()
{
return view('components.pdf-ruler');
}
public function shouldRender(): bool
{
return config('app.debug', false);
}
}
Le template Blade — resources/views/components/pdf-ruler.blade.php :
<div style="
position: absolute;
{{ $side === 'right' ? 'right: 0;' : 'left: 0;' }}
top: 0;
bottom: 0;
width: {{ $width }}px;
background: #ffeb3b;
font-size: 7px;
font-family: monospace;
color: #000;
line-height: 1;
padding: 0 2px;
z-index: 9999;
box-sizing: border-box;
">
@for ($i = 0; $i <= $max; $i += $step)
<div style="
{{ $i === 0 ? '' : "margin-top: {$step}px;" }}
border-top: 1px solid rgba(0,0,0,0.2);
padding-top: 1px;
">{{ $i }}</div>
@endfor
</div>
Utilisation dans votre template PDF :
<div class="page">
{{-- Visible uniquement quand APP_DEBUG=true --}}
<x-pdf-ruler />
{{-- Ou sur le côté droit --}}
<x-pdf-ruler side="right" />
{{-- Les deux côtés en même temps --}}
<x-pdf-ruler side="left" />
<x-pdf-ruler side="right" />
{{-- Résolution plus fine --}}
<x-pdf-ruler :step="5" />
... contenu de votre facture ...
</div>
La méthode shouldRender() est la partie essentielle. Quand APP_DEBUG=false — ce qui est toujours le cas en production — Laravel ignore complètement le composant. Aucun HTML n’est généré, aucun traitement n’est fait. Il n’y a donc aucun risque de laisser la règle apparaître par erreur dans un PDF client.
Quelques repères A4 utiles à garder en tête
Quand vous utilisez la règle, ces valeurs de référence sont pratiques :
| Référence | Valeur en pixels (96 dpi) |
|---|---|
| Hauteur d’une page A4 | 1123 px |
| Largeur d’une page A4 | 794 px |
| Marge haute typique | 40 px |
| Marge basse typique | 40 px |
| Hauteur utile | ~1043 px |
Je mets :max="1123" quand j’ai besoin de vérifier les positions jusqu’en bas de la page.
Quelques autres leçons apprises avec dompdf
Les tableaux sont vos amis. Même si ça donne l’impression de revenir en arrière, <table> reste l’outil de mise en page le plus fiable avec dompdf. Les lignes de facture, le bloc des totaux, l’en‑tête avec les infos de la société à gauche et le numéro de facture à droite — tout est en tableaux.
Les styles inline sont plus fiables que les feuilles de style. Le support de la cascade CSS dans dompdf a quelques bizarreries. Quand quelque chose ne s’affiche pas comme prévu, déplacer le style en inline règle souvent le problème immédiatement.
Les images gagnent à être encodées en base64. Pour le logo par exemple, convertissez‑le en data URI base64. Dompdf peut avoir du mal avec les chemins de fichiers, surtout dans des environnements Docker.
$logo = base64_encode(file_get_contents(public_path('images/logo.png')));
$logoSrc = 'data:image/png;base64,' . $logo;
Les sauts de page ont besoin de CSS explicite. Si votre facture peut s’étendre sur plusieurs pages, ajoutez page-break-inside: avoid sur les lignes du tableau pour que celles‑ci ne soient jamais coupées entre deux pages.
tr { page-break-inside: avoid; }
Le résultat
La règle de debug a l’air d’un petit détail, mais elle m’a vraiment aidé dans mon travail sur les templates PDF. Au lieu de boucles “générer–deviner–ajuster” interminables, j’ouvre le PDF une fois, je lis les valeurs dont j’ai besoin, et je mets les bons nombres du premier coup.
Le fait d’en faire un composant le rend réutilisable dans tous les templates PDF du projet — devis, bons de livraison, reçus — avec une seule ligne, et sans risque qu’il apparaisse en production.
Si vous ajoutez la génération de factures à votre SaaS Laravel et que vous voulez vous éviter quelques heures de frustration, j’espère que cet exemple vous sera utile. N’hésitez pas à copier le code du composant — c’est typiquement le genre d’outil qui devrait exister dans chaque projet Laravel utilisant dompdf.
Je construis des outils SaaS et j’écris sur les choix techniques qui se cachent derrière. Si cet article vous a été utile, la version LinkedIn est un peu plus courte et peut valoir le coup d’être partagée dans votre réseau.
