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

Pdf Merger Bundle Laravel Package

tomsgu/pdf-merger-bundle

A Symfony bundle for merging multiple PDF files into one, exposing PDF merger functionality through the service container with a simple, configurable API for PHP applications. Ideal for combining reports, invoices, and documents programmatically.

View on GitHub
Deep Wiki
Context7

Getting Started

First, a reality check: this is a Symfony bundle, not a Laravel package. There is no composer.json extra.laravel block, so Laravel's package auto-discovery will not register it. In a Laravel codebase you have two options, and you should usually pick the first:

  1. Skip the bundle, use the underlying library. The bundle is a thin DI wrapper around tomsgu/pdf-merge. Install just the library:

    composer require tomsgu/pdf-merge
    

    This avoids dragging in Symfony's dependency-injection and HTTP-kernel components.

  2. Install the bundle anyway if you're in a hybrid Symfony/Laravel team or already depend on Symfony components. You'll need to register the service manually (see Implementation Patterns), because there's no service provider to do it for you.

Where to look first:

  • The tomsgu/pdf-merge README is the real documentation — the bundle adds ~nothing except wiring.
  • In the bundle, open Resources/config/services.yaml (or config/services.yaml in newer versions) to find the service ID and class it registers. That's the only thing you need from the bundle.
  • Check the bundle's composer.json for the exact version of tomsgu/pdf-merge it pins; that's the API you'll actually be calling.

First use case — merge a customer's invoices into one PDF:

use Tomsgu\PdfMerger\PdfMerger;

$merger = new PdfMerger();
$merger->addPDF(storage_path('invoices/inv-001.pdf'), 'all');
$merger->addPDF(storage_path('invoices/inv-002.pdf'), 'all');
$merger->addPDF(storage_path('invoices/inv-003.pdf'), '1-2');

// Argument order differs between tomsgu's fork and the original PDFMerger lib.
// Confirm against the installed version's README. In tomsgu's fork it's (path, mode):
$merger->merge(storage_path('invoices/pack.pdf'), 'file');

Modes are typically 'file' (write to disk), 'string' (return the raw PDF bytes), and 'download' (emit HTTP headers and exit). For a Laravel controller you almost always want 'string' so you can hand it to response()->make() or Storage::put().

Implementation Patterns

Manual service binding (if you installed the bundle). Don't try to boot the Symfony kernel. Just bind the class it wraps:

// app/Providers/AppServiceProvider.php
use Tomsgu\PdfMerger\PdfMerger;

public function register(): void
{
    $this->app->bind(PdfMerger::class, fn () => new PdfMerger());
}

Then inject PdfMerger anywhere. Because PdfMerger is stateful (addPDF accumulates state until merge()), bind it as a transient, not a singleton — otherwise one request's files leak into the next. $this->app->bind(...) does that; singleton(...) does not. This is the single most common bug people hit with this library.

A "statement pack" service. Wrap the merger in a domain service so controllers and jobs stay clean:

class InvoicePackBuilder
{
    public function __construct(private PdfMerger $merger) {}

    /** @param Invoice[] $invoices */
    public function build(array $invoices): string
    {
        foreach ($invoices as $invoice) {
            $this->merger->addPDF(
                Storage::disk('local')->path($invoice->pdf_path),
                'all'
            );
        }
        return $this->merger->merge('', 'string'); // raw bytes
    }
}

Streaming back to the browser without writing an intermediate file:

$bytes = $builder->build($invoices);

return response($bytes, 200, [
    'Content-Type'        => 'application/pdf',
    'Content-Disposition' => 'inline; filename="invoices.pdf"',
    'Content-Length'      => strlen($bytes),
]);

Page ranges. The addPDF second argument accepts 'all' or a comma-separated list like '1,3,5-7'. This lets you extract a subset — e.g. strip cover pages from every invoice before packing, or pull just page 1 (the summary) from a multi-page report. Combine this with 'all' on others freely; order of addPDF calls is the order in the output.

Queued jobs for large batches. Merging 50+ PDFs of several MB each will blow PHP's memory_limit (see Gotchas). Push it to a queued job, write the result to S3/local storage, and notify the user. The merger's string mode holds the entire output in memory, so the job's peak usage is roughly the sum of input sizes plus the output size.

Integration with existing PDF tooling. If you already use barryvdh/laravel-dompdf to generate PDFs, the workflow is: generate each invoice to a temp path with Dompdf, feed those paths to the merger, then delete the temps in a finally block. The two libraries are complementary — Dompdf generates, pdf-merge combines.

Gotchas and Tips

FPDI licensing and format limits. This package uses setasign/fpdi under the hood (free version). It cannot read PDFs that are encrypted, password-protected, use XFA forms, or use certain PDF 1.5+ compressed object streams. You'll get a setasign\Fpdi\PdfParser\PdfParserException when a customer uploads a "modern" PDF exported from some tools. Wrap addPDF() in a try/catch and give the user a clear "this file is not supported" message rather than a stack trace. Commercial FPDI with the fpdi-pdf-parser add-on is the paid fix.

Argument order changed between forks. The original clegginabox/pdfmerger library uses merge($mode, $path); tomsgu/pdf-merge flipped it to merge($path, $mode). Copy-pasting examples from blog posts written against the original will silently misbehave (you'll get a file literally named "file" in your working directory). Pin your version and check the README.

Memory. Each addPDF imports pages into an in-memory FPDF document. For a 200-page 10 MB output you may need memory_limit of 256 MB or more. Don't raise it globally in php.ini — use ini_set('memory_limit', '512M') inside the queued job or the specific controller, and make sure max_execution_time covers it too.

Local filesystem paths only. addPDF takes filesystem paths, not Storage URLs or S3 URIs. If your PDFs live on S3, you must download them to a temp path first (Storage::disk('s3')->get() then file_put_contents(tmpfile())), and clean up. There is no stream wrapper support.

Statelessness. Reuse of a PdfMerger instance across requests (e.g. resolved as a singleton by the bundle's Symfony service definition) will concatenate files from unrelated users into a single PDF — a serious data-leak vector. If you do install the bundle's service, immediately override its scope to transient in your container.

No Laravel facade, no config publishing. Don't waste time looking for config/pdf-merger.php or a Pdf facade; there is none. Treat the bundle as "a YAML file that news up a class" and configure via your own service provider or config/services.php.

Extension points. The PdfMerger class is not final in most versions — you can subclass it to add helpers (addFromStorageDisk(), mergeInChunks()) and bind that subclass in your provider. This is cleaner than sprinkling logic at every call site.

Debugging failed merges. The library tends to fail late, inside merge(), with generic "unable to create output" messages that actually mask a bad input PDF. Re-run the input list one PDF at a time to isolate the offender before blaming your code. Log the file sizes and page counts of every input as you add them; nine times out of ten the culprit is a 0-byte or 1-page corrupt upload.

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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels