bobv/latex-bundle
Symfony bundle that generates .tex files with object-oriented PHP, compiles them to PDF, returns PDF HTTP responses, and caches results to avoid unnecessary regeneration.
This is a Symfony bundle, not a Laravel package. There is no service provider, no facade, and no config/latex.php published by the package. Your first job is deciding whether to wrap it or reach for barryvdh/laravel-dompdf instead. If you need LaTeX-grade output (math, bibliographies, precise typography), the wrap is worth it.
Step 1 — Runtime prerequisites (do this before touching PHP).
You need a working TeX toolchain on every environment that compiles PDFs:
# Debian/Ubuntu base images
apt-get update && apt-get install -y \
texlive-latex-base texlive-latex-recommended \
texlive-latex-extra texlive-fonts-recommended \
texlive-bibtex-extra
Verify with pdflatex --version inside the same container/user context your queue worker runs as. This is the single most common source of "works on my machine" failures — a pdftoppm-less slim image will compile nothing.
Step 2 — Install the bundle anyway.
composer require bobv/latex-bundle
It will pull Symfony components (symfony/process, symfony/twig-bridge, etc.). Laravel already ships several of these transitively, so check for version conflicts with composer why symfony/process. Note the namespace changed from BobV to Bobv in v3.0.0 — most blog posts and Stack Overflow answers you'll find are stale.
Step 3 — Read the right docs first.
Skip the README (it's thin and still references "Symfony2"). Go straight to src/Resources/doc/documentation.md in the repo, then the UPGRADE.md. The table of contents is the only real map of the API surface.
Step 4 — Build a thin bridge, not an integration.
Do not try to boot the bundle's DI extension. Instantiate the core classes directly in a Laravel service provider:
// app/Providers/LatexServiceProvider.php
public function register(): void
{
$this->app->singleton(LatexGenerator::class, function () {
return new LatexGenerator(
// build the Twig Environment yourself (see Gotchas)
$this->app->make(\Twig\Environment::class),
new ProcessBuilder(), // Symfony\Component\Process
config('latex.paths')
);
});
}
First use case — invoice/receipt PDF. Build a LatexElement tree or a Twig .tex.twig template, render, compile to PDF, return a response()->streamDownload(). That's your 30-minute proof of concept; if it takes longer, the toolchain is the problem, not the PHP.
Pattern: Service-layer wrapper around the generator.
Never let controllers touch LatexGenerator directly. Wrap it so tests can swap in a fake and so you have one place to change when the bundle makes a breaking release (they happen — v3 renamed the root namespace).
final class PdfRenderer
{
public function __construct(private LatexGenerator $generator) {}
public function render(LatexInterface $doc, string $filename): StreamedResponse
{
$pdf = $this->generator->generatePdf($doc); // returns a response/stream per the bundle API
return response()->streamDownload(
fn () => print($pdf->getContent()),
$filename,
['Content-Type' => 'application/pdf']
);
}
}
Pattern: Always queue PDF generation.
LaTeX compilation is 0.5–5 seconds and shells out. Do it in a job, store the file on a disk, then notify or email a signed URL. This also sidesteps PHP-FPM timeouts and gives you natural retry semantics when pdflatex fails on a bad template.
class GenerateInvoicePdf implements ShouldQueue
{
public int $timeout = 120;
public int $tries = 2; // LaTeX rarely succeeds on retry, but transients happen
public function handle(PdfRenderer $renderer): void { /* ... */ }
}
Pattern: Cache on content hash, not document ID.
The bundle has a "basic caching system," but it's keyed the way Symfony's is. In Laravel you're better off hashing the rendered .tex source plus the template mtime and keying your own cache (file/Redis) on that. You get correct invalidation for free when the template or data changes.
Pattern: Two template strategies, pick one per project.
LatexGenerator + LatexElement subclasses for structured documents with reusable blocks (headers, signature blocks, tables). Good when the document is assembled from many optional parts..tex.twig — better when a designer or LaTeX-savvy colleague maintains the template. You render it with real Twig and hand the source to the compiler. Note: this requires wiring a Twig Environment yourself, since Laravel gives you Blade.Twig bridge, if you go that route:
$twig = new \Twig\Environment(
new \Twig\Loader\FilesystemLoader(resource_path('latex'))
);
$twig->addExtension(new \Bobv\LatexBundle\Twig\LatexExtension(/* escaping config */));
The bundle's Twig filters escape LaTeX special characters (\, %, $, &, _, #, {, }) — use them on every interpolated variable. Do not hand-escape.
Pattern: Store the generated .tex alongside the PDF in dev. When a compile fails, the error output from pdflatex is often truncated or unhelpful. Having the exact source that failed is the fastest path to a fix.
vendor/bobv/latex-bundle/src when docs fall short.LogicException: Bundle "BobvLatexBundle" not registered style errors the moment you try to load the bundle's services through Laravel's container. Don't fight it — instantiate classes manually.Process component is the coupling point. The bundle shells out via symfony/process. This usually coexists fine with Laravel (Laravel uses it too), but pin versions and test after Laravel upgrades.pdflatex returns a LatexGeneratorException with a shell exit code, not a clear "not installed" message. Assert the binary on boot: Process::fromShellCommandline('pdflatex --version')->mustRun(); in a health check.shell_exec / proc_open must be enabled. Shared hosts and some hardened PHP images disable these. Check php -i | grep disable_functions before promising a feature.& in an address or _ in a filename will abort the compile with a cryptic LaTeX error. If you use the OOP builder, ensure you're using the escaped-text element (added in v2.5.1) for all user-supplied strings.symfony/string plus escaping.use_symfony_string: true. Since you're not loading the bundle config, wire whatever escaping config the LatexExtension takes directly in your Twig setup — otherwise accented names and em-dashes become ?.BobV to Bobv in v3. Old tutorials are wrong. Follow UPGRADE.md.bibtex (or configured alternative) on the box and a .bib file in the working directory. The v2.5.0 release notes are the only real reference..tex to storage/app/latex-debug/, and run pdflatex -interaction=nonstopmode file.tex by hand. The last 20 lines before the ! are the real error. The bundle's "improve error parsing" change in v5.3.0 helps but does not replace this.LatexElement classes for your reusable blocks (letterhead, footer with page numbers, signature grid) and compose them into documents. That's the intended pattern and survives upgrades.How can I help you explore Laravel packages today?