Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Fpdf Bundle Laravel Package

mayeco/fpdf-bundle

mayeco/fpdf-bundle is a Symfony bundle that integrates the FPDF PHP library, exposing it as a service and handling configuration so applications can generate PDF documents from controllers or templates with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before you write a line: mayeco/fpdf-bundle is a Symfony bundle. It ships a Bundle class and a DependencyInjection extension that register a pre-configured FPDF service in Symfony's container. Laravel has no bundle system — nothing in this package auto-boots, there is no service provider, no facade, no publishable config. If you composer require it into a Laravel app you get an unbooted Bundle class and a vendored copy of FPDF 1.7. The only thing you actually consume is the library.

So the honest day-to-day starting point is: use the FPDF payload directly via setasign/fpdf (1.8.x, PSR-0 autoloaded, PHP 8 compatible), not this bundle. Same global FPDF class, same API, one fewer framework coupling. Reach for the bundle itself only if you inherit a Symfony app already wired to it.

If you do need to understand what the bundle gives a Symfony app — because you're porting from one — look here first:

  1. vendor/mayeco/fpdf-bundle/Resources/config/services.yml — the service id and class it exposes (typically an FPDF definition with unit/orientation/page-size defaults).
  2. The vendored fpdf.php — confirm it is 1.7: global class, no namespace, Latin-1 assumptions.
  3. fpdf.org and its script library — still the best reference for Cell, MultiCell, Ln, SetFont, Output.

The minimal Laravel equivalent of what the bundle does (one service binding plus a configured subclass):

// app/Providers/PdfServiceProvider.php
public function register(): void
{
    // Bind a factory, not a shared instance — see Gotchas.
    $this->app->bind(InvoicePdf::class, fn () => new InvoicePdf());
}

// app/Pdf/InvoicePdf.php
class InvoicePdf extends \FPDF
{
    public function __construct()
    {
        parent::__construct('P', 'mm', 'A4');
        $this->SetAutoPageBreak(true, 15);
    }
}

First use case: an invoice or report endpoint that streams a PDF inline. GET /invoices/{invoice}/pdf → a builder service assembles the document → $pdf->Output('S', "invoice-{$id}.pdf") returns the bytes as a string → hand them to a Laravel response() with the right headers. That single flow exercises everything you'll need: font setup, layout, pagination, and transport.

Implementation Patterns

Keep document construction out of the controller. The bundle's own idiom — inject the FPDF service, mutate it from a controller — becomes a fat controller mutating shared state in Laravel terms. Prefer one builder class per document type, invoked by a thin controller:

class InvoiceController extends Controller
{
    public function show(Invoice $invoice, InvoicePdfBuilder $builder): Response
    {
        $pdf = $builder->build($invoice); // returns raw PDF bytes

        return response($pdf, 200, [
            'Content-Type'        => 'application/pdf',
            'Content-Disposition' => "inline; filename=\"invoice-{$invoice->id}.pdf\"",
        ]);
    }
}

Use Output('S') and return a response — never Output('I') or 'D' inside a Laravel request. 'I'/'D' echo to stdout and send their own headers, which fights Laravel's response layer and produces "headers already sent" bugs. 'S' returns a string you control. For large documents, prefer 'F' to write to a temp file and stream it back with response()->download() or response()->file().

Centralise the constructor arguments. The only meaningful thing the bundle contributes is wiring unit, orientation, and page size as a service definition. Re-create that as a small config file so page geometry isn't scattered across builders:

// config/pdf.php
return ['unit' => 'mm', 'page_size' => 'A4', 'margin' => 15];

Read it in the builder's constructor, not at each AddPage() call site.

Tables are the day-to-day workhorse. FPDF has no table primitive — you draw with Cell/MultiCell and manual X/Y math. The stable pattern for multi-page tables:

  • Compute column widths as an array once, derive X positions from a cumulative sum.
  • Draw a repeating header row at the top of each page by overriding Header() (or checking GetY() against a page-break threshold and calling AddPage()).
  • Use MultiCell for wrapping text columns and Cell for fixed numeric columns; capture GetY() after each row so wrapping doesn't collide with the next row.
  • Reserve the footer with SetAutoPageBreak(true, $margin) so content never overlaps it.

