Luke Anderson PHP

How to read excel sheet using php

Reading Excel files in PHP comes up constantly in real projects — importing client data, processing accounting exports, building admin dashboards, or migrating spreadsheets to a database. Unlike CSV files, Excel files are binary (XLS) or zipped XML (XLSX) formats, so you can’t just open them with fopen(). You need a proper library.

This guide shows you how to read Excel files in PHP the modern, recommended way using PhpSpreadsheet — the actively maintained successor to the old PHPExcel library. By the end, you’ll know how to read XLSX and XLS files, grab specific cells, handle multiple worksheets, work with large files without crashing, and deal with the common quirks like merged cells and Excel date numbers.

Which PHP Library Should You Use to Read Excel?

There are a few options, but only one is genuinely recommended in 2026:

Library Status Recommended?
PhpSpreadsheet Actively maintained ✅ Yes — use this
PHPExcel Abandoned in 2017 ❌ No — don’t use
Box/Spout Archived in 2022 ❌ No — unmaintained
OpenSpout Active fork of Spout ✅ Good for large-file streaming
simplexlsx Active, lightweight ✅ OK for simple read-only use

PhpSpreadsheet is the go-to library — it reads and writes XLSX, XLS, CSV, ODS, and several other formats, and it’s used by virtually every major PHP framework and CMS that touches Excel. We’ll focus on it here.

Step 1: Install PhpSpreadsheet via Composer

You need Composer (PHP’s package manager) installed. From your project root:

bash
composer require phpoffice/phpspreadsheet

This adds the library and all its dependencies. PhpSpreadsheet requires PHP 8.1 or newer as of the current version.

Then include Composer’s autoloader at the top of your PHP file:

php
<?php
require 'vendor/autoload.php';

use PhpOffice\PhpSpreadsheet\IOFactory;

Step 2: Basic Example — Read an Excel File Into an Array

The simplest, most common task: load an XLSX file and convert it to a PHP array.

php
<?php
require 'vendor/autoload.php';

use PhpOffice\PhpSpreadsheet\IOFactory;

$filePath = 'data.xlsx';

// Load the spreadsheet (auto-detects XLSX, XLS, CSV, ODS, etc.)
$spreadsheet = IOFactory::load($filePath);

// Get the first worksheet
$worksheet = $spreadsheet->getActiveSheet();

// Convert all cells to a 2D array
$rows = $worksheet->toArray(null, true, true, true);

foreach ($rows as $row) {
    print_r($row);
}

What’s happening:

  1. IOFactory::load($filePath) auto-detects the file format and returns a Spreadsheet object.
  2. getActiveSheet() returns the currently selected worksheet (usually the first one).
  3. toArray(null, true, true, true) converts the whole sheet to a 2D array. The four parameters mean: null value placeholder, calculate formulas, format numbers/dates, return column letters as array keys.

Step 3: A Production-Ready Reader with Headers

The basic version returns numbered rows and letter-keyed columns (A, B, C). For real use, you want associative arrays keyed by your header row.

php
<?php
require 'vendor/autoload.php';

use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;

function readExcelToArray(string $filePath, int $sheetIndex = 0): array {
    if (!file_exists($filePath) || !is_readable($filePath)) {
        throw new RuntimeException("File not found or unreadable: {$filePath}");
    }

    try {
        $spreadsheet = IOFactory::load($filePath);
    } catch (ReaderException $e) {
        throw new RuntimeException("Could not read Excel file: " . $e->getMessage());
    }

    $worksheet = $spreadsheet->getSheet($sheetIndex);
    $rows = $worksheet->toArray(null, true, true, false);

    if (empty($rows)) {
        return [];
    }

    // First row = headers
    $headers = array_map('trim', array_shift($rows));

    $data = [];
    foreach ($rows as $row) {
        // Skip fully empty rows
        if (count(array_filter($row, fn($v) => $v !== null && $v !== '')) === 0) {
            continue;
        }
        $data[] = array_combine($headers, $row);
    }

    return $data;
}

// Usage
$users = readExcelToArray('users.xlsx');
foreach ($users as $user) {
    echo $user['Name'] . ' - ' . $user['Email'] . PHP_EOL;
}

