PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Reader\Exception
Can't open file $filename
Error message
Can't open file $filename
What it means
Thrown by Shared\OLE::read() when fopen() on the given path fails, before any OLE parsing starts. It means PHP could not open the file in binary read mode at all — the OLE (.xls container) layer never saw the content. Suppressed error handling (@fopen) makes the exception the only signal of the underlying I/O problem.
Source
Thrown at src/PhpSpreadsheet/Shared/OLE.php:115
public int $smallBlockSize;
/**
* Threshold for big blocks.
*/
public int $bigBlockThreshold;
/**
* Reads an OLE container from the contents of the file given.
*
* @acces public
*
* @return bool true on success, PEAR_Error on failure
*/
public function read(string $filename): bool
{
$fh = @fopen($filename, 'rb');
if ($fh === false) {
throw new ReaderException("Can't open file $filename");
}
$this->_file_handle = $fh;
$signature = fread($fh, 8);
if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
throw new ReaderException("File doesn't seem to be an OLE container.");
}
fseek($fh, 28);
if (fread($fh, 2) != "\xFE\xFF") {
// This shouldn't be a problem in practice
throw new ReaderException('Only Little-Endian encoding is supported.');
}
// Size of blocks and short blocks in bytes
/** @var int<1, max> */
$temp = 2 ** self::readInt2($fh);
$this->bigBlockSize = $temp;
$this->smallBlockSize = 2 ** self::readInt2($fh);
View on GitHub (pinned to 65b080eef4)
Solutions
- Verify the file exists and is readable before parsing: is_file($filename) && is_readable($filename), and log the absolute path via realpath() at the failure site.
- Fix permissions/ownership so the web-server or worker user can read the file (chmod 644 / correct group).
- If the path is user-supplied, resolve it once with realpath() at request start and keep that absolute path for the read.
- For uploads, move the file to your own storage (move_uploaded_file) and read from there, never from the raw tmp path twice.
Example fix
// before
$ole->read($path); // Can't open file /tmp/tmpXyz/report.xls
// after
if (!is_string($path) || !is_file($path) || !is_readable($path)) {
throw new InvalidArgumentException("Excel file missing or unreadable: $path");
}
$ole->read($path); Defensive patterns
Strategy: validation
Validate before calling
if (!is_string($file) || !is_file($file) || !is_readable($file)) {
throw new InvalidArgumentException("Excel file missing or unreadable: " . var_export($file, true));
}
$ole->read($file); Type guard
function readableExcelFile(string $file): ?string
{
return (is_file($file) && is_readable($file)) ? realpath($file) : null;
} Try / catch
try { $ole->read($file); }
catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
if (str_starts_with($e->getMessage(), "Can't open file")) {
// missing/unreadable — fix the path or permissions, do not retry blindly
throw new RuntimeException("Cannot read workbook at $file", 0, $e);
}
throw $e;
} Prevention
- Resolve user-supplied paths once with realpath() and reuse the absolute path.
- Move uploads into your own storage before parsing; never re-read a tmp path on a later request.
- Check ownership/permissions for the PHP-FPM user when files are produced by CLI/cron jobs.
When it happens
Trigger: Calling $ole->read($filename) (directly or via a reader that uses the PEAR-style OLE class) with a path that does not exist, is not readable by the PHP user, is a directory, or uses a stream wrapper that is disallowed/unregistered. File::assertFile() style checks happen in some callers but read() itself only does the fopen.
Common situations: Reading an uploaded file after it was moved/deleted (tmp_name reused across requests); permission mismatch under FPM when the file was created by CLI/cron; path typos or wrong relative CWD in queue workers; safe-mode-style wrapper restrictions on remote URLs.
Related errors
- Could not open file "{$filename}" for writing.
- File "$filename" does not exist or is not readable.
- TrueType Font file not found
- File doesn't seem to be an OLE container.
- Only Little-Endian encoding is supported.
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/dc46d9d172dd7616.
Report an issue: GitHub.