whitedigital-eu/document-generator-bundle
A PHP bundle that adds flexible document generation to Symfony applications, letting you build files from templates and reusable content. Streamline creating invoices, reports, and other documents directly from your app with a simple, configurable workflow.
This package is distributed as a Symfony-style bundle rather than a native Laravel package, so the first job is bridging it into Laravel's container before you write any generation code.
composer require whitedigital-eu/document-generator-bundle
Open vendor/whitedigital-eu/document-generator-bundle/ and read, in this order:
composer.json — confirms the PHP constraint and whether symfony/* contracts are pulled in.src/ — the generator service class and any DependencyInjection/ extension, which tells you the exact service id and configuration keys.README.md — template syntax and the expected data-array shape.DocumentGeneratorServiceProvider. Symfony bundles expect a container Extension to translate config arrays into service arguments; in Laravel you replicate that with a singleton:$this->app->singleton(DocumentGenerator::class, function ($app) {
return new DocumentGenerator(
templatePath: config('documents.templates'),
outputPath: config('documents.output'),
);
});
Publish a config/documents.php with templates, output (point this at a disk path, not storage_path() directly — see gotchas) and any driver/format settings.
First use case: render a DOCX invoice from a stored .docx template. Put the template in resources/templates/invoice.docx with placeholder tokens, then:
$path = app(DocumentGenerator::class)->generate('invoice.docx', [
'customer' => $customer->name,
'invoice_no' => $invoice->number,
'lines' => $invoice->lines->map->only(['description', 'qty', 'price'])->all(),
]);
Start with a two-line template and one scalar placeholder before touching repeating blocks — it isolates wiring problems from template-syntax problems.
Wrap it, don't sprinkle it. Bind the generator behind your own DocumentService interface so the bundle's classes never appear in controllers. When the bundle's API shifts (or you outgrow it), you change one adapter.
Template-first workflow. Keep templates in version control under resources/templates/, treat them as fixtures, and add a test that renders each one with a canonical data array. A broken template is otherwise only discovered in production.
Repeating blocks via n-keyed arrays. The primary pattern is passing a flat list of associative arrays for a repeating region (table rows, address lists). As of 0.3.3, numerically-indexed lists are validated correctly:
'lines' => [
['description' => 'Consulting', 'qty' => 2, 'price' => 500],
['description' => 'Hosting', 'qty' => 1, 'price' => 50],
],
Pass ->values()->all() on Eloquent collections so keys are 0..n-1 rather than the collection's original keys — this is now the supported path rather than something to work around.
Queue anything user-triggered. Generation is CPU- and disk-bound. Dispatch a GenerateDocument job, write to a private disk, and notify when done:
dispatch(new GenerateDocument($invoice));
Storage integration. Use Laravel's Storage facade for retrieval (signed temporary URLs) but hand the generator absolute filesystem paths via Storage::disk('documents')->path($file). Keep the two concerns separate in your adapter.
Data extraction is your job. Build a dedicated toDocumentArray() method on the model (or a small presenter class) rather than passing models in. This keeps the template contract explicit and testable, and it's where you normalise dates/currency to strings the template can render.
The 0.3.3 fix matters if you use collections. Prior to 0.3.3, validation rejected arrays with integer keys, so repeated rows coming from Collection::all() (keys 0,1,2…) failed while hand-written string-keyed arrays passed. If you were on 0.3.2 or earlier you may have a workaround in place — re-keying data with 'row_0'-style keys, wrapping in an extra object, or array_combine. Remove those shims and re-run your template tests; leaving them in will confuse the next developer and can break nested blocks.
Upgrade is low-risk. 0.3.3 is a single validation fix on top of 0.3.2. No API, config, or template-syntax changes. Bump, run your rendering tests, done. Pin it ("0.3.3" or ^0.3) — this is a 0.x package, so minor bumps can break.
Validate your own data first. The bundle's validator checks the shape of what you pass against the template, but error messages surface as exceptions mid-render. Validate the domain data (required fields, non-empty line items) in a FormRequest or a toDocumentArray() guard so failures are legible.
Config quirks in a non-Symfony host. The bundle's Symfony extension won't auto-run under Laravel. Any configuration it supports must be passed manually to the constructor, and defaults baked into the extension will not apply. Read the extension class to see which defaults you need to mirror — otherwise you'll hit puzzling nulls on output or driver settings.
Debugging failed renders. Generate to a literal storage_path() during debugging rather than a configured disk so you can open the file immediately; corrupt or half-written output usually means a template token mismatch, not a code bug. Keep a known-good template + data pair as a smoke test.
Extension points. The generator's format/driver abstraction (if exposed) and its output-path handling are the useful seams. Subclass or decorate the generator's rendering step to post-process (PDF conversion, watermarking, metadata) rather than editing templates.
Large documents. Memory usage scales with the data array, not the template. Chunk generation for anything over a few thousand rows and merge afterwards, or you'll hit PHP memory limits inside a queue worker.
How can I help you explore Laravel packages today?