tudorr89 / pdfcombiner
A modern PHP package to combine multiple PDF files with page-level granularity. Supports Laravel auto-discovery and standalone usage.
Requires
- php: ^8.2
- setasign/fpdi: ^2.6
- tecnickcom/tcpdf: ^6.7
Requires (Dev)
- illuminate/support: ^10.0 || ^11.0 || ^12.0
- orchestra/testbench: ^8.0 || ^9.0 || ^10.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10.5 || ^11.0
Suggests
- illuminate/support: Required to use the Laravel service provider and facade (^10.0 || ^11.0 || ^12.0).
README
A modern PHP package to combine multiple PDF files with page-level granularity. Built on TCPDF and FPDI for reliable rendering. Supports Laravel auto-discovery (10.x, 11.x, 12.x) and standalone usage.
Requirements
- PHP
^8.2 - Composer
Under the hood the package depends on tecnickcom/tcpdf and setasign/fpdi — both are pulled in automatically.
Installation
composer require tudorr89/pdfcombiner
Laravel
If you use Laravel ≥ 5.5 the package registers itself via auto-discovery. No manual setup required.
Publish the config (optional):
php artisan vendor:publish --tag=pdfcombine-config
Standalone (non-Laravel)
require 'vendor/autoload.php'; use PdfCombine\PDFCombiner; $combiner = new PDFCombiner;
Quick Start
Combine two files and save to disk
$combiner = new \PdfCombine\PDFCombiner(); $combiner ->addFile('/path/to/report.pdf') ->addFile('/path/to/appendix.pdf') ->save('/path/to/combined.pdf');
Combine specific pages only
$combiner ->addFile('/path/to/large.pdf', [1, 5, 7]) // pages 1, 5, 7 ->addFile('/path/to/summary.pdf') // all pages ->save('/path/to/result.pdf');
Stream to browser for download
$combiner ->addFile('/path/to/doc.pdf') ->addRaw($pdfBinaryString, 'attachment.pdf') ->download('final-report.pdf');
Get PDF contents as a string
$content = $combiner ->addFile('/path/to/doc.pdf') ->blob(); // or $content = $combiner->stream();
Laravel — Dependency Injection
use PdfCombine\Contracts\PDFCombinerInterface; class ReportController { public function combine(PDFCombinerInterface $combiner) { $combiner ->addFile(storage_path('reports/january.pdf')) ->addFile(storage_path('reports/february.pdf')) ->save(storage_path('reports/q1.pdf')); return response()->download(storage_path('reports/q1.pdf')); } }
Laravel — Facade
use PDFCombine; PDFCombine::addFile('/path/to/a.pdf') ->addFile('/path/to/b.pdf') ->download('combined.pdf');
API Reference
addFile(string $path, array $pages = []): self
Add a PDF file from disk.
$path— absolute or relative path to a.pdffile$pages— optional array of page numbers to include (1-indexed). Empty array = all pages.
$combiner->addFile('/path/to/doc.pdf'); // all pages $combiner->addFile('/path/to/doc.pdf', [1, 3]); // pages 1 and 3 only $combiner->addFile('/path/to/doc.pdf', range(5, 10)); // pages 5 through 10
addFiles(array $paths): self
Add multiple files at once. Accepts plain paths or [path, pages] tuples.
$combiner->addFiles([ '/path/to/cover.pdf', ['/path/to/body.pdf', [1, 2, 3]], '/path/to/back.pdf', ]);
addRaw(string $content, string $filename = 'document.pdf', array $pages = []): self
Add PDF content from a raw binary string (e.g. from an HTTP response, database BLOB, or upload stream).
$rawPdf = file_get_contents('https://example.com/remote.pdf'); $combiner->addRaw($rawPdf, 'remote.pdf');
save(string $outputPath): bool
Combine all added files and write the result to $outputPath. Returns true on success.
$combiner->addFile('a.pdf')->addFile('b.pdf')->save('combined.pdf');
download(string $filename = 'combined.pdf'): void
Send the combined PDF to the browser as a download (sends Content-Disposition: attachment headers + body). Does not call exit — middleware and terminating callbacks still run.
$combiner->download('quarterly-report.pdf');
In Laravel, prefer streaming the body yourself so you stay on the full response pipeline:
return response()->streamDownload( fn () => print($combiner->stream()), 'quarterly-report.pdf', ['Content-Type' => 'application/pdf'], );
stream(string $filename = 'combined.pdf'): string
Return the combined PDF as a raw binary string.
$pdf = $combiner->stream(); // store in S3, send via email attachment, etc.
blob(): string
Alias for stream().
getPageCount(): int
Return the total number of pages that would be included in the output (honours page-range filters).
$combiner ->addFile('doc.pdf', [2, 4]) ->addFile('summary.pdf'); echo $combiner->getPageCount(); // 2 + all pages of summary.pdf
getFileCount(): int
Return the number of files currently queued.
reset(): self
Clear all queued files. Preserves orientation / unit / paper size so the same configured instance can be reused.
$combiner->reset();
resetConfig(): self
Restore orientation / unit / paper size to their defaults (P / mm / A4). Does not touch the file queue.
$combiner->resetConfig();
Configuration
Orientation
use PdfCombine\Contracts\PDFCombinerInterface; $combiner->withOrientation(PDFCombinerInterface::ORIENTATION_PORTRAIT); // 'P' $combiner->withOrientation(PDFCombinerInterface::ORIENTATION_LANDSCAPE); // 'L'
Unit of measurement
$combiner->withUnit(PDFCombinerInterface::UNIT_MM); $combiner->withUnit(PDFCombinerInterface::UNIT_CM); $combiner->withUnit(PDFCombinerInterface::UNIT_IN); $combiner->withUnit(PDFCombinerInterface::UNIT_PT);
Paper size
// Named constants $combiner->withPaperSize(PDFCombinerInterface::SIZE_A4); $combiner->withPaperSize(PDFCombinerInterface::SIZE_A5); $combiner->withPaperSize(PDFCombinerInterface::SIZE_A6); $combiner->withPaperSize(PDFCombinerInterface::SIZE_LETTER); $combiner->withPaperSize(PDFCombinerInterface::SIZE_LEGAL); // Or any TCPDF-compatible format string $combiner->withPaperSize('A3'); $combiner->withPaperSize('Legal'); // Or custom [width, height] array (in the unit defined above) $combiner->withPaperSize([210, 297]); // A4 in mm
Laravel config (config/pdfcombine.php)
return [ 'orientation' => env('PDFCOMBINE_ORIENTATION', 'P'), 'unit' => env('PDFCOMBINE_UNIT', 'mm'), 'paper_size' => env('PDFCOMBINE_PAPER_SIZE', 'A4'), ];
Environment variables override the defaults:
PDFCOMBINE_ORIENTATION=L PDFCOMBINE_UNIT=in PDFCOMBINE_PAPER_SIZE=Letter
Fluent interface
All setter and adder methods return $this, so you can chain calls:
$content = $combiner ->withOrientation(PDFCombinerInterface::ORIENTATION_LANDSCAPE) ->withPaperSize(PDFCombinerInterface::SIZE_A3) ->addFile('cover.pdf') ->addFile('body.pdf', range(2, 10)) ->addFile('back.pdf') ->stream();
Error handling
The package throws typed exceptions from PdfCombine\Exceptions\PDFCombineException:
| Method | Exception message |
|---|---|
| File not found | PDF file not found at path: ... |
| Invalid / unreadable PDF | The file at '...' is not a valid PDF or could not be read. |
| No files added | No PDF files have been added to combine. |
| Save failed | Failed to save the combined PDF to: ... |
| Page out of range | Requested page X is out of range. The document has Y pages. |
use PdfCombine\Exceptions\PDFCombineException; try { $combiner->addFile('/missing.pdf')->save('/out.pdf'); } catch (PDFCombineException $e) { logger()->error('PDF combine failed: ' . $e->getMessage()); }
Changelog
See the releases page on GitHub.
License
MIT — see the LICENSE file for details.