stev/spout-bundle
A PHP bundle wrapping the Spout spreadsheet library, giving your app a simple service for importing and exporting XLSX, ODS, and CSV files. Handy for reading and writing spreadsheets without heavy dependencies or extension requirements.
composer require stev/spout-bundle, then add the bundle to config/bundles.php. Confirm the exact FQCN by reading vendor/stev/spout-bundle/src/ before editing — the class name is not documented anywhere reliable, and this package has no README worth relying on.composer why-not stev/spout-bundle and inspect its composer.json (php, symfony/*, box/spout constraints) before trying to drop it into a Symfony 6/7 project. Expect to pin, or to skip the bundle entirely.Box\Spout\Reader\Common\Creator\ReaderEntityFactory and Box\Spout\Writer\Common\Creator\WriterEntityFactory. Run php bin/console debug:container | grep -i spout and open vendor/stev/spout-bundle/src/Resources/config/services.* to see the services/tags it actually exposes. That five-minute read is the whole "documentation" for this package.// Inside a Symfony command — pattern, not a bundle-specific API
$reader = ReaderEntityFactory::createReaderFromFile($path); // CSV, XLSX, ODS
$reader->open($path);
foreach ($reader->getSheetIterator() as $sheet) {
foreach ($sheet->getRowIterator() as $i => $row) {
if ($i === 1) { $headers = $row->getCells(); continue; }
// map + persist
}
}
$reader->close();
App\Import\XlsxImporter that accepts a header map and a row callback, so controllers and commands share one code path. Let the bundle (or the raw Spout factory) construct the reader; keep iteration logic in your service.iterator_to_array($sheet->getRowIterator()). Instead flush Doctrine every ~500 rows and $em->clear() to keep the identity map from growing.WriterEntityFactory::createXLSXWriter()->openToFile($path), add rows, close(), then email or move the file.StreamedResponse callback with openToBrowser($filename) and close(). This is the only clean way to combine Spout's output with Symfony's response lifecycle.WriterEntityFactory::createRowFromArray($values) is fine for plain data, but use WriterEntityFactory::createCell(...) when you need dates, numbers, or formulas to land in the right Excel type — otherwise everything arrives as text and users complain.TypeError from Spout internals.box/spout v4 (different factories), only your adapter layer changes.box/spout directly (or its successor openspout/openspout) and skip the bundle.ReaderEntityFactory/WriterEntityFactory were removed/renamed in v4. You are pinning an old Spout version — audit it for CVEs before adopting.openToBrowser() fights Symfony. It emits Content-Type/Content-Disposition and exits-adjacent behavior through output buffering. If you call it from a normal controller action you'll get corrupted responses or "headers already sent". Always use StreamedResponse and call ob_end_clean() if anything echoed earlier.sys_get_temp_dir(). On containers with a small /tmp (or read-only mounts) large exports silently fail. Set a writable temp path and monitor disk.$sheet->getRowIterator() inside foreach is lazy — don't collect rows into arrays "to sort them". Stream and sort downstream.DateTime objects depending on the file. Normalize explicitly; don't strtotime() a float.debug:container --tag=..., reading the bundle's service definitions, and stepping through Spout's ReaderFactory are far more productive than searching for docs. If a reader type isn't resolving, the cause is almost always the file extension → reader mapping, not the bundle.*EntityFactory methods in your code — it keeps you testable and makes the eventual migration to Spout v4 a one-file change.How can I help you explore Laravel packages today?