PHPOffice/PHPWord · error · Exception

Could not open ' . $sFileName . ' for reading! File does…

Error message

Could not open ' . $sFileName . ' for reading! File does not exist, or it is not readable.

What it means

OLERead::read() is the low-level reader for OLE compound documents (legacy .doc, .xls). Before parsing, it checks is_readable($sFileName) and throws if the path cannot be read — covering nonexistent files, unreadable permissions, and directories. Called from loadOLE when loading legacy binary documents.

Solutions

  1. Confirm the file exists at the exact path with file_exists()/is_readable() before calling loadOLE.
  2. Copy remote/uploaded content to a local temp file first, then load that path.
  3. Fix filesystem permissions (chown/chmod) so the PHP process user can read the file.
  4. Check open_basedir and safe paths in php.ini if the path is outside allowed roots.

Example fix

// before
$phpWord = IOFactory::load('/uploads/report.doc'); // file was moved
// after
$path = '/var/www/uploads/report.doc';
if (!is_readable($path)) {
    throw new RuntimeException("OLE document not readable: $path");
}
$phpWord = IOFactory::load($path);
Defensive patterns

Strategy: validation

Validate before calling

if (!is_file($path)) {
    throw new InvalidArgumentException("File not found: $path");
}
if (!is_readable($path)) {
    throw new RuntimeException("File not readable (check permissions/open_basedir): $path");
}

Type guard

function isReadableFile(string $path): bool
{
    return is_string($path) && is_file($path) && is_readable($path);
}

Try / catch

try {
    $phpWord = IOFactory::load($path);
} catch (\PhpOffice\PhpWord\Exception\Exception $e) {
    if (str_contains($e->getMessage(), 'for reading')) {
        throw new RuntimeException("Cannot read OLE document at $path", 0, $e);
    }
    throw $e;
}

Prevention

When it happens

Trigger: Passing a path to loadOLE/read where the file does not exist, lacks read permission for the PHP user, is a directory, or is a stream/URL wrapper that is_readable() cannot validate (e.g. some remote wrappers).

Common situations: Opening legacy .doc files after moving uploads to another storage directory; web-server user lacking permission on the upload folder; passing a URL or in-memory content instead of a local filesystem path; open_basedir restrictions.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of PHPOffice/PHPWord@aef95c0415 (2026-09-14). Data as JSON: /api/errors/87172ef78436372b. Report an issue: GitHub.

Appendix: source

Thrown at src/PhpWord/Shared/OLERead.php:87

    public $bigBlockChain = null;
    public $smallBlockChain = null;	
    public $entry = null;	
    public $rootentry = null;
    public $wrkObjectPoolelseif = null;
    public $props = array();	

    /**
     * Read the file
     *
     * @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

View on GitHub (pinned to aef95c0415)