Skip to content
Search

Excel Adapter

Flow PHP's Adapter Excel is a comprehensive library engineered to enable smooth interactions with Excel within your ETL (Extract, Transform, Load) workflows. This adapter is indispensable for developers looking to seamlessly extract from or load data into Excel, ensuring a coherent and reliable data transformation journey. By leveraging the Adapter Excel library, developers can utilize a robust set of features designed for precise interaction with Excel, simplifying complex data transformations and enhancing data processing efficiency. The Adapter Excel library encapsulates a wide range of functionalities, providing a streamlined API for managing Excel tasks, which is essential in modern data processing and transformation scenarios. This library reflects Flow PHP's dedication to offering versatile and effective data processing solutions, making it an optimal choice for developers dealing with Excel in large-scale and data-intensive projects. With Flow PHP's Adapter Excel, managing Excel data within your ETL workflows becomes a more simplified and efficient task, perfectly aligning with the robust and adaptable nature of the Flow PHP ecosystem.

Note: This adapter only supports local filesystem paths due to limitations of the underlying OpenSpout library. Remote filesystems (S3, Azure Blob Storage, etc.) are not supported.

Installation

For detailed installation instructions, see the installation page.

Extractor

<?php

$rows = data_frame()
    ->read(from_excel('path/to/your/excel.xlsx'))
    ->fetch();
<?php

$rows = data_frame()
    ->read(from_excel('path/to/your/excel.ods'))
    ->fetch();
<?php

$rows = data_frame()
    ->read(
        from_excel('path/to/your/excel.xlsx')
            ->withSheetName('Sheet name')
    )
    ->fetch();
<?php

$rows = data_frame()
    ->read(
        from_excel('path/to/your/excel.xlsx')
            ->withHeader(false)
            ->withOffset(5)
    )
    ->fetch();

Row numbers count from 1 and include the header row, so ->withOffset(5) on a sheet with a header skips rows 2-4 and starts at row 5.

Schema

Without a declared schema, from_excel() samples the workbook before the first row is read, decides one schema, and every batch it yields carries exactly that schema. schema() answers without running the pipeline:

<?php

from_excel('path/to/your/excel.xlsx')->schema();

The default sample is the first 20 480 rows of up to 10 files. Every inferred column is nullable, and where narrowing is not safe the column floors to string.

A workbook has its own cell types, so inference reads those first: a numeric cell is integer or float, a boolean cell is boolean, a date-formatted cell is date or datetime. A cell holding text stays string even when the text looks like something else - TRUE, 12.9 and 2023-10-02 are all text a spreadsheet author chose not to type.

The exception is types a cell cannot hold at all. There is no uuid, json or timezone cell, so text is the only way to write one and text is narrowed for exactly those three:

254d61c5-22c8-4407-83a2-76f1cab53af2   ->  uuid
{"street":"Main St"}                   ->  json
America/New_York                       ->  timezone
TRUE / 12.9 / 2023-10-02               ->  string

Tune the sample, or floor it:

<?php

$rows = data_frame()
    ->read(
        from_excel('path/to/your/*.xlsx')
            ->inferSchema(infer_schema()->sampleSize(1_000)->filesToSniff(1)->allStrings())
    )
    ->fetch();

Reading a glob whose files carry different headers raises InferredSchemaException, naming the file that diverged and the file the schema came from. Declare the schema with ->withSchema(...), or read the files as one wider schema with ->inferSchema(infer_schema()->unionByName()).

Declare the schema instead whenever it is known - a declared schema is never sampled for:

<?php

$rows = data_frame()
    ->read(
        from_excel('path/to/your/excel.xlsx')
            ->withSchema(schema(int_schema('id'), string_schema('name')))
    )
    ->fetch();

Loader

Basic Usage

<?php

data_frame()
    ->read($extractor)
    ->write(to_excel('path/to/output.xlsx'))
    ->run();

ODS Format

<?php

use Flow\ETL\Adapter\Excel\ExcelWriter;

data_frame()
    ->read($extractor)
    ->write(to_excel('path/to/output.ods')->withWriter(ExcelWriter::ODS))
    ->run();

Custom Sheet Name

<?php

data_frame()
    ->read($extractor)
    ->write(to_excel('path/to/output.xlsx')->withSheetName('MyData'))
    ->run();

Dynamic Sheet Names from Entry

Route rows to different sheets based on entry values:

<?php

$loader = to_excel('path/to/output.xlsx')
    ->withSheetNameFromEntry('category');

data_frame()
    ->read($extractor)
    ->write($loader)
    ->run();

Without Header Row

<?php

data_frame()
    ->read($extractor)
    ->write(to_excel('path/to/output.xlsx')->withHeader(false))
    ->run();

Header Styling

<?php

use OpenSpout\Common\Entity\Style\Style;

$headerStyle = new Style(fontBold: true);

$loader = to_excel('path/to/output.xlsx')
    ->withHeaderStyle($headerStyle);

data_frame()
    ->read($extractor)
    ->write($loader)
    ->run();

Cell Styling

Apply custom styles to individual cells based on their value and the column they belong to:

<?php

use Flow\ETL\Adapter\Excel\CellStyler;
use Flow\ETL\Schema\Definition;
use OpenSpout\Common\Entity\Style\Style;

$cellStyler = new class implements CellStyler {
    public function style(
        mixed $value,
        Definition $definition,
        int $rowNumber,
        int $columnIndex,
        string $sheetName,
    ): ?Style {
        // Make first column bold
        if ($columnIndex === 0) {
            return new Style(fontBold: true);
        }

        return null;
    }
};

$loader = to_excel('path/to/output.xlsx')
    ->withCellStyler($cellStyler);

data_frame()
    ->read($extractor)
    ->write($loader)
    ->run();

Custom Writer Options

For advanced configuration, pass OpenSpout options directly:

<?php

use OpenSpout\Writer\XLSX\Options as XlsxOptions;

$options = new XlsxOptions(
    SHOULD_USE_INLINE_STRINGS: false,
    DEFAULT_COLUMN_WIDTH: 15.0,
    DEFAULT_ROW_HEIGHT: 20.0,
);

data_frame()
    ->read($extractor)
    ->write(to_excel('path/to/output.xlsx')->withWriterOptions($options))
    ->run();

For ODS format:

<?php

use OpenSpout\Writer\ODS\Options as OdsOptions;

$options = new OdsOptions(
    DEFAULT_COLUMN_WIDTH: 15.0,
    DEFAULT_ROW_HEIGHT: 20.0,
);

data_frame()
    ->read($extractor)
    ->write(to_excel('path/to/output.ods')->withWriterOptions($options))
    ->run();

Custom Date/Time Formats

Dates and datetimes are written as real date cells, so they read back as date / datetime rather than as text. withDateFormat() and withDateTimeFormat() take an Excel number format - they control how the cell is displayed, never the value. withTimeFormat() still takes a PHP DateInterval::format() string, because a workbook has no cell type that reads back as time.

<?php

data_frame()
    ->read($extractor)
    ->write(
        to_excel('path/to/output.xlsx')
            ->withDateFormat('dd/mm/yyyy')
            ->withDateTimeFormat('dd/mm/yyyy hh:mm')
            ->withTimeFormat('%H:%I')
    )
    ->run();

Default formats:

  • Date: yyyy-mm-dd (Excel number format)
  • DateTime: yyyy-mm-dd hh:mm:ss (Excel number format)
  • Time: %H:%I:%S (PHP DateInterval::format())

Found a typo or an outdated section? Edit this page on GitHub


Contributors

Built in the open.

Join us on GitHub
scroll back to top