uga/twig2pdf-bundle
uga/twig2pdf-bundle is a Symfony bundle that bridges Twig templates and PDF generation, letting applications render Twig/HTML views into PDF documents or responses. It simplifies producing invoices, reports, and other downloadable PDFs from familiar Twig templates.
This is not a Laravel package. uga/twig2pdf-bundle is a Symfony 2.8 LTS bundle that registers a Twig2Pdf service in the Symfony DI container and wraps the spipu/html2pdf (TCPDF-based) renderer. It has no service provider, no facade, no publishable config, and its composer.json requires Symfony 2.8 components — so composer require uga/twig2pdf-bundle into a modern Laravel app will almost certainly fail dependency resolution against the symfony/* 6.x/7.x packages Laravel already ships.
Treat it as a reference implementation, not a dependency. The minimal path:
Resources/config/services.yml and the Twig2Pdf class first — that is the entire package. You are looking for two things: how it renders a Twig template to an HTML string, and how it hands that string to Html2Pdf and builds a Response.If you insist on the same underlying engine, require it directly:
composer require spipu/html2pdf
// app/Http/Controllers/InvoicePdfController.php
public function __invoke(Invoice $invoice)
{
$html = view('pdf.invoice', ['invoice' => $invoice])->render();
$pdf = new \Spipu\Html2Pdf\Html2Pdf('P', 'A4', 'en');
$pdf->writeHTML($html);
return response($pdf->output('invoice-'.$invoice->id.'.pdf', 'S'), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="invoice.pdf"',
]);
}
If you specifically want the Laravel-native route, barryvdh/laravel-dompdf or spatie/laravel-pdf (Browsershot) will get you further in an afternoon than porting this bundle ever will.
Extract the service, keep the shape. The bundle's value is the shape: render(template, data) → html → pdf → response. Mirror it with a small injectable class so controllers, jobs, and mailables all share one code path:
class PdfRenderer
{
public function render(string $view, array $data, array $options = []): string
{
$html = view($view, $data)->render();
$pdf = new Html2Pdf($options['orientation'] ?? 'P', 'A4', 'en', true);
$pdf->writeHTML($html);
return $pdf->output('', 'S'); // 'S' = return as string
}
}
Return strings, not responses, from the renderer. The Symfony bundle tends to return a Response directly. That couples you to HTTP and makes the renderer useless for queued jobs and mail attachments. Let the controller decide between response()->streamDownload() and Storage::put().
Separate the layout from the content view. One pdf.layout Blade file holding <style> and page setup, with @yield('content'). TCPDF-era engines ignore most modern CSS, so keep the layout table-based and inline the CSS rather than relying on a stylesheet link.
Queue anything that isn't a single page. Render is CPU-bound and synchronous; a 30-page report will hold a PHP-FPM worker for seconds. Dispatch a job, write to storage/app/private/pdfs, then expose a signed download URL.
Integration tip for mail: attach via $message->attachData($renderer->render(...), 'invoice.pdf', ['mime' => 'application/pdf']). Never attach a Response object.
If you must run the original bundle, do it in a separate Symfony 2.8 microservice behind an HTTP call, not inside the Laravel app. It's the only way the Symfony 2.8 constraints won't poison your dependency tree.
require.{{ var }}, {% for %}, filters like |raw) will not run through Blade. Any template you copy has to be rewritten; {{ }} happens to overlap, {% %} does not.ob_start() / ob_get_clean(), or the downloaded file will be corrupt and open with "cannot display PDF."public_path('images/logo.png') or embed as base64. The Symfony bundle's asset() calls have no Blade equivalent — swap for asset() helper output with an absolute URL, or base64.Html2Pdf routinely exceeds 128M on image-heavy pages. Set ini_set('memory_limit', '512M') and set_time_limit(120) around the call, or raise it in the job's middleware.page-break-before), pt-based sizing.Spipu\Html2Pdf\Exception\Html2PdfException, log $e->getMessage() plus the HTML to a temp file, and inspect that HTML in a browser — most failures are unbalanced tags.'UTF-8' explicitly to the constructor; the default is ISO-8859-1 on older versions and will mangle accents and currency symbols.PdfRendererContract) with Html2PdfRenderer and BrowsershotRenderer implementations bound in a service provider. You will want to swap engines the first time a stakeholder asks for a modern-looking PDF, and that swap should be a one-line config change.Content-Type: application/pdf and the %PDF- magic bytes rather than snapshotting the whole binary; byte output varies by engine version and makes tests flaky.How can I help you explore Laravel packages today?