{"record":{"id":"dd9b7982a40d8e76","repo":"PHPOffice/PhpSpreadsheet","slug":"could-not-open-resource-for-writing","errorCode":null,"errorMessage":"Could not open resource for writing.","messagePattern":"Could not open resource for writing\\.","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Writer\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Writer/Ods.php","lineNumber":137,"sourceCode":"\n        // Close file\n        try {\n            $zip->finish();\n        } catch (OverflowException) {\n            throw new WriterException('Could not close resource.');\n        }\n\n        $this->maybeCloseFileHandle();\n    }\n\n    /**\n     * Create zip object.\n     */\n    private function createZip(): ZipStream\n    {\n        // Try opening the ZIP file\n        if (!is_resource($this->fileHandle)) {\n            throw new WriterException('Could not open resource for writing.');\n        }\n\n        // Create new ZIP stream\n        return ZipStream0::newZipStream($this->fileHandle);\n    }\n\n    /**\n     * Get Spreadsheet object.\n     */\n    public function getSpreadsheet(): Spreadsheet\n    {\n        return $this->spreadSheet;\n    }\n\n    /**\n     * Set Spreadsheet object.\n     *\n     * @param Spreadsheet $spreadsheet PhpSpreadsheet object","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Writer/Ods.php#L119-L155","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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()."],"exampleFix":"// before\n$fh = fopen($exportDir . '/out.ods', 'wb'); // returns false if $exportDir is unwritable\n$writer->save($fh); // WriterException: Could not open resource for writing.\n\n// after\n$fh = fopen($exportDir . '/out.ods', 'wb');\nif ($fh === false) {\n    throw new RuntimeException(\"Cannot open output file in $exportDir\");\n}\n$writer->save($fh);\n// - or simply -\n$writer->save($exportDir . '/out.ods');","handlingStrategy":"validation","validationCode":"// Validate before save()\nif (is_string($target)) {\n    $dir = dirname($target);\n    if (!is_dir($dir) || !is_writable($dir)) {\n        throw new RuntimeException(\"Output directory not writable: $dir\");\n    }\n} else {\n    if (!is_resource($target) || get_resource_type($target) !== 'stream') {\n        throw new RuntimeException('Output target must be a filename or an open stream resource');\n    }\n}\n$writer->save($target);","typeGuard":"/** @param mixed $fh @phpstan-assert-if-true resource $fh */\nfunction isOpenStream(mixed $fh): bool\n{\n    return is_resource($fh) && get_resource_type($fh) === 'stream';\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["ods","file-handle","resource","fopen","permissions","stream"],"backgroundTag":"invalid-file-handle","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}