Why this version is better:

  • Returns associative arrays so you can access fields by header name ($user['Email']) instead of $user['B'].
  • Catches reader exceptions so corrupted files don’t crash your script.
  • Skips empty rows that Excel often leaves at the bottom of sheets.
  • Trims headers so accidental whitespace doesn’t break your keys.
  • Accepts a sheet index so you can read any worksheet, not just the first.

Step 4: Reading Specific Cells, Rows, and Ranges

Sometimes you don’t need the whole sheet — just a few cells or a specific range:

php
$worksheet = $spreadsheet->getActiveSheet();

// Read a single cell by coordinate
$name = $worksheet->getCell('B2')->getValue();

// Read a single cell by row/column index (column 2 = B, row 2)
$email = $worksheet->getCell([2, 2])->getValue();

// Read calculated value of a formula cell
$total = $worksheet->getCell('D10')->getCalculatedValue();

// Read a specific range as an array
$rangeData = $worksheet->rangeToArray('A2:D20', null, true, true, false);

// Get highest used row and column
$lastRow = $worksheet->getHighestRow();
$lastCol = $worksheet->getHighestColumn();

echo "Sheet has {$lastRow} rows and goes up to column {$lastCol}\n";

The difference between getValue() and getCalculatedValue() matters: getValue() returns the raw formula like =SUM(A1:A10), while getCalculatedValue() returns the result (e.g. 42).

Step 5: Working With Multiple Worksheets

Excel files often have multiple sheets (tabs at the bottom). Here’s how to handle them:

php
$spreadsheet = IOFactory::load('multi-sheet.xlsx');

// Get list of all sheet names
$sheetNames = $spreadsheet->getSheetNames();
print_r($sheetNames);
// e.g. ['Customers', 'Orders', 'Products']

// Loop through all sheets
foreach ($spreadsheet->getAllSheets() as $sheet) {
    echo "Sheet: " . $sheet->getTitle() . "\n";
    echo "Rows: " . $sheet->getHighestRow() . "\n\n";
}

// Get a sheet by name
$ordersSheet = $spreadsheet->getSheetByName('Orders');
$orders = $ordersSheet->toArray();

// Get a sheet by index
$firstSheet = $spreadsheet->getSheet(0);

Step 6: Reading Large Excel Files Without Running Out of Memory

PhpSpreadsheet loads the entire file into memory by default. For a 50MB+ XLSX with hundreds of thousands of rows, this will exhaust your memory limit. Two solutions:

Option A: Read-only mode + chunk filter

php
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;

class ChunkReadFilter implements IReadFilter {
    private int $startRow;
    private int $endRow;

    public function setRows(int $startRow, int $chunkSize): void {
        $this->startRow = $startRow;
        $this->endRow = $startRow + $chunkSize;
    }

    public function readCell($column, $row, $worksheetName = ''): bool {
        return $row >= $this->startRow && $row < $this->endRow;
    }
}

$filePath = 'huge.xlsx';
$chunkSize = 1000;

$reader = IOFactory::createReaderForFile($filePath);
$reader->setReadDataOnly(true); // Skip formatting — much faster
$chunkFilter = new ChunkReadFilter();
$reader->setReadFilter($chunkFilter);

$startRow = 2; // Skip header row
while (true) {
    $chunkFilter->setRows($startRow, $chunkSize);
    $spreadsheet = $reader->load($filePath);
    $sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);

    if (empty($sheetData)) break;

    foreach ($sheetData as $row) {
        // Process one row — insert into DB, send to API, etc.
    }

    $spreadsheet->disconnectWorksheets();
    unset($spreadsheet);

    $startRow += $chunkSize;
}

Option B: Use OpenSpout for true streaming

For truly massive files (millions of rows), use OpenSpout — it streams rows one at a time without ever loading the file into memory:

bash
composer require openspout/openspout
php
use OpenSpout\Reader\XLSX\Reader;

$reader = new Reader();
$reader->open('massive.xlsx');

