OpenSpout Documentation

repository·5.x·Indexed 22 days ago

https://github.com/openspout/openspout

A 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.

Tokens
13.1K
Snippets
36
Records
43
Agent score
78%

What's inside OpenSpout

  1. Overview of OpenSpout

    5.x
    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).
  2. Understand OpenSpout's memory management

    5.x

    OpenSpout 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.
  3. How styles and immutability work in OpenSpout v5

    5.x

    In 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, use with*** methods (e.g., withProperty()), which return a new instance of the object with the specific property overwritten.
  4. Limitations: Chart support

    5.x
    OpenSpout 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.
  5. Manage multiple sheets in a Writer or Reader

    5.x

    Writing 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');
  6. Configure XLSX String Storage (Shared vs Inline)

    5.x

    XLSX files can store strings in two ways via OpenSpout\Writer\XLSX\Options:

    1. Inline Strings (SHOULD_USE_INLINE_STRINGS: true, default): Faster to process but less optimized for file size as duplicates are not de-duplicated.
    2. 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,
    ));
  7. Read data from a specific sheet by position

    5.x

    To 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 of 2). Once the target index is matched, iterate through the rows and cells as needed, then break the 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();
  8. Stream spreadsheet generation directly to the browser in Symfony

    5.x

    You 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 Symfony StreamedResponse callback.

    1. Create a writer using WriterEntityFactory::createXLSXWriter().
    2. Initialize a StreamedResponse with a callback.
    3. Inside the callback, call $writer->openToBrowser('filename.xlsx').
    4. Iterate through your data, creating rows with WriterEntityFactory::createRowFromArray($row) and adding them via $writer->addRow().
    5. Close the writer with $writer->close().
    6. Set the Content-Type header to application/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;
        }
    }
  9. Read files with OpenSpout

    5.x

    OpenSpout 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 using getRowIterator(). Each row contains a cells property.

    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();
  10. Stream spreadsheet content in Symfony using StreamedResponse

    5.x

    To 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 standard Response, you use a callback function within StreamedResponse to echo content chunks as they are processed by OpenSpout.

    When reading a file, use OpenSpout\Reader\XLSX\Reader to iterate through sheets and rows. To ensure the browser receives data incrementally, use flush() 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;
        }
    }
  11. Upgrade from `box/spout:v3` to `openspout/openspout:v3`

    5.x

    To migrate from the original box/spout library to the OpenSpout fork, follow these two steps:

    1. Update your composer.json to replace box/spout with openspout/openspout.
    2. Perform a global find-and-replace in your codebase to change the namespace Box\Spout to OpenSpout.
  12. Configure the fallback style for a Writer

    5.x

    By default, OpenSpout uses a standard style for all created rows. You can override this global default by passing a custom Style object into the Options class when instantiating a Writer.

    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);