{"record":{"id":"16a8bc28a68299c3","repo":"PHPOffice/PhpSpreadsheet","slug":"unable-to-open-stream-path","errorCode":null,"errorMessage":"Unable to open stream $path","messagePattern":"Unable to open stream \\$path","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Shared/OLE.php","lineNumber":236,"sourceCode":"            $isRegistered = true;\n        }\n\n        // Store current instance in global array, so that it can be accessed\n        // in OLE_ChainedBlockStream::stream_open().\n        // Object is removed from self::$instances in OLE_Stream::close().\n        $GLOBALS['_OLE_INSTANCES'][] = $this; //* @phpstan-ignore offsetAccess.nonOffsetAccessible (I don't know how to fix this)\n        $keys = array_keys($GLOBALS['_OLE_INSTANCES']); //* @phpstan-ignore argument.type (I don't know how to fix this)\n        $instanceId = end($keys);\n\n        $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId;\n        if ($blockIdOrPps instanceof OLE\\PPS) {\n            $path .= '&blockId=' . $blockIdOrPps->startBlock;\n            $path .= '&size=' . $blockIdOrPps->Size;\n        } else {\n            $path .= '&blockId=' . $blockIdOrPps;\n        }\n\n        $resource = fopen($path, 'rb') ?: throw new Exception(\"Unable to open stream $path\");\n\n        return $resource;\n    }\n\n    /**\n     * Reads a signed char.\n     *\n     * @param resource $fileHandle file handle\n     */\n    private static function readInt1($fileHandle): int\n    {\n        [, $tmp] = unpack('c', fread($fileHandle, 1) ?: '') ?: [0, 0];\n        /** @var int $tmp */\n\n        return $tmp;\n    }\n\n    /**","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Shared/OLE.php#L218-L254","documentation":"Thrown by Shared\\OLE::getStream() when fopen() cannot open the custom 'ole-chainedblockstream://' wrapper URL it builds from an OLE instance id, block id and size. The wrapper is the mechanism that exposes a chain of FAT blocks inside the compound document as a readable PHP stream; failure means the wrapper rejected those parameters or the wrapper was not registered.","triggerScenarios":"Calling OLE::getStream($blockIdOrPps) where the OLE instance index in $GLOBALS['_OLE_INSTANCES'] no longer matches (instance registry mutated), or the blockId/size are invalid for the file; the ChainedBlockStream wrapper's stream_open() returns false, e.g. when the referenced OLE object is gone or the block lies outside the parsed FAT. Most realistic with corrupted .xls files or misuse of the internal OLE API.","commonSituations":"Corrupted or hostile .xls uploads whose allocation tables point at nonexistent blocks; code that kept a stale OLE object and calls getStream() after the instance registry changed; edge cases in heavily fragmented workbooks with malformed block chains.","solutions":["Treat as corruption in practice: obtain a clean copy of the workbook and retry before debugging wrapper internals.","Let the high-level readers do the work: Reader\\Xls (via OLERead) handles block streams for you; avoid calling Shared\\OLE::getStream() directly unless you maintain custom OLE tooling.","Wrap .xls ingestion in try/catch for PhpSpreadsheet\\Exception and quarantine the offending file for inspection.","If it recurs on one specific file, inspect it with an OLE viewer (e.g. Structured Storage eXplorer) to confirm broken block chains."],"exampleFix":"// before\n$resource = $ole->getStream($pps); // Unable to open stream ole-chainedblockstream://oleInstanceId=3&blockId=...\n\n// after\ntry {\n    $resource = $ole->getStream($pps);\n} catch (\\PhpOffice\\PhpSpreadsheet\\Exception $e) {\n    // malformed block chain in the container — reject the file\n    throw new RuntimeException('Workbook container is corrupt: ' . $e->getMessage(), 0, $e);\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { $resource = $ole->getStream($pps); }\ncatch (\\PhpOffice\\PhpSpreadsheet\\Exception $e) {\n    if (str_contains($e->getMessage(), 'Unable to open stream')) {\n        throw new RuntimeException('Workbook block chain is corrupt: ' . $e->getMessage(), 0, $e);\n    }\n    throw $e;\n}","preventionTips":["Avoid calling Shared\\OLE::getStream() directly; use the high-level readers that handle block chains internally.","Do not hold OLE instances across long lifecycles where the instance registry can change.","Quarantine .xls files that trigger stream-wrapper failures and inspect them with an OLE viewer."],"tags":["ole","stream-wrapper","file-corruption","phpspreadsheet"],"backgroundTag":"stream-wrapper-open-failed","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}