OpenSpout Documentation
repository·5.x·Indexed 22 days ago
https://github.com/openspout/openspoutA high-performance PHP library for reading and writing large CSV, XLSX, and ODS spreadsheet files with minimal memory overhead (typically less than 3MB). It utilizes a streaming approach to process large datasets efficiently, offering features such as cell-level styling, sheet management, and XLSX-specific configurations like shared strings, auto-filters, and workbook protection.
What's inside OpenSpout
- OpenSpout is a community-driven PHP library designed for reading and writing spreadsheet files (CSV, XLSX, and ODS) in a fast and scalable manner. It is specifically optimized for processing very large files while maintaining extremely low memory usage (typically less than 3MB).
Understand OpenSpout's memory management
5.xOpenSpout achieves low memory usage (often less than 3MB) by using a streaming approach for both reading and writing.
- Writing: Data is streamed to files one or a few lines at a time. Only the rows currently being written are held in memory, and memory is freed once they are written.
- Reading: Only one row is stored in memory at a time.
- XLSX Shared Strings: To handle XLSX shared strings without exhausting memory, OpenSpout uses a technique of storing them in several small temporary files to allow fast access while maintaining a low memory footprint.
How styles and immutability work in OpenSpout v5
5.xIn version 5.0, OpenSpout changed how styles are handled to improve performance. Previously, merging a cell's style with a default row style was computationally expensive.
Key changes in v5:
- Style Application: You can no longer rely on automatic merging of a cell style with a default row style. You must choose between having a fallback row style (when a Cell has none) OR a custom style for the Cell. For patterns like zebra striping, you must manually alternate between two distinct styles for even and odd cells.
- Immutability: To ensure deterministic outcomes, most classes are now
readonly. Properties can only be set once during construction. To modify an object, usewith***methods (e.g.,withProperty()), which return a new instance of the object with the specific property overwritten.
Limitations: Chart support
5.xOpenSpout does not support charts. This is a deliberate design choice to ensure low memory usage and scalability. Generating charts would require keeping large amounts of data in memory, which contradicts OpenSpout's core principle of streaming data to handle large datasets efficiently.Manage multiple sheets in a Writer or Reader
5.xWriting Sheets
You can control which sheet data is written to by managing the 'current' sheet:
getCurrentSheet(): Retrieves the sheet currently being written to.addNewSheetAndMakeItCurrent(): Creates a new sheet and switches context to it.setCurrentSheet($sheet): Switches the context back to a specific sheet object.getSheets(): Returns all sheets created in the current writer instance.getCurrentSheet()->setName('Name'): Customizes the name of the current sheet.
Note on Excel Sheet Names: You are responsible for ensuring names are not blank, $\le$ 31 characters, do not contain
\ / ? * : [ ], and do not start/end with a single quote.Reading Sheets
When iterating through a reader, you can access sheet metadata:
$sheet->getName()$sheet->isVisible()$sheet->isActive()(the sheet that was active when the file was last saved)
// Writing example $firstSheet = $writer->getCurrentSheet(); $writer->addRow($rowForSheet1); $newSheet = $writer->addNewSheetAndMakeItCurrent(); $writer->addRow($rowForSheet2); $writer->setCurrentSheet($firstSheet); $writer->addRow($anotherRowForSheet1); // Customizing name $sheet = $writer->getCurrentSheet(); $sheet->setName('My custom name');Configure XLSX String Storage (Shared vs Inline)
5.xXLSX files can store strings in two ways via
OpenSpout\Writer\XLSX\Options:- Inline Strings (
SHOULD_USE_INLINE_STRINGS: true, default): Faster to process but less optimized for file size as duplicates are not de-duplicated. - Shared Strings (
SHOULD_USE_INLINE_STRINGS: false): Optimizes file size by de-duplicating strings.
Important: Apple Numbers and iOS previewers do not support inline strings. If you need to support these platforms, you must use shared strings.
use OpenSpout\Writer\XLSX\Writer; use OpenSpout\Writer\XLSX\Options; // Use shared strings for better compatibility with Apple products $writer = new Writer(new Options( SHOULD_USE_INLINE_STRINGS: false, ));- Inline Strings (
Read data from a specific sheet by position
5.xTo read data from a specific sheet based on its position, iterate through the sheets using
getSheetIterator()and check the sheet index using$sheet->getIndex(). Note that the index is 0-based (e.g., the 3rd sheet has an index of2). Once the target index is matched, iterate through the rows and cells as needed, thenbreakthe loop to stop reading further sheets.$reader = new \OpenSpout\Reader\XLSX\Reader(); $reader->open($filePath); foreach ($reader->getSheetIterator() as $sheet) { // only read data from 3rd sheet if ($sheet->getIndex() === 2) { // index is 0-based foreach ($sheet->getRowIterator() as $row) { // do something with the row example grab cell 2 $cells = $row->cells; //Load all the cells $cell_value = $cells[2]->getValue(); echo "$cell_value \n"; } break; // no need to read more sheets } } $reader->close();Stream spreadsheet generation directly to the browser in Symfony
5.xYou can stream the generation of an XLSX file directly to a user's browser without saving a temporary file on the server. This is achieved by using OpenSpout's
openToBrowser()method inside a SymfonyStreamedResponsecallback.- Create a writer using
WriterEntityFactory::createXLSXWriter(). - Initialize a
StreamedResponsewith a callback. - Inside the callback, call
$writer->openToBrowser('filename.xlsx'). - Iterate through your data, creating rows with
WriterEntityFactory::createRowFromArray($row)and adding them via$writer->addRow(). - Close the writer with
$writer->close(). - Set the
Content-Typeheader toapplication/vnd.ms-excel.
class MyStreamController extends Controller { /** * @Route("/spreadsheet/stream-data") */ public function streamDataAction(): StreamedResponse { $writer = WriterEntityFactory::createXLSXWriter(); $data = [ ['c1r1','c2r1','c3r1'], ['c1r2','c2r3','c3r4'], ]; $response = new StreamedResponse(function () use ($writer, $data) { $writer->openToBrowser('filename.xlsx'); foreach ($data as $row) { $writer->addRow(WriterEntityFactory::createRowFromArray($row)); } $writer->close(); }); $response->headers->set('Content-Type', 'application/vnd.ms-excel'); return $response; } }- Create a writer using
Read files with OpenSpout
5.xOpenSpout provides a consistent interface for reading files regardless of the format. The reader automatically detects the file type based on the extension (e.g.,
.csv,.ods,.xlsx). If the extension is non-standard, you can instantiate a specific reader class directly (e.g.,\OpenSpout\Reader\XLSX\Reader).To read data, open the file, iterate through the sheets using
getSheetIterator(), and then iterate through the rows usinggetRowIterator(). Each row contains acellsproperty.use OpenSpout\Reader\CSV\Reader; $reader = new Reader(); $reader->open('/path/to/file.ext'); foreach ($reader->getSheetIterator() as $sheet) { foreach ($sheet->getRowIterator() as $row) { // do stuff with the row $cells = $row->cells; } } $reader->close();Stream spreadsheet content in Symfony using StreamedResponse
5.xTo avoid waiting for a large spreadsheet to be fully read into memory before sending a response to the browser, you can use Symfony's
StreamedResponse. Instead of building a large string and returning a standardResponse, you use a callback function withinStreamedResponsetoechocontent chunks as they are processed by OpenSpout.When reading a file, use
OpenSpout\Reader\XLSX\Readerto iterate through sheets and rows. To ensure the browser receives data incrementally, useflush()periodically (e.g., every N rows) within the callback to push the echoed content to the client.class MyStreamController extends Controller { const FLUSH_THRESHOLD = 100; /** * @Route("/spreadsheet/stream") */ public function readAction() { $filePath = '/path/to/static/file.xlsx'; $response = new StreamedResponse(); $response->headers->set('Content-Type', 'text/html'); $response->setCallback(static function() use ($filePath): void { $reader = new \OpenSpout\Reader\XLSX\Reader(); $reader->open($filePath); $i = 0; foreach ($reader->getSheetIterator() as $sheet) { echo '<table>'; foreach ($sheet->getRowIterator() as $row) { echo '<tr>'; echo implode(array_map(static function($cell): string { return '<td>' . $cell . '</td>'; }, $row->cells)); echo '</tr>'; $i++; if ($i % self::FLUSH_THRESHOLD === 0) { flush(); } } echo '</table><br>'; } $reader->close(); }); return $response; } }Upgrade from `box/spout:v3` to `openspout/openspout:v3`
5.xTo migrate from the original
box/spoutlibrary to the OpenSpout fork, follow these two steps:- Update your
composer.jsonto replacebox/spoutwithopenspout/openspout. - Perform a global find-and-replace in your codebase to change the namespace
Box\SpouttoOpenSpout.
- Update your
Configure the fallback style for a Writer
5.xBy default, OpenSpout uses a standard style for all created rows. You can override this global default by passing a custom
Styleobject into theOptionsclass when instantiating aWriter.use OpenSpout\Common\Entity\Style\Style; use OpenSpout\Writer\XLSX\Writer; use OpenSpout\Writer\XLSX\Options; $fallbackStyle = new Style( fontName: 'Arial', fontSize: 11, ); $writer = new Writer(new Options(FALLBACK_STYLE: $fallbackStyle)); $writer->openToFile($filePath);