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

  1. Verify the file type by its real content, not extension (e.g. `file` command or finfo) before choosing a reader.
  2. Use the Word2007 reader for .docx (zip) files instead of the legacy OLE-based reader.
  3. Re-obtain the original file if it is truncated or corrupted (check file size / re-upload).
  4. 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

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


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 MSAT

View on GitHub (pinned to aef95c0415)