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
- Resolve to an absolute path before loading and fail loudly if realpath() returns false
- Gate with file_exists() && is_readable() and surface your own error message including the resolved path
- Fix ownership/permissions or adjust open_basedir for the runtime user
- 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
- Convert user- or config-supplied paths to absolute (realpath) before loading
- Verify the runtime user can read the storage mount in staging smoke tests
- Move uploaded files to your own storage path and read from there, not from tmp names that may be cleaned up
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
- Could not create temporary file
- Can't open file $filename
- Could not open file {filename} for reading.
- TrueType Font file not found
- File $path not found!
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/fcdf6c4722ff6710.
Report an issue: GitHub.