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.
1. Check compatibility before installing. This bundle targets PHP 7.1+ and Symfony 3.4+ and its last release was April 2021. Before composer require, confirm your Symfony version is in the supported range (likely 3.4–5.x). On Symfony 6/7 you should expect to fork or patch the bundle (missing return types on Bundle subclasses, deprecated service/twig config).
composer require erelke/twig-spreadsheet-bundle
# PhpSpreadsheet is the core dependency; align it explicitly
composer require phpoffice/phpspreadsheet
# Only if you need PDF output
composer require mpdf/mpdf
2. Register the bundle — Flex: add to config/bundles.php; legacy: AppKernel::registerBundles().
3. Discover the real config and service IDs rather than trusting old blog posts:
bin/console config:dump-reference twig_spreadsheet
bin/console debug:container | grep -i spreadsheet
The bundle's service definitions under vendor/erelke/twig-spreadsheet-bundle/Resources/config/ and its tests are the most reliable documentation for Twig function names and the renderer service ID.
4. First use case — an "Export to Excel" button. Render a Twig template into a Spreadsheet object, then hand it to a PhpSpreadsheet writer:
#[Route('/export/orders.xlsx', name: 'export_orders')]
public function export(/* the bundle's renderer service */ $renderer): Response
{
$spreadsheet = $renderer->render('spreadsheet/orders.xlsx.twig', [
'orders' => $orderRepository->findRecent(),
'generatedAt' => new \DateTimeImmutable(),
]);
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$response = new StreamedResponse(fn () => $writer->save('php://output'));
$response->headers->set('Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
$response->headers->set('Content-Disposition', 'attachment; filename="orders.xlsx"');
return $response;
}
Verify the exact service ID and method signature from debug:container — the snippet above reflects the intended usage pattern, not a guaranteed API.
Template-as-workbook. Each export is a Twig file that emits cells; keep one template per report shape (orders.xlsx.twig, invoices.pdf.twig). This is the bundle's whole value proposition: designers can tweak layout without touching PHP.
Macros for repeated rows. Lean on the bundle's macro/include support: a _header.twig macro for column titles, _row.twig for a data row, _totals.twig for aggregates. Compose per report rather than copy-pasting markup.
{% import 'spreadsheet/_row.twig' as row %}
{% for order in orders %}
{{ row.render(order) }}
{% endfor %}
Use an existing .xlsx as the styling template. Instead of fighting PhpSpreadsheet's style API from Twig, keep a master workbook with fonts, colors, column widths, and number formats, load it as the base, then fill cells. This is the most maintainable path for anything client-facing.
Writer selection by format. Centralize format → writer mapping (Xlsx, Xls, Ods, Csv, Mpdf) in one service so controllers stay thin and Content-Type/filename headers stay consistent.
Async for anything large. Render in a Messenger handler and store to disk/S3, then email a link. Streaming a 50k-row workbook from a web request will time out.
Cache/refactor into an ExportService. Wrap renderer + writer + response-building so a controller is 3 lines and exports are unit-testable.
Testing. Don't snapshot bytes. Re-read the produced file with IOFactory::load() and assert on cell values/formulas — the workbook is the contract.
phpoffice/phpspreadsheet and expect to patch signatures.memory_limit, enable cell caching, use setReadDataOnly when reading templates, and prefer CSV for high-volume data pipelines.=, +, -, or @ becomes a formula or an injection vector. Prefix user-supplied strings with ' or sanitize before writing.{% %} blocks can land in cell values or shift your layout. Use {%- -%} trimming in spreadsheet templates.DateTimeInterface objects, not formatted strings, and set number formats in the base template so Excel localizes correctly. For CSV, set delimiter/enclosure explicitly for your target locale (decimal comma, etc.).var/ and inspect with getCell('A1')->getValue(). Twig exceptions inside a spreadsheet template are often opaque — isolate one template at a time.config:dump-reference twig_spreadsheet after any upgrade rather than assuming the old YAML still applies.AbstractExtension; custom writers or a replacement renderer can override the bundle service. These are your escape hatches when the upstream code stops fitting.How can I help you explore Laravel packages today?