PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Reader\Exception
The filename $filename is not recognised as an OLE file
Error message
The filename $filename is not recognised as an OLE file
What it means
Thrown by OLERead::read() when the first 8 bytes of the file do not equal the OLE2 signature D0 CF 11 E0 A1 B1 1A E1. OLERead is the lightweight header parser the Xls reader uses to pull streams out of a legacy .xls; a mismatch means the file is not a legacy binary workbook at all (or is damaged), regardless of its extension.
Source
Thrown at src/PhpSpreadsheet/Shared/OLERead.php:81
/** @var int[] */
private array $possibleLoop = [];
/**
* Read the file.
*/
public function read(string $filename): void
{
File::assertFile($filename);
// Get the file identifier
// Don't bother reading the whole file until we know it's a valid OLE file
$this->data = (string) file_get_contents($filename, false, null, 0, 8);
// Check OLE identifier
$identifierOle = pack('CCCCCCCC', 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1);
if ($this->data != $identifierOle) {
throw new ReaderException('The filename ' . $filename . ' is not recognised as an OLE file');
}
// Get the file data
$this->data = (string) file_get_contents($filename);
// Total number of sectors used for the SAT
$this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
// SecID of the first sector of the directory stream
$this->rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS);
// SecID of the first sector of the SSAT (or -2 if not extant)
$this->sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS);
// SecID of the first sector of the MSAT (or -2 if no additional sectors are used)
$this->extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS);
// Total number of sectors used by MSATView on GitHub (pinned to 65b080eef4)
Solutions
- Use IOFactory::createReaderForFile($path) or IOFactory::load($path) so the reader is chosen by content sniffing, not extension.
- If you must pick manually, check the magic bytes first: 'PK...' => Xlsx reader, D0CF11E0... => Xls reader, otherwise treat as CSV/other.
- Validate upload size > 0 and content type before parsing; reject early with a clear message.
- Ask the sender for a re-export when the signature check fails on a file that should be a real .xls.
Example fix
// before
$spreadsheet = (new \PhpOffice\PhpSpreadsheet\Reader\Xls())
->load('export.xls'); // is not recognised as an OLE file (it's really CSV)
// after
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load('export.xls');
// or explicit sniffing:
$head = (string) file_get_contents('export.xls', false, null, 0, 4);
$reader = str_starts_with($head, "\xD0\xCF\x11\xE0")
? new \PhpOffice\PhpSpreadsheet\Reader\Xls()
: new \PhpOffice\PhpSpreadsheet\Reader\Csv(); Defensive patterns
Strategy: validation
Validate before calling
$head = (string) @file_get_contents($file, false, null, 0, 4);
if (str_starts_with($head, "\xD0\xCF\x11\xE0")) {
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls();
} elseif (str_starts_with($head, 'PK')) {
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
} else {
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Csv();
} Try / catch
try { $spreadsheet = (new \PhpOffice\PhpSpreadsheet\Reader\Xls())->load($file); }
catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
if (str_contains($e->getMessage(), 'not recognised as an OLE file')) {
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file); // content-based retry
} else { throw $e; }
} Prevention
- Never hardcode Reader\Xls for '.xls' filenames — content decides, extension lies.
- Centralize upload handling through IOFactory::load()/createReaderForFile().
- Reject zero-byte and truncated uploads on size/mime checks before parsing.
When it happens
Trigger: Reader\Xls::canRead()/load() on a file whose content is not OLE: an .xlsx (ZIP 'PK' header) renamed to .xls, a CSV/HTML export with an .xls extension, or a zero-byte/failed upload. The code reads exactly 8 bytes via file_get_contents(..., 0, 8) and string-compares them to the packed identifier.
Common situations: Hardcoding `new Xls()` because the filename ends in .xls while users upload OOXML or CSV; Excel's 'strict' exports; third-party systems emitting HTML tables named .xls; uploads interrupted so the file is empty.
Related errors
- File doesn't seem to be an OLE container.
- Only Little-Endian encoding is supported.
- Unsupported PPS type
- $bitmap: only 1 plane supported in bitmap image.\n
- Invalid R1C1-format Cell Reference
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/3c3f738866f54aaf.
Report an issue: GitHub.