Bind a factory, not a singleton. FPDF is a mutable object — AddPage, SetFont, Cell all mutate internal state. A shared singleton will leak one document's fonts/pages into the next request. $this->app->bind(...) (fresh instance per resolution) is correct; singleton(...) is a trap.

Override Header()/Footer() on a base subclass. This is the clean extension point for branding, page numbers ("Page X of Y" requires a two-pass trick or deferring the total), and recurring disclaimers. It beats sprinkling header-drawing code into every builder.

Queue large documents. PDF generation is CPU-bound and synchronous, and FPDF holds the whole document in memory. A 200-page statement blocks a web worker and can blow memory_limit. Dispatch a job, write 'F' output to storage, notify when ready — don't render it in the request cycle.

Gotchas and Tips

The install itself is the first gotcha. The package targets Symfony 2.x-era constraints. Laravel 10/11 pull Symfony 6/7 components, so composer require will very likely hard-conflict. Run composer require mayeco/fpdf-bundle --dry-run to confirm empirically — if it fails, that settles the decision in fifteen minutes and setasign/fpdf is your answer.

UTF-8 is the classic FPDF landmine. FPDF 1.7 is Latin-1. Non-ASCII names, addresses, currency symbols, and accented characters silently render as garbage or blank. The dangerous part is the silence — nothing errors, the customer just gets a mangled invoice. If you have any non-Latin-1 content (and if you're shipping to real users, you probably do), either convert every string with iconv('UTF-8', 'windows-1252//TRANSLIT', $s) before drawing, or accept that FPDF is the wrong tool. Add a CI fixture with accented/non-ASCII content either way.

Fonts live on disk. FPDF needs .php/.z font definition files in a directory it can read; a missing or non-writable path yields confusing errors rather than a clear "font not found". Pin the font path in config, and generate the definition files (makefont) as part of your deploy, not at runtime.

PHP 8.2+ friction. The 1.7 codebase predates PHP 8 — expect dynamic-property deprecations and possibly removed-function fatals. setasign/fpdf 1.8.x is the maintained, PHP 8-compatible drop-in; use it and the noise disappears. If you're stuck on the 1.7 copy, watch your error log for deprecation spam masking real issues.

Don't echo — return or stream. Output('I') in a controller is the #1 source of "headers already sent" and corrupted downloads in Laravel. Always build the string ('S') or file ('F') and let Laravel emit the response.

No namespace, no autoloading via the bundle. The FPDF class is global. In a Laravel codebase that means your subclasses must extends \FPDF and you should confirm the class is actually reachable — the bundle's autoloading works through Symfony's container/class-loader, which won't run here. Depend on setasign/fpdf so Composer's PSR-0 handles it.

Memory, not speed, is your ceiling. FPDF assembles the whole document in memory. Set an explicit per-document row/page cap, alert on worker OOM, and test with a realistically large document — not "Hello World". The large one is the only one that reveals pagination, memory, and encoding problems.

Testing. Golden-file regression tests (byte comparison, or extract text with pdftotext and assert on content) are cheap and catch silent encoding/layout regressions. Don't assert on exact bytes if you plan to switch engines — assert on extracted text.

License/attribution. FPDF ships under its own permissive license with expectations around document producer/attribution metadata; the repo's MIT covers only the Symfony glue. If these PDFs are customer-facing, have someone confirm the metadata is acceptable — this is a legal question, not an engineering one.

Cost of reversal is in the layout code, not the integration. The service provider is fifty lines; the Cell/MultiCell coordinate math is what's expensive to rewrite. If there's any chance you'll migrate to an HTML-to-PDF engine later, put a PdfRenderer interface in front of the builder now and bind one implementation — that single seam caps the future cost.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core