erelke/twig-spreadsheet-bundle
Symfony bundle that integrates PhpSpreadsheet with Twig, letting you build spreadsheets from templates with macro and include support. Exports to XLSX, XLS, ODS, CSV, and PDF via mPDF.
Framing correction up front. This is not a Laravel package. erelke/TwigSpreadsheetBundle is a Symfony bundle — it registers a DI extension, a compiler pass, and services into the Symfony container, and depends on symfony/framework-bundle, twig/twig, and phpoffice/phpspreadsheet. Laravel will not auto-discover or boot it. That mismatch is the single largest architectural fact in this assessment and it reframes the evaluation from "should we composer require this" to "is this codebase a useful reference we should mine."
Architecture fit. The package's actual value is one idea: it lets a spreadsheet file serve as the layout template, with Twig rendered into cells rather than into HTML. That is genuinely valuable for finance/ops reporting where stakeholders author the visual layout in Excel (fonts, merged cells, column widths, print areas) and engineers only supply the data bindings. The Twig macro/include support means reusable cell snippets and header blocks. None of that is Symfony-specific in principle — the rendering pipeline is PhpSpreadsheet reader → cell-range iteration → Twig environment → writer. Only the wiring is Symfony-shaped.
Integration feasibility. Low as a dependency, moderate as a reimplementation. Installing it into Laravel means fighting bundle registration, the Symfony event dispatcher integration, and a 2021-era service definition file. The realistic path is extraction: read the bundle's service classes (likely a template loader, a spreadsheet renderer, and a format-selection layer), port the logic into a Laravel service provider, and use twigbridge if you want Twig in Laravel — or rewrite the binding layer onto Blade. The surface area is probably a few hundred lines, not thousands, so this is a 1–3 day spike, not a project.
Technical risk: high, on multiple axes.
include() semantics and autoescaping defaults. Symfony 6/7 removed the container APIs a 2021 bundle compiled against. Assume it does not run on a current stack without patching.=, +, -, @ prefixes executing on open). There is no indication this bundle sanitizes, and an abandoned package will never get that patch..xlsx artifacts in git. Reviewing diffs is impossible; a designer's styling change is an opaque binary commit.Key questions for the maintainer/spike owner:
Stack fit. Poor as-is, workable as a pattern. Laravel + Blade is the house stack; introducing Twig at all is a second templating language and a permanent cognitive tax. The pragmatic middle is: keep PhpSpreadsheet (actively maintained, MIT, the real engine), keep Blade for the data layer, and implement the spreadsheet-as-template concept natively — load a .xlsx template, walk a named-range or placeholder map, write values, stream the output.
Migration path. Three options ranked:
SpreadsheetTemplate service in our app: PhpSpreadsheet\IOFactory::load() a template, resolve {{ token }} placeholders per cell, write via Xlsx writer. 2–4 days, full ownership, no Twig, no dead dependency, and we control the injection sanitizer.dusterio/laravel-symfony-style shims or a Symfony sidecar; cost exceeds the value for a feature this narrow.Compatibility. If we do any extraction, pin explicit versions: PHP 8.2, phpoffice/phpspreadsheet ^2 (or ^3), twig/twig ^3. Budget a half-day for API drift on the writer and style calls. Do not inherit the bundle's composer.json constraints.
Sequencing.
rpt_header, rpt_total) instead of raw cell coordinates so templates survive row insertions.Ship behind a feature flag; keep the Symfony bundle out of composer.json entirely.
Maintenance. If we adopt the dependency: the burden is 100% ours from day one — no upstream patches, no security response, no PHP 9 readiness. That is a standing liability for a feature that generates reports. If we build option 1: the burden is normal — we own ~300 lines of our own, well-tested code, on a maintained engine. The build option is less maintenance despite more initial work; this is the central TPM judgment here.
Support. No vendor, no maintainer responsiveness, no issue triage. Internal support means whoever wrote the extraction owns it. Document the placeholder convention in a README and keep a sample template in the repo — otherwise template authoring becomes an oral tradition and the bus factor is 1.
Scaling. PhpSpreadsheet holds the full workbook object graph in memory — roughly 1KB+ per cell with styles. A 50k-row × 20-column export is a memory incident waiting to happen. Concretely: cap synchronous generation at ~5k rows, push anything larger to a queue worker with its own memory limit, and for genuinely large exports switch to a streaming writer (OpenSpout) or offer CSV instead of xlsx. Twig rendering into cells adds a per-cell template evaluation cost on top — benchmark at 10k cells before committing to the templating approach for high-volume reports. Set memory_limit and a hard row ceiling in config, and fail loudly rather than OOM-killing the worker.
Failure modes to instrument:
.xlsx template and removes a named range → runtime null-reference. Validate template structure on upload, not at generation time.Ramp-up. The engine (PhpSpreadsheet) is well documented; a mid-level Laravel dev reaches productive output in 2–3 days. The template-authoring workflow is the real ramp: business users need a one-page guide to placeholder placement, and engineers need to internalize that layout lives in a binary artifact under version control. If we go the Twig route, add a day for developers who've never used Twig's macro/include and for the escaping model — Twig's autoescape defaults interacting with spreadsheet cell content is a subtle source of double-escaped output.
Verdict. Do not take the dependency. Two stars, zero dependents, five years stale, and the wrong framework for our stack is enough to disqualify it as a composer require. Treat it as a well-scoped reference design, harvest the spreadsheet-as-Twig-template concept, and implement it in ~a week on PhpSpreadsheet with Blade and our own sanitization. Revisit only if a maintained fork appears with active CI on PHP 8.3+.
How can I help you explore Laravel packages today?