qipsius/tcpdf-bundle
Symfony bundle that integrates TCPDF into your project, providing a configured service for generating PDF documents from HTML, with manageable settings for fonts, page formats, and output.
composer require qipsius/tcpdf-bundle
The bundle pulls in tecnickcom/tcpdf, so you do not need to install TCPDF separately. There is no Flex recipe, so register it manually:
// config/bundles.php
return [
// ...
Qipsius\TCPDFBundle\QipsiusTCPDFBundle::class => ['all' => true],
];
Confirm the service id and its constructor arguments before writing any code — the bundle registers a pre-configured TCPDF instance (conventionally qipsius.tcpdf), but the exact id/alias varies by version:
bin/console debug:container tcpdf
bin/console config:dump-reference qipsius_tcpdf
Minimal configuration (paths are the only thing you normally need to touch):
# config/packages/qipsius_tcpdf.yaml
qipsius_tcpdf:
tcpdf:
k_path_images: '%kernel.project_dir%/public/images/'
k_path_cache: '%kernel.cache_dir%/tcpdf'
k_path_fonts: '%kernel.project_dir%/vendor/tecnickcom/tcpdf/fonts'
First use case — a one-page PDF from a controller:
public function invoice(TCPDF $tcpdf): Response
{
$pdf = clone $tcpdf; // see Gotchas: the service is a shared prototype
$pdf->SetFont('helvetica', '', 11);
$pdf->AddPage();
$pdf->writeHTML('<h1>Invoice #1042</h1><p>Thanks for your business.</p>');
return new Response($pdf->Output('invoice.pdf', 'S'), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="invoice.pdf"',
]);
}
That's the whole loop: inject, clone, draw, Output(..., 'S'), wrap in a Response. Everything else is TCPDF API knowledge, not bundle knowledge.
Wrap the shared service in a factory. The bundled service is a singleton prototype. For anything beyond a single PDF per request, expose your own service that clones the prototype or constructs a fresh instance:
final class PdfFactory
{
public function __construct(private \TCPDF $prototype) {}
public function create(string $orientation = 'P', string $format = 'A4'): \TCPDF
{
$pdf = clone $this->prototype;
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
return $pdf;
}
}
Twig-rendered PDFs. The most productive pattern is HTML in Twig, rendered to a string, then writeHTML(). Keep templates table-based with inline styles and no flexbox/grid — TCPDF's HTML engine is roughly HTML4 + a CSS subset:
$html = $this->renderView('pdf/invoice.html.twig', ['invoice' => $invoice]);
$pdf->writeHTML($html, true, false, true, false, '');
Shared header/footer. Subclass TCPDF, override Header()/Footer(), and register your subclass as the service by overriding the bundle's definition in config/services.yaml (same service id, class: pointing at yours). This is the cleanest extension point the bundle gives you.
Output modes. Pick deliberately:
'I' — inline stream (browser viewer)'D' — force download'S' — return the bytes as a string (use this for storage, mail attachments, tests)'F' — write to a path on diskEmail attachments. Generate to a temp file and hand the path to Symfony Mailer:
$path = tempnam(sys_get_temp_dir(), 'pdf') . '.pdf';
$pdf->Output($path, 'F');
$email->attachFromPath($path, 'invoice.pdf', 'application/pdf');
Async generation. TCPDF objects are stateful and non-serializable — never put them in a Messenger payload. Instead dispatch a message carrying only the entity id, and build the PDF inside the handler via your factory.
Testing. Assert on %PDF- prefix and Content-Type rather than byte-for-byte output; TCPDF output is not deterministic across versions.
AddPage() twice in one request yields a two-page PDF the second time. Always clone or build fresh. This is by far the most common bug with this bundle.k_path_* values mid-request does nothing; TCPDF also define()s K_PATH_* constants at class load, so a second differing configuration in the same process is silently ignored.k_path_cache at %kernel.cache_dir% and make sure deploy scripts don't wipe permissions. A non-writable cache is a classic source of blank or truncated output.K_PATH_IMAGES. Relative <img src="logo.png"> fails silently (a red box, or nothing) if the path is wrong. Prefer absolute paths in dev, or set k_path_images to a real directory.AddPage(); afterwards SetHeaderData/SetFooterMargin calls won't apply to already-emitted pages.$pdf->getErrors() — TCPDF accumulates errors rather than throwing — and verify writeHTML() received non-empty content. Suppress the E_DEPRECATED noise TCPDF emits on modern PHP in narrow scopes rather than globally.helvetica core font does not cover non-Latin scripts. Switch to dejavusans (bundled) for accented/Cyrillic text; expect larger files and slower generation.memory_limit; raise it in the handler/command, not in php.ini globally. Bulk jobs should generate one PDF per process iteration and unset() between runs.^2/^3 explicitly and read the changelog on Symfony major upgrades.config/services.yaml as the customization surface.How can I help you explore Laravel packages today?