FastExcelReader: a fast, low-memory Excel and CSV reader for PHP

aVadim 20.03.2026 00:00

If you need to read data out of an Excel file in PHP, the first thing a search engine suggests is PhpSpreadsheet. It is a powerful library, but it pays for that versatility in speed and memory: even small files eat hundreds of megabytes, and on large ones the library runs out of memory and dies. If your task is specifically to read data — import, integration, migration — rather than to edit a workbook, there is a sharper tool: avadim/fast-excel-reader. Let’s look at what it can do.

What this library is

FastExcelReader is part of the FastExcelPhp family (alongside FastExcelWriter for writing, FastExcelTemplator for templates and FastExcelLaravel for Laravel). Its philosophy: read-only, but very fast and memory-frugal. It runs on PHP 7.4+ and installs with a single command:

bash
composer require avadim/fast-excel-reader

The formats supported in 3.0.0 are XLSX (Office 2007+) and CSV.

The key architectural trait is streaming reads. The library does not build an in-memory object model of the workbook: the sheet XML is parsed node by node through XMLReader, and rows are yielded by a generator. At any moment only the current row lives in memory, so a file with 500,000 rows needs memory of the same order as a file with 5,000.

Quick start

php
use avadim\FastExcelReader\Excel;

$excel = Excel::open('report.xlsx');

// the whole sheet as an array (for small files)
$rows = $excel->readRows();

// or row by row through a generator (for files of any size)
$sheet = $excel->sheet();
foreach ($sheet->nextRow() as $rowNum => $rowData) {
    // $rowData = ['A' => ..., 'B' => ...]
}

Multiple sheets? You can pick one by name or iterate over all of them:

php
$excel->getSheetNames();          // names of all sheets
$excel->selectSheet('Prices');    // select a sheet by name

Convenient result keys

Column letters as keys are awkward to work with. The library can turn the first row into the keys of associative arrays:

php
// the first row of the sheet becomes the headers
$rows = $excel->sheet()->withHeader()->readRows();
// [2 => ['SKU' => 'A-100', 'Price' => 990], 3 => [...], ...]

You can also supply your own keys per column and control row/column numbering with the KEYS_* flags:

php
$rows = $excel->readRows(['A' => 'sku', 'B' => 'price'], Excel::KEYS_FIRST_ROW | Excel::KEYS_ROW_ZERO_BASED);

Empty rows and cells are skipped by default, and the TRIM_STRINGS and TREAT_EMPTY_STRING_AS_EMPTY_CELL flags let you treat whitespace-only cells as empty too. Note that empty cells come back as null, not as an empty string.

Read areas

Real-world files rarely start at A1: there is a decorative header on top and the data sits somewhere in the middle of the sheet. The read area is set explicitly — as a range or as a named area of the workbook:

php
$excel->selectSheet('Demo1')
    ->setReadArea('B4:D11', true)   // true — the first row of the area holds the headers
    ->readRows();

$excel->setReadArea('Values');      // a defined name from the workbook
$cells = $excel->readCells();

Dates: no magic numbers

Excel stores dates as serial numbers (for example, 45123), and naive readers hand them back exactly like that. FastExcelReader recognizes dates automatically from the numeric cell formats, and the output format is configurable:

php
$excel->setDateFormat('Y-m-d');
// a date cell comes back as '2023-10-05', not 45123

Not just values: styles, images, merged cells

A rarity for fast readers — FastExcelReader reads formatting as well:

php
// values together with styles: ['v' => value, 's' => styles, 'f' => formula]
$rows = $sheet->readRowsWithStyles();

// styles only: fonts, fills, borders, formats
$styles = $sheet->readCellStyles();

This lets you, say, skip rows a manager marked in red, or tell a subtotal (bold font) from an ordinary row.

Merged cells are a common cause of “holes” on import (the value is stored only in the first cell of the range). The library exposes everything about them:

php
$sheet->getMergedCells();   // all ranges: ['B3:B6', ...]
$sheet->isMerged('B4');     // true
$sheet->mergedRange('B4');  // 'B3:B6'

And — the cherry on top — extracting images from XLSX. A price list with product photos stops being a problem:

php
if ($sheet->hasImage('C7')) {
    $sheet->saveImageTo('C7', $dir);   // save the cell’s image into a directory
}
$sheet->getImageBlob('C7');            // or get it as binary

CSV: the same API, the same features

Version 3.0.0 reads CSV too — not through a wrapper around fgetcsv(), but with its own parser:

  • automatic detection of the delimiter (comma, semicolon, tab) and of the encoding (UTF-8/16/32, windows-1251, KOI8-R, Shift_JIS and others) — the output is always UTF-8;
  • full RFC 4180 support: quotes, escaping, multi-line fields, BOM;
  • strict mode (by the book) and tolerant mode (get as much as possible out of a malformed file);
  • skipping empty rows, comment lines, trimming whitespace.
php
$csv = Excel::openCsv('legacy-export.csv', ['encoding' => 'Windows-1252', 'delimiter' => ';']);

foreach ($csv->withHeader()->nextRow() as $row) {
    echo $row['Name'];
}

The API deliberately mirrors the XLSX reader: nextRow(), readRows() and withHeader() work the same way. An “accept both Excel and CSV” import is written as a single branch of code.

What the library does not do

Honest limits of applicability:

  • it does not write or edit files — FastExcelWriter is there for that;
  • it does not evaluate formulas — it returns the formula text and the last cached value;
  • the XLS format (Excel 97–2003) is not supported in 3.0.0 — only XLSX and CSV.

Who should take a look

FastExcelReader 3.0.0 is the natural choice when the task sounds like “read tabular data and process it”: importing price lists and catalogs, ingesting user-uploaded files, integrations and migrations. You get the speed and near-constant memory of a streaming reader — while keeping access to the dates, styles, merged cells and images that usually force you to reach for a heavy general-purpose library.

bash
composer require avadim/fast-excel-reader

Documentation and examples are in the GitHub repository.

Comments (0)