PackageTrack
Sign in Get early access

avadim/fast-excel-reader

Lightweight and very fast XLSX Excel Spreadsheet and CSV Reader in PHP

v4.4.2 822K downloads/mo #3787 most downloaded on Packagist aVadim483/fast-excel-reader

What this package is like to depend on

Last release 6 days ago

17 Aug 2026

Ships fairly regularly

a new release about every 4 weeks

Rarely documented

notes for 10 of 85 stable releases

Nothing withdrawn

no release was ever pulled

6 years old

85 releases · first in 2020

21 releases in the last 12 months

see the full history below

Release timeline

84 releases · Nov 2020 to Aug 2026
2021 2022 2023 2024 2025 2026
Release Pre-release

Releases

latest 60 of 85
  1. v4.4.2 17 Aug 2026
    Release notes

    Fixed

    • A number format the file declares itself is no longer mistaken for the builtin format its
      index stands for. The format numbers 14-22, 27-36, 45-47, 50-58 and 71-81 are reserved for
      builtin date formats, several of them localized, so a cell using one carries no format code
      of its own. But a file may register a code for such a number - XLS writers do it routinely,
      1C among them, and a workbook converted from XLS keeps doing it in XLSX - and it has then
      redefined that number. The reader typed such cells by the number alone, so an article number
      like 043, stored as 43 under the custom pattern 000, came back as a timestamp of
      1900-02-12 with no error raised. The declared pattern now decides, for the cell value as well
      as for format-category of the cell style; a number used without a declaration still means
      the builtin format it always did.
    Open source →
    Release notes

    Fixed

    • A number format the file declares itself is no longer mistaken for the builtin format its index stands for. The format numbers 14-22, 27-36, 45-47, 50-58 and 71-81 are reserved for builtin date formats, several of them localized, so a cell using one carries no format code of its own. But a file may register a code for such a number - XLS writers do it routinely, 1C among them, and a workbook converted from XLS keeps doing it in XLSX - and it has then redefined that number. The reader typed such cells by the number alone, so an article number like 043, stored as 43 under the custom pattern 000, came back as a timestamp of 1900-02-12 with no error raised. The declared pattern now decides, for the cell value as well as for format-category of the cell style; a number used without a declaration still means the builtin format it always did.
    Open source →
  2. v4.4.1 16 Aug 2026
    Release notes

    Fixed

    • Characters stored as _xHHHH_ are decoded back to the character they stand for. XML cannot carry control characters (and normalizes CR), so a spreadsheet keeps them escaped — a cell holding a CR is stored as _x000D_, and Excel as well as the sibling fast-excel-writer writes it that way. The reader used to return the literal text _x000D_, so such a value did not survive a write/read round trip. Text that literally reads _xHHHH_ is stored as _x005F_xHHHH_ and still comes back unchanged; both forms are resolved in a single pass, so a decoded value is never decoded twice. Requires avadim/fast-excel-helper 1.4 or above.
    Open source →
  3. v4.4.0 08 Aug 2026
    Release notes

    Added

    • Opening a workbook from a string or a stream. Excel::openString($content) reads a workbook
      held in memory (a database BLOB, an HTTP body, an S3/Flysystem read), and Excel::openStream($stream)
      reads it from an open stream resource (a URL via fopen(), php://memory, a Flysystem read stream).
      Both pick the reader by signature exactly like open(), so a string/stream carrying XLSX, XLS or CSV
      reads back identically to the same file on disk, and both accept the same $options as open().
      The content is copied to a temporary file (honouring Excel::setTempDir()), which is removed on
      script shutdown; the stream passed to openStream() is not closed. See
      docs/10-getting-started.md.
    • Reading cell hyperlinks. Sheet::getHyperlinks() returns a sheet's hyperlinks keyed by the
      cell (or range) they are anchored to, each as ['link' => ..., 'location' => ..., 'display' => ..., 'tooltip' => ...]. External targets are resolved from the sheet relationships file; internal links
      expose their in-document location. A sheet without hyperlinks returns an empty array. See
      docs/16-sheet-metadata.md.
    • Reading workbook document properties. Excel::getProperties() returns the core
      (docProps/core.xml) and application (docProps/app.xml) properties as one associative array with
      normalised keys — creator, lastModifiedBy, created, modified, title, application,
      company, ... Only the properties present in the file are returned; a workbook without a docProps
      part returns an empty array. See docs/12-advanced-reading.md.
    Open source →
    Release notes

    Added

    • Opening a workbook from a string or a stream. Excel::openString($content) reads a workbook held in memory (a database BLOB, an HTTP body, an S3/Flysystem read), and Excel::openStream($stream) reads it from an open stream resource (a URL via fopen(), php://memory, a Flysystem read stream). Both pick the reader by signature exactly like open(), so a string/stream carrying XLSX, XLS or CSV reads back identically to the same file on disk, and both accept the same $options as open(). The content is copied to a temporary file (honouring Excel::setTempDir()), which is removed on script shutdown; the stream passed to openStream() is not closed. See docs/10-getting-started.md.
    • Reading cell hyperlinks. Sheet::getHyperlinks() returns a sheet's hyperlinks keyed by the cell (or range) they are anchored to, each as ['link' => ..., 'location' => ..., 'display' => ..., 'tooltip' => ...]. External targets are resolved from the sheet relationships file; internal links expose their in-document location. A sheet without hyperlinks returns an empty array. See docs/16-sheet-metadata.md.
    • Reading workbook document properties. Excel::getProperties() returns the core (docProps/core.xml) and application (docProps/app.xml) properties as one associative array with normalised keys — creator, lastModifiedBy, created, modified, title, application, company, ... Only the properties present in the file are returned; a workbook without a docProps part returns an empty array. See docs/12-advanced-reading.md.
    Open source →
  4. v4.3.0 26 Jul 2026
    Release notes

    Changed

    • An empty (zero-byte) file passed to Excel::open() now fails up front with a clear "File ... is empty" error, instead of being silently opened as a zero-row CSV — open() guesses the format by signature and an empty file matches none. Excel::openCsv() is unchanged and still reads an empty file as a valid, zero-row CSV.
    Open source →
    Release notes

    Changed

    • An empty (zero-byte) file passed to Excel::open() now fails up front with a clear "File ... is empty" error, instead of being silently opened as a zero-row CSV — open() guesses the format by signature and an empty file matches none. Excel::openCsv() is unchanged and still reads an empty file as a valid, zero-row CSV.
    Open source →
  5. v4.2.0 26 Jul 2026
    Release notes

    Added

    • Reading CSV through Excel::open() — returns a one-sheet Csv\CsvBook exposing the same reading API as XLSX/XLS (key modes, read areas, withHeader(), readColumns(), ...). Format is chosen by signature (OLE2 → XLS, ZIP → XLSX, otherwise CSV); pass Excel::open($file, $options) to configure the CSV reader or ['format' => 'csv'] to force it. openCsv() is unchanged and still returns the low-level Csv\CsvReader (also via CsvBook::getReader()). CSV has no styles/dates/merged cells/images — those accessors return empty results. See docs/20-csv.md.
    • Excel::isXlsx() — ZIP-signature check (PK\x03\x04).
    • CSV binary guard — binary input (a NUL byte, or a high share of control characters) is rejected up front with a clear "file appears to be binary" error; UTF-16/UTF-32 text is exempt. New allow_binary option (CsvOptions::setAllowBinary()) turns it off.

    Changed

    • Excel::open(string $file, $options = []) takes an optional second argument (BC-safe); a file that is neither OLE2 nor ZIP is now read as CSV instead of being pushed into the XLSX reader.
    • A ZIP that is not an XLSX (a DOCX/PPTX or a plain archive) now fails with "Not an XLSX workbook: the ZIP archive has no xl/workbook.xml" (with a DOCX/PPTX hint) instead of a cryptic "Internal file not found".

    Fixed

    • CSV: an empty file (or one whose length is a multiple of the read buffer) no longer emits a PHP warning and a phantom trailing row.
    • CSV: CsvReader::rewind() now fully resets the read state, so re-reading after a partial pass no longer replays a stale buffer.

    Full bilingual changelog: CHANGELOG.md · CHANGELOG.ru.md

    Open source →
    Release notes

    Added

    • Reading CSV through Excel::open(). open() now opens CSV files too, returning a Csv\CsvBook — a normal one-sheet workbook that exposes the same AbstractSheet reading API (key modes, read areas, withHeader(), readColumns(), ...) as XLSX and XLS. Format is chosen by signature (OLE2 → XLS, ZIP → XLSX, otherwise CSV); pass Excel::open($file, $options) to configure the CSV reader or force it with ['format' => 'csv']. Through open() the default column keys are Excel letters, for parity with XLSX. openCsv() is unchanged and still returns the low-level Csv\CsvReader engine (also reachable via CsvBook::getReader()). CSV carries no styles, number/date typing, merged cells or images; those accessors return empty results instead of throwing. See docs/20-csv.md.
    • Excel::isXlsx() — signature check (PK\x03\x04) telling a real XLSX/ZIP package apart from plain text, which open() then reads as CSV.
    • CSV: binary input (a NUL byte, or a high share of control characters) is now rejected up front with a clear "file appears to be binary" error instead of failing deep in the parser. UTF-16/UTF-32 text is exempt. The new allow_binary option (CsvOptions::setAllowBinary()) turns the guard off.

    Changed

    • Excel::open(string $file, $options = []) takes an optional second argument (BC-safe). A file that is neither an OLE2 nor a ZIP container is now read as CSV instead of being pushed into the XLSX reader (where it used to fail) — a fix in behaviour for non-spreadsheet input.
    • A ZIP that is not an XLSX (a DOCX/PPTX, or a plain archive) now fails with a clear "Not an XLSX workbook: the ZIP archive has no xl/workbook.xml" message — with a DOCX/PPTX hint — instead of the cryptic "Internal file not found: xl/_rels/workbook.xml.rels".

    Fixed

    • CSV: an empty file (or one whose length is an exact multiple of the read buffer) no longer emits a PHP warning and a phantom trailing row — it is now correctly read as zero rows.
    • CSV: CsvReader::rewind() now fully resets the read state, so re-reading a file after a partial pass no longer replays a stale buffer.
    Open source →
  6. v4.1.0 25 Jul 2026
    Release notes

    Fixed

    • Built-in date formats (numFmtId 14-22, such as the "short date" code 14) are now rendered
      deterministically. Previously the reader overwrote their patterns from the ambient ICU locale
      whenever ext-intl was loaded, so the same file produced different strings depending on the
      server locale and on whether the extension was installed (#53).
      These codes now resolve to fixed patterns regardless of environment. Formats that a file spells
      out explicitly are unaffected.

    Added

    • useLocaleFormats(?string $locale = null) — opt in to locale-dependent rendering of the built-in
      date codes (the previous behaviour, now explicit). Pass a locale for reproducible output, or nothing
      to use the process default locale. Requires ext-intl.

    Full changelog: CHANGELOG.md · по-русски

    Open source →
    Release notes

    Fixed

    • Built-in date formats (numFmtId 14-22, such as the "short date" code 14) are now rendered deterministically. Previously the reader overwrote their patterns from the ambient ICU locale whenever ext-intl was loaded, so the same file produced different strings depending on the server locale and on whether the extension was installed (#53). These codes now resolve to fixed patterns regardless of environment. Formats that a file spells out explicitly are unaffected.

    Added

    • useLocaleFormats(?string $locale = null) — opt in to locale-dependent rendering of the built-in date codes (the previous behaviour, now explicit). Pass a locale for reproducible output, or nothing to use the process default locale. Requires ext-intl.
    Open source →
  7. v4.0.1 23 Jul 2026
    Release notes

    Fixed

    • XLS now reports formula text with the leading =, like XLSX always has. The f field of a formula
      cell has the same shape whichever format the file came from, so code no longer needs to special-case
      XLS. Formulas whose text cannot be recovered still report null, not "=".

    Full changelog: CHANGELOG.md · по-русски

    Open source →
    Release notes

    Fixed

    • XLS now reports formula text with the leading =, like XLSX always has. The f field of a formula cell has the same shape whichever format the file came from, so code no longer needs to special-case XLS. Formulas whose text cannot be recovered still report null, not "=".
    Open source →
  8. v4.0.0 22 Jul 2026
    Release notes

    Added

    • Reading of legacy XLS workbooks (Excel 97-2003, BIFF8). Excel::open() now chooses the reader
      from the file signature, so XLSX and XLS are opened the same way; the file extension is never
      consulted. Excel::openXls() opens a workbook explicitly and Excel::isXls() exposes the test.
      See docs/21-xls.md.
      • Values and types, with dates detected through number formats
      • Cell styles: fonts, fills, borders, alignment, number formats and palette colours
      • Formula text, including shared formulas
      • Embedded images
      • Multiple worksheets, hidden and very hidden sheets, merged cells, sheet dimensions
      • Encrypted workbooks and BIFF5/BIFF7 files are rejected with a message saying which
    • AbstractBook and AbstractSheet, holding the format-independent half of the readers: read areas,
      key modes, result-mode flags, the row generator and every read* helper. XLSX and XLS share one
      implementation of the whole public reading API.
    • withHeader() now accepts an optional list of column names: withHeader(['name', 'birthday']). The
      header row is still skipped, but the names come from the list instead of from its values. Names are
      positional - the first name goes to the first column of the read area - so no column letters are
      involved and the same call works on a sheet whose data does not start at A1. A shorter list renames
      only the columns it covers. Supported for XLSX, XLS and CSV. Calling it with no argument is
      unchanged. This mirrors the naming of writeHeader() in the sibling fast-excel-writer.

    Fixed

    • Sheet::readCellsWithStylesFrom() returned bare cell values instead of values with styles: it called
      readCells() rather than readCellsWithStyles(), and passed the style key into a bool parameter.
    • Sheet::readCellsWithStyles($styleKey) never narrowed the result to the requested property. The key
      was looked up on the nested style, where properties sit inside their group, so 'fill-color' - the
      example in the method's own docblock - always returned the complete style instead.
    • Complete cell styles - getCompleteStyleByIdx(), readCellsWithStyles() and everything built on
      them - died with Call to undefined method DOMText::getAttribute() on workbooks whose styles.xml
      is written with indentation, which several writers do.

    Changed

    • Reading XLSX is about 1.5 times faster. Values, types and peak memory are unchanged.
    • The accessors that returned a concrete Sheet now return AbstractSheet, and the fluent setters on
      the workbook return AbstractBook. The objects handed back are unchanged, and a subclass may still
      narrow the return type back, so this only affects explicit type declarations in calling code.

    Full changelog: CHANGELOG.md · по-русски

    Open source →
    Release notes

    Added

    • Reading of legacy XLS workbooks (Excel 97-2003, BIFF8). Excel::open() now chooses the reader from the file signature, so XLSX and XLS are opened the same way; the file extension is never consulted. Excel::openXls() opens a workbook explicitly and Excel::isXls() exposes the test. See docs/21-xls.md.
      • Values and types, with dates detected through number formats
      • Cell styles: fonts, fills, borders, alignment, number formats and palette colours
      • Formula text, including shared formulas
      • Embedded images
      • Multiple worksheets, hidden and very hidden sheets, merged cells, sheet dimensions
      • Encrypted workbooks and BIFF5/BIFF7 files are rejected with a message saying which
    • AbstractBook and AbstractSheet, holding the format-independent half of the readers: read areas, key modes, result-mode flags, the row generator and every read* helper. XLSX and XLS share one implementation of the whole public reading API.
    • withHeader() now accepts an optional list of column names: withHeader(['name', 'birthday']). The header row is still skipped, but the names come from the list instead of from its values. Names are positional - the first name goes to the first column of the read area - so no column letters are involved and the same call works on a sheet whose data does not start at A1. A shorter list renames only the columns it covers. Supported for XLSX, XLS and CSV. Calling it with no argument is unchanged. This mirrors the naming of writeHeader() in the sibling fast-excel-writer.

    Fixed

    • Sheet::readCellsWithStylesFrom() returned bare cell values instead of values with styles: it called readCells() rather than readCellsWithStyles(), and passed the style key into a bool parameter.
    • Sheet::readCellsWithStyles($styleKey) never narrowed the result to the requested property. The key was looked up on the nested style, where properties sit inside their group, so 'fill-color' - the example in the method's own docblock - always returned the complete style instead.
    • Complete cell styles - getCompleteStyleByIdx(), readCellsWithStyles() and everything built on them - died with Call to undefined method DOMText::getAttribute() on workbooks whose styles.xml is written with indentation, which several writers do.

    Changed

    • Reading XLSX is about 1.5 times faster. Values, types and peak memory are unchanged.
    • The accessors that returned a concrete Sheet now return AbstractSheet, and the fluent setters on the workbook return AbstractBook. The objects handed back are unchanged, and a subclass may still narrow the return type back, so this only affects explicit type declarations in calling code.
    Open source →
  9. v3.2.0 21 Jul 2026
    Release notes

    Bug fixes

    Four defects in the sheet reading path, all in methods that had no test coverage.

    • Sheet::readFirstRowCellsFrom() always threw a TypeError. It forwarded $columnKeys into readFirstRowCells(?bool $styleIdxInclude), so even a default call failed. Because the result is keyed by cell address, column keys cannot apply at all — renaming a column would corrupt the address — so the parameter was dropped, mirroring readCellsFrom(). The new signature is readFirstRowCellsFrom(string $areaRange, ?bool $styleIdxInclude = null). No working call can break, since every call to the old signature threw.
    • Restricting columns while requesting numeric column keys returned only nulls. With a read area in place the row template was keyed by column letter while values were stored under numeric keys, and the values were then filtered out. Affected setReadArea() and setReadAreaColumns() combined with KEYS_COL_ZERO_BASED / KEYS_COL_ONE_BASED (and therefore KEYS_ZERO_BASED / KEYS_ONE_BASED). An explicit column name still takes precedence over the numeric key.
    • Sheet::rewind() discarded its $columnKeys argument, although it is documented as an alias of reset().
    • Sheet::firstCol() ignored the column bounds of the read area, reporting the first cell of the row as stored in the file. firstRow() was unaffected.

    Tests

    The suite grows from 84 to 262 tests (357 to 757 assertions), added ahead of the fixes as a regression net:

    • 105 characterization snapshots covering every read* method, the full KEYS_* matrix, result-mode flags, read areas, styles, dates, metadata and degenerate inputs, compared as whole arrays with assertSame.
    • Dedicated tests for generator semantics, read areas, result modes, merged cells, workbook delegation and streaming memory behaviour.
    • RESULT_MODE_ROW, TRIM_STRINGS, TREAT_EMPTY_STRING_AS_EMPTY_CELL and KEYS_RELATIVE had no coverage at all before and are now tested.

    Method coverage: Sheet 33.9% → 58.9%, Excel 55.6% → 69.4%.

    Notes

    • Reading XLSX files that use namespace-prefixed tags (<x:row>, <x:c>) still returns an empty result. This is a pre-existing limitation, now documented by a test rather than silently unnoticed.
    • API reference under docs/ regenerated.

    Full changelog: v3.1.0...v3.2.0

    Open source →
    Release notes

    Fixed

    Four defects, all in methods that had no test coverage.

    • Sheet::readFirstRowCellsFrom() always threw a TypeError: it forwarded $columnKeys into readFirstRowCells(?bool $styleIdxInclude), so even a default call failed. Because the result is keyed by cell address, column keys cannot apply, and the parameter was removed to match readCellsFrom(). No working call can break, since every call to the old signature threw.
    • Restricting columns while requesting numeric column keys returned only nulls. With a read area in place the row template was keyed by column letter while values were stored under numeric keys, and the values were then filtered out. Affected setReadArea() and setReadAreaColumns() combined with KEYS_COL_ZERO_BASED or KEYS_COL_ONE_BASED, and therefore also KEYS_ZERO_BASED and KEYS_ONE_BASED.
    • Sheet::rewind() discarded its $columnKeys argument although it is documented as an alias of reset(): the body assigned to the parameter instead of forwarding it.
    • Sheet::firstCol() ignored the column bounds of the read area, reporting the first cell of the row as stored in the file. firstRow() was unaffected.

    Added

    • A regression suite covering the reading path: characterization snapshots over every read* method, the full KEYS_* matrix, result-mode flags, read areas, styles, dates, metadata and degenerate inputs, plus targeted tests for generator semantics, read areas, merged cells and streaming memory behaviour. RESULT_MODE_ROW, TRIM_STRINGS, TREAT_EMPTY_STRING_AS_EMPTY_CELL and KEYS_RELATIVE had no coverage at all before.

    Known limitations

    • XLSX files that use namespace-prefixed tags (<x:row>, <x:c>) read back as empty.
    Open source →
  10. v3.1.0 16 Jul 2026
    Release notes

    Add stat() to Excel and Sheet, refactor countActualDimension() to str…

    …eaming

    - Sheet::stat() returns rows/cols/cell counts in a single streaming pass,
    cached in actualRows/actualCols/cellStat
    - Excel::stat() aggregates per-sheet stats with workbook totals
    - Rewrite countActualDimension() to a memory-bounded streaming scan that
    glues tags across block boundaries and finds the last row via a tail window
    - Document the full-scan cost in related methods' PHPDoc
    - Add XlsxStatTest covering sheet/workbook stat and consistency with readCells

    Open source →
  11. v3.0.3 14 Jul 2026

    Nothing published for this version

  12. v3.0.2 22 May 2026

    Nothing published for this version

  13. v3.0.1 12 Mar 2026

    Nothing published for this version

  14. v3.0.0 14 Feb 2026

    Nothing published for this version

  15. v2.30.0 24 Jan 2026

    Nothing published for this version

  16. v2.29.1 23 Jan 2026

    Nothing published for this version

  17. v2.29.0 18 Jan 2026

    Nothing published for this version

  18. v2.28.1 14 Dec 2025

    Nothing published for this version

  19. v2.28.0 07 Nov 2025

    Nothing published for this version

  20. v2.27.0 21 Sep 2025

    Nothing published for this version

  21. v2.26.2 08 Sep 2025

    Nothing published for this version

  22. v2.26.1 no date

    Nothing published for this version

  23. v2.26.0 01 Jun 2025

    Nothing published for this version

  24. v2.25.1 03 May 2025

    Nothing published for this version

  25. v2.25.0 16 Apr 2025

    Nothing published for this version

  26. v2.24.0 27 Feb 2025

    Nothing published for this version

  27. v2.23.0 06 Feb 2025

    Nothing published for this version

  28. v2.22.1 21 Dec 2024

    Nothing published for this version

  29. v2.22.0 30 Nov 2024

    Nothing published for this version

  30. v2.21.1 01 Nov 2024

    Nothing published for this version

  31. v2.21.0 27 Oct 2024

    Nothing published for this version

  32. v2.20.0 12 Oct 2024

    Nothing published for this version

  33. v2.19.0 22 Sep 2024

    Nothing published for this version

  34. v2.18.2 15 Sep 2024

    Nothing published for this version

  35. v2.18.1 28 Aug 2024

    Nothing published for this version

  36. v2.18.0 11 Aug 2024

    Nothing published for this version

  37. v2.17.1 29 Jun 2024

    Nothing published for this version

  38. v2.17.0 17 Jun 2024

    Nothing published for this version

  39. v2.16.0 07 Jun 2024

    Nothing published for this version

  40. v2.15.5 21 May 2024

    Nothing published for this version

  41. v2.15.4 11 Apr 2024

    Nothing published for this version

  42. v2.15.3 10 Apr 2024

    Nothing published for this version

  43. v2.15.2 10 Apr 2024

    Nothing published for this version

  44. v2.15.1 07 Apr 2024

    Nothing published for this version

  45. v2.15.0 06 Apr 2024

    Nothing published for this version

  46. v2.14.2 07 Mar 2024

    Nothing published for this version

  47. v2.14.1 05 Mar 2024

    Nothing published for this version

  48. v2.14.0 07 Dec 2023

    Nothing published for this version

  49. v2.12.0 03 Nov 2023

    Nothing published for this version

  50. v2.11.0 25 Oct 2023

    Nothing published for this version

  51. v2.10.0 29 Sep 2023

    Nothing published for this version

  52. v2.9.2 09 Sep 2023

    Nothing published for this version

  53. v2.9.1 06 Sep 2023

    Nothing published for this version

  54. v2.9.0 06 Sep 2023

    Nothing published for this version

  55. v2.8.4 29 Aug 2023

    Nothing published for this version

  56. v2.8.3 29 Aug 2023

    Nothing published for this version

  57. v2.8.2 28 Aug 2023

    Nothing published for this version

  58. v2.8.1 25 Aug 2023

    Nothing published for this version

  59. v2.8.0 24 Aug 2023

    Nothing published for this version

  60. v2.7.0 11 Aug 2023

    Nothing published for this version

Every package, every release, already written down.

The archive is open and free. Watching your own project is what we are building next.

Browse the archive