motoronix/twig-spreadsheet-bundle
Motoronix Twig Spreadsheet Bundle brings Twig templating to spreadsheet generation in Symfony, letting you define Excel and CSV exports as reusable Twig templates and render them from your controllers or services, skipping manual PHP spreadsheet code. Ideal for reports and data exports.
What the package actually is. motoronix/twig-spreadsheet-bundle is a Symfony bundle, not a Laravel package. Its purpose is narrow and useful-sounding: expose PhpSpreadsheet through Twig so that spreadsheet workbooks (XLSX/CSV) can be authored as templates — cell values, merges, styles, and loops declared in a .twig file — rather than built imperatively in PHP. The real engine is phpoffice/phpspreadsheet; this bundle is an authoring/rendering convenience layer on top of it.
Architecture fit. Poor for a Laravel codebase. A Symfony bundle is a packaging convention bound to Symfony's Kernel, DI extension (DependencyInjection/*Extension.php), service tagging, bundle config trees (Configuration.php), and typically Symfony HTTP response classes (StreamedResponse/Response) for downloads. Laravel has no bundle registration mechanism; none of that wiring is reused. What is reusable is the Twig extension layer (the filters/functions/tags that map Twig nodes onto PhpSpreadsheet API calls) — and that layer is coupled to Twig's ExtensionInterface and Environment, which Laravel does not ship.
Integration feasibility. Two viable shapes:
rcrowe/twigbridge to Laravel, configure a second template engine, register the bundle's Twig extensions manually in a service provider. This is achievable but introduces a permanent second templating engine, a second set of escaping/autoescape semantics, and a bundle whose service definitions assume Symfony's container semantics (constructor autowiring by type, parameter bags, no Laravel facades). Expect to write shims and to patch the bundle's service definitions out of the picture entirely.app/Spreadsheet/, stripping the Symfony DI glue. This is usually the correct answer and changes the risk calculus from "dependency on an unmaintained package" to "dependency on ~a few hundred lines of copied code we now own."Technical risk. High, and mostly supply chain, not capability:
spreadsheet-bundle/TwigSpreadsheetBundle lineage. A TPM should assume fork provenance until proven otherwise and check whether upstream fixes are being merged forward.IOFactory, style/conditional-formatting objects, PHP version floors). A thin, low-traffic wrapper is exactly the kind of package that silently pins an old major and blocks your own security patching of PhpSpreadsheet.composer.json constraints visible at scoring time means the PHP/Symfony/PhpSpreadsheet version matrix is unverified. Symfony 5→6→7 bundle base-class changes (Bundle vs AbstractBundle, return types on extension methods) break thin bundles routinely.Key questions before any pilot:
phpoffice/phpspreadsheet major is required, and can it be widened without patching? Does it conflict with the version our other tooling needs?spreadsheet-bundle/spreadsheet-bundle? If so, what has diverged, and are upstream fixes being pulled?php://output or build the whole workbook in memory? (This determines whether large exports are safe.)=, +, -, @ in user-supplied cell values (CSV/formula injection)?Stack fit. The stack fit is conceptually good and mechanically awkward. Conceptually, template-authored spreadsheets are a strong fit for a product where finance/ops stakeholders own report layouts: a Twig file in version control is reviewable, diffable, and editable by non-PHP staff in a way that a 400-line SpreadsheetExport class is not. Mechanically, Laravel + Twig is a mismatch: Blade is the sanctioned engine, Twig needs a bridge, and now every developer must know both. That is a real, recurring cost that should be priced in.
Recommended approach: don't add it as a Composer dependency. Vendor the Twig-extension and renderer classes into the application under app/Support/Spreadsheet/, wrapped behind our own interface:
SpreadsheetRenderer::render(string $template, array $context): StreamedDownload
Backed initially by the vendored Twig extensions; trivially re-implementable with Blade directives + direct PhpSpreadsheet calls if the DSL proves unnecessary. This preserves the one thing the package offers (declarative templates) while removing the abandonment risk, the Symfony-container shims, and the version-pin problem. Keep the MIT header intact and record the source commit in the file docblock for provenance.
Migration path. If an existing Symfony service already uses this bundle, do not lift-and-shift the templates and the bundle simultaneously. Sequence:
.twig spreadsheet templates; classify by feature usage (does anything use images, charts, conditional formats, or multi-sheet loops?).Compatibility. Pin PhpSpreadsheet explicitly in our own composer.json and let the vendored code adapt to it, not the reverse. Watch: PHP floor (PhpSpreadsheet has moved its minimum PHP version forward repeatedly), ext-zip and ext-gd/ext-fileinfo availability across our container images and CI, and memory limits in php.ini for the CLI/queue workers that will actually do the rendering.
Sequencing. Timebox a spike (2–3 days) that answers only: (a) does the Twig DSL cover our top-5 report templates, (b) what PhpSpreadsheet version does it force, (c) how many lines of Symfony glue must be removed to make it run in Laravel. If the answer to (c) exceeds a few hundred lines or (a) requires extending the DSL, stop and build directly on PhpSpreadsheet — at that point the package is contributing negative value. Defer the Twig bridge decision until after the spike; if the DSL is dropped, so is the bridge.
Maintenance. With vendored code, maintenance is a known, bounded internal cost: we own the Twig extension surface (~small), and we own the upgrade work when PhpSpreadsheet majors. With the dependency, maintenance is unbounded and externalized — a zero-star package with no release cadence will either go silent (forcing urgent vendoring later, under pressure) or publish breaking changes without migration notes. The vendored path is more work now and strictly less work later. Budget roughly one engineer-day per PhpSpreadsheet major bump either way.
Support. There is effectively no support channel. No dependents means no Stack Overflow corpus, no blog posts, no one else's bug reports to read. Every question becomes a read-the-source exercise. The upstream to lean on is PhpSpreadsheet's documentation and its own issue tracker, which is genuinely good — another argument for keeping the coupling to PhpSpreadsheet direct and the coupling to this bundle loose.
Scaling. The bottleneck is never the bundle, it is PhpSpreadsheet: cell-by-cell object graph in memory, roughly ~1KB per cell as a working estimate. A 50k-row × 20-column export is ~1M cells and will exhaust a 512MB worker. Implications:
memory_limit on the queue worker explicitly, and treat OOM as an expected, retryable failure rather than an incident.Xlsx writer with a temp file and setPreCalculateFormulas(false); avoid IOFactory auto-detection on our own output.Failure modes to instrument and alert on:
.twig template edited without a golden-file test will silently shift columns. Mitigate with byte-comparison tests against committed fixtures.=, +, -, @, \t, \r must be written as explicit strings (setCellValueExplicit(..., DataType::TYPE_STRING)), not inferred. Verify the vendored code does this and add it if not; this is a security finding, not a nicety.composer update elsewhere in the app forces a PhpSpreadsheet downgrade and spreadsheet generation breaks in a way unrelated to the change that triggered it. Lock PhpSpreadsheet explicitly and put an integration test in CI that actually writes a workbook.Ramp-up. Low for anyone who knows Twig and has seen a templating engine (hours to productive, a day to understand the spreadsheet-specific tags and style objects). Higher for a Blade-only team, because the cost is not this package but Twig itself — a second engine with different autoescape rules, different filter syntax, and different debugging ergonomics. If the team is Blade-native and the number of spreadsheet templates is small (under ~5), skip Twig entirely: write the exports imperatively against PhpSpreadsheet behind a SpreadsheetRenderer interface, and revisit template authoring only when a non-engineer genuinely needs to own a layout. Document the DSL for whoever inherits it, and keep a single worked example template next to the renderer — with zero community examples available, that in-repo example is the entire onboarding path.
Bottom line for the decision log. The idea — declarative spreadsheet templates — is worth adopting. This specific package is not a safe runtime dependency at 0 stars, 0 dependents, and unverified version constraints. Vendor the MIT-licensed Twig extensions behind our own interface, pin PhpSpreadsheet ourselves, render on a queue, and prove correctness with golden-file tests. If the spike shows the DSL doesn't cover our templates, drop it without regret and call PhpSpreadsheet directly — the export engine is doing all the real work either way.
How can I help you explore Laravel packages today?