PHPOffice/PHPWord · error · Exception
The filename ' . $sFileName . ' is not recognised as an OLE…
Error message
The filename ' . $sFileName . ' is not recognised as an OLE file
What it means
OLERead::read() reads the first 8 bytes of the file and compares them to the OLE magic identifier (D0 CF 11 E0 A1 B1 1A E1). If they differ, the file exists and is readable but is not an OLE compound document, so Exception is thrown. This is how the library rejects non-OLE files passed to legacy readers.
Solutions
- Verify the file type by its real content, not extension (e.g. `file` command or finfo) before choosing a reader.
- Use the Word2007 reader for .docx (zip) files instead of the legacy OLE-based reader.
- Re-obtain the original file if it is truncated or corrupted (check file size / re-upload).
- Optionally check the first 8 bytes yourself: substr($data,0,8) === "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1".
Example fix
// before
$phpWord = \PhpOffice\PhpWord\IOFactory::load('report.doc'); // actually a docx
// after
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file('report.doc');
$phpWord = str_contains($mime, 'zip')
? \PhpOffice\PhpWord\IOFactory::createReader('Word2007')->load('report.doc')
: \PhpOffice\PhpWord\IOFactory::load('report.doc'); Defensive patterns
Strategy: validation
Validate before calling
$magic = (string) file_get_contents($path, false, null, 0, 8);
$isOle = $magic === "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1";
$isZip = str_starts_with($magic, "PK\x03\x04");
if (!$isOle && !$isZip) {
throw new InvalidArgumentException("Unrecognized document format: $path");
}
$phpWord = $isZip
? IOFactory::createReader('Word2007')->load($path)
: IOFactory::load($path); Try / catch
try {
$phpWord = IOFactory::load($path);
} catch (\PhpOffice\PhpWord\Exception\Exception $e) {
if (str_contains($e->getMessage(), 'not recognised as an OLE file')) {
throw new RuntimeException("Not a valid legacy .doc file: $path", 0, $e);
}
throw $e;
} Prevention
- Detect file type by content (finfo/magic bytes), never by extension
- Route .docx (zip) files to the Word2007 reader, not the OLE-based one
- Validate uploads (size + magic bytes) at ingestion time
- Reject truncated files by comparing to expected sizes/checksums
When it happens
Trigger: Loading a file that exists and is readable but whose header is not the OLE signature: a .docx (ZIP) passed to a legacy .doc reader, a renamed text/HTML file with a .doc extension, an RTF file, or a truncated/corrupted OLE file whose first 8 bytes were overwritten.
Common situations: Users renaming .docx or .rtf files to .doc; script/autoresponse attachments saved truncated; confusing the legacy Doc reader with the Word2007 reader; partial uploads.
Related errors
- Could not open ' . $sFileName . ' for reading! File does…
- The archive failed to load with the following error code…
- Invalid value, on of ' . implode(', ', $position) . '…
- Invalid value, on of ' . implode(', ', $restartNumbers) . '…
- Invalid value, dirty or clean possible
AI-assisted analysis of PHPOffice/PHPWord@aef95c0415 (2026-09-14).
Data as JSON: /api/errors/a5819cfc6771d833.
Report an issue: GitHub.
Appendix: source
Thrown at src/PhpWord/Shared/OLERead.php:96
*
* @param $sFileName string Filename
*
* @throws Exception
*/
public function read($sFileName)
{
// Check if file exists and is readable
if (!is_readable($sFileName)) {
throw new Exception('Could not open ' . $sFileName . ' for reading! File does not exist, or it is not readable.');
}
// Get the file identifier
// Don't bother reading the whole file until we know it's a valid OLE file
$this->data = file_get_contents($sFileName, false, null, 0, 8);
// Check OLE identifier
if ($this->data != self::IDENTIFIER_OLE) {
throw new Exception('The filename ' . $sFileName . ' is not recognised as an OLE file');
}
// Get the file data
$this->data = file_get_contents($sFileName);
// 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 aef95c0415)