foreach ($reader->getSheetIterator() as $sheet) {
    foreach ($sheet->getRowIterator() as $row) {
        $cells = $row->toArray();
        // Process row — uses constant memory regardless of file size
    }
}

$reader->close();

OpenSpout uses ~5MB of memory whether your file has 1,000 rows or 10 million.

Step 7: Handling Excel Quirks (Dates, Formulas, Merged Cells)

Excel dates are numbers, not strings

A date like 2025-11-04 is stored as the number 45965 (days since 1900-01-01). Convert it back:

php
use PhpOffice\PhpSpreadsheet\Shared\Date;

$rawValue = $worksheet->getCell('A2')->getValue(); // 45965
$dateTime = Date::excelToDateTimeObject($rawValue);
echo $dateTime->format('Y-m-d'); // 2025-11-04

Or just use the formatted parameter when calling toArray():

php
$rows = $worksheet->toArray(null, true, true, true); // 4th param formats dates as text

Reading merged cells

PhpSpreadsheet returns the value only in the top-left cell of a merged range — other cells return null. Check for merges:

php
foreach ($worksheet->getMergeCells() as $mergeRange) {
    echo "Merged range: {$mergeRange}\n"; // e.g. "A1:C1"
}

Skipping formula calculation (faster reading)

Calculating formulas is slow. If you only need the cached values that Excel saved:

php
$reader = IOFactory::createReaderForFile($filePath);
$reader->setReadDataOnly(true); // No formatting, no calculation — fastest
$spreadsheet = $reader->load($filePath);

Common Errors and Fixes

“Class ‘PhpOffice\PhpSpreadsheet\IOFactory’ not found”

You forgot the autoloader. Add require 'vendor/autoload.php'; at the top.

“Allowed memory size exhausted”

The file is too big for default settings. Increase memory or switch to OpenSpout / chunk filter:

php
ini_set('memory_limit', '512M');

“Could not open file for reading! File does not exist.”

Check the file path is absolute or relative to the script’s working directory. Use realpath($filePath) to debug.

Encoding issues with special characters

PhpSpreadsheet handles UTF-8 natively. If you see garbled text, the issue is likely in how you’re outputting the data, not reading it. Make sure your output headers say UTF-8:

php
header('Content-Type: text/html; charset=utf-8');

Old .xls files (Excel 97-2003) fail to load

You need the optional dependency:

bash
composer require phpoffice/phpspreadsheet

PhpSpreadsheet supports XLS natively, but very old or corrupted files sometimes need to be re-saved as XLSX in Excel first.

Quick Reference: PhpSpreadsheet Reader Methods

Method What it does
IOFactory::load($path) Loads any supported file format (auto-detect)
IOFactory::createReaderForFile($path) Creates a reader without loading — for filters/options
$reader->setReadDataOnly(true) Skip formatting (faster, less memory)
$reader->setReadFilter($filter) Apply a chunk/column filter
$spreadsheet->getActiveSheet() Get currently active worksheet
$spreadsheet->getSheet($index) Get worksheet by zero-based index
$spreadsheet->getSheetByName('Name') Get worksheet by tab name
$worksheet->toArray() Convert sheet to 2D PHP array
$worksheet->rangeToArray('A1:D10') Convert specific range to array
$worksheet->getCell('B2')->getValue() Read one cell’s raw value
$worksheet->getCell('B2')->getCalculatedValue() Read one cell’s calculated value

Wrapping Up

For 99% of Excel reading tasks in PHP, PhpSpreadsheet is the right answer — install with Composer, load the file, convert to array, done. The production-ready function above handles headers, exceptions, and empty rows so you can drop it into any project.

For huge files where memory is a concern, switch to OpenSpout for true row-by-row streaming, or use PhpSpreadsheet’s chunk filter pattern.

The biggest gotchas to remember: Excel dates are numeric, formulas have raw vs calculated values, and setReadDataOnly(true) is your friend for performance. Get those right and you’ll handle anything Excel throws at you.

If you’re building an upload feature where users submit Excel files, always validate the data after reading — never trust user input, and use prepared statements when saving to a database.

Discussion

Be the first to comment

Leave a comment

Get a quote