PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Writer\Exception
Could not open resource for writing.
Error message
Could not open resource for writing.
What it means
The Ods writer's createZip() requires $this->fileHandle to be a live stream resource before it can hand it to ZipStream. BaseWriter::openFileHandle() assigns whatever non-string value you pass to save() verbatim, so if you pass the result of a failed fopen() (boolean false), an already-closed resource, or null, is_resource() is false and this exception fires. It does NOT mean the file could not be opened on disk (that case throws 'Could not open file ... for writing.' earlier in openFileHandle()) - it means the value you passed in as the output target is not a usable stream.
Source
Thrown at src/PhpSpreadsheet/Writer/Ods.php:137
// Close file
try {
$zip->finish();
} catch (OverflowException) {
throw new WriterException('Could not close resource.');
}
$this->maybeCloseFileHandle();
}
/**
* Create zip object.
*/
private function createZip(): ZipStream
{
// Try opening the ZIP file
if (!is_resource($this->fileHandle)) {
throw new WriterException('Could not open resource for writing.');
}
// Create new ZIP stream
return ZipStream0::newZipStream($this->fileHandle);
}
/**
* Get Spreadsheet object.
*/
public function getSpreadsheet(): Spreadsheet
{
return $this->spreadSheet;
}
/**
* Set Spreadsheet object.
*
* @param Spreadsheet $spreadsheet PhpSpreadsheet objectView on GitHub (pinned to 65b080eef4)
Solutions
- Check the fopen() result before passing it: if ($fh === false) fail loudly with your own error.
- Simplest: pass a filename string to save() and let the writer open and close the handle itself.
- Verify the target directory exists and is writable, and that no open_basedir/safe-path restriction blocks it.
- Never fclose() a handle you intend to pass to save(); the writer closes it via maybeCloseFileHandle().
Example fix
// before
$fh = fopen($exportDir . '/out.ods', 'wb'); // returns false if $exportDir is unwritable
$writer->save($fh); // WriterException: Could not open resource for writing.
// after
$fh = fopen($exportDir . '/out.ods', 'wb');
if ($fh === false) {
throw new RuntimeException("Cannot open output file in $exportDir");
}
$writer->save($fh);
// - or simply -
$writer->save($exportDir . '/out.ods'); Defensive patterns
Strategy: validation
Validate before calling
// Validate before save()
if (is_string($target)) {
$dir = dirname($target);
if (!is_dir($dir) || !is_writable($dir)) {
throw new RuntimeException("Output directory not writable: $dir");
}
} else {
if (!is_resource($target) || get_resource_type($target) !== 'stream') {
throw new RuntimeException('Output target must be a filename or an open stream resource');
}
}
$writer->save($target); Type guard
/** @param mixed $fh @phpstan-assert-if-true resource $fh */
function isOpenStream(mixed $fh): bool
{
return is_resource($fh) && get_resource_type($fh) === 'stream';
} Prevention
- Always check fopen() !== false before passing a handle to save().
- Prefer passing a filename string so the writer owns open/close lifecycle.
- Never fclose() a handle you plan to hand to save(); closed resources fail is_resource().
- In containers, verify the mounted output directory exists and is writable at boot.
When it happens
Trigger: Calling $writer->save(fopen($path, 'wb')) where fopen returned false (unwritable directory, open_basedir restriction, URL wrapper failure) and the return value was not checked; passing a resource variable that was fclose()'d before save(); passing null or a non-stream value as the $filename argument.
Common situations: Developers switching from save('file.ods') to save($resource) to write into php://output, s3:// streams, or memory without checking fopen's return; permission problems in containerized deployments where /app/var isn't writable; reusing a cached file-handle property that another code path already closed.
Related errors
- Could not open file {filename} for reading.
- Unable to read data from {$pFilename}
- Could not create temporary file
- File "$filename" does not exist or is not readable.
- Can't open file $filename
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/dd9b7982a40d8e76.
Report an issue: GitHub.