PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Reader\Exception

File "$filename" does not exist or is not readable.

Error message

File "$filename" does not exist or is not readable.

What it means

File::assertFile() runs prohibitWrappers() then is_file()/is_readable(); any failure throws this ReaderException (src/PhpSpreadsheet/Shared/File.php:165). Every reader's load() path calls it (error 180's flow shows it before canRead), making it the first gate for loading from disk — the file must exist as a regular file and be readable by the PHP process.

Source

Thrown at src/PhpSpreadsheet/Shared/File.php:165

        if (
            Preg::IsMatch('~^phar://~i', $filename)
            || (Preg::isMatch('/^([\w.\s\x00-\x1f]+):/', $filename) && !Preg::isMatch('/^([\w.]+):/', $filename))
            || Preg::isMatch('~^[\w.]+://.*phar:~is', $filename)
        ) {
            throw new Exception(
                "Disallowed stream wrapper used for {$filename}"
            );
        }
    }

    /**
     * Assert that given path is an existing file and is readable, otherwise throw exception.
     */
    public static function assertFile(string $filename, string $zipMember = ''): void
    {
        self::prohibitWrappers($filename);
        if (!is_file($filename) || !is_readable($filename)) {
            throw new ReaderException('File "' . $filename . '" does not exist or is not readable.');
        }

        if ($zipMember !== '') {
            $zipfile = "zip://$filename#$zipMember";
            if (!self::fileExists($zipfile)) {
                // Has the file been saved with Windoze directory separators rather than unix?
                $zipfile = "zip://$filename#" . str_replace('/', '\\', $zipMember);
                if (!self::fileExists($zipfile)) {
                    throw new ReaderException("Could not find zip member $zipfile");
                }
            }
        }
    }

    /**
     * Same as assertFile, except return true/false and don't throw Exception.
     * Will nevertheless throw if filename uses invalid protocol, e.g. phar.
     */

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Resolve to an absolute path before loading and fail loudly if realpath() returns false
  2. Gate with file_exists() && is_readable() and surface your own error message including the resolved path
  3. Fix ownership/permissions or adjust open_basedir for the runtime user
  4. For stream or user-uploaded content, persist to a temp file first and load that path

Example fix

// before
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($request['path']); // relative/untrusted

// after
$path = realpath($request['path']);
if ($path === false || !is_readable($path)) {
    throw new \InvalidArgumentException("Spreadsheet file not found or unreadable: {$request['path']}");
}
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($path);
Defensive patterns

Strategy: validation

Validate before calling

function assertSpreadsheetReadable(string $path): string
{
    $real = realpath($path);
    if ($real === false || !is_file($real) || !is_readable($real)) {
        throw new \InvalidArgumentException("Spreadsheet missing/unreadable: $path");
    }

    return $real;
}

$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load(assertSpreadsheetReadable($path));

Prevention

When it happens

Trigger: load('missing.xlsx'); a relative path resolved against a different working directory (CLI vs web server); permissions denying the runtime user; open_basedir restrictions on the path; the file deleted between upload handling and read; passing a directory name.

Common situations: Moved/renamed uploads; scaffolding with a wrong base directory; running as www-data while files belong to another user; symlinked storage mounts; queue workers whose cwd differs from the web entrypoint.

Related errors


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/fcdf6c4722ff6710. Report an issue: GitHub.