PHP ZipArchive压缩后unlink临时文件报流资源无效错误
ZipArchive批量添加文件时临时文件清理报错解决方案
现有实现代码
控制器层
$zip = new ZipArchive(); $zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE); $filesToClose = $this->advancedService->addFiles($files, $zip); $zip->close(); foreach ($filesToClose as $file) { fclose($file); unlink(stream_get_meta_data($file)['uri']); } // return response
服务层addFiles方法
// addFiles function foreach ($files as $file) { $fileName = 'FILE_'.$file->getId().'.'.$file->getExtension(); $tempFile = tmpfile(); $content = file_get_contents('uriFromSdk'); fwrite($tempFile, $content); $zipArchive->addFile(stream_get_meta_data($tempFile)['uri'], $fileName); $filesToClose[] = $tempFile; } return $filesToClose;
报错信息
运行代码时触发PHP警告:stream_get_meta_data(): supplied resource is not a valid stream resource
已知现象:调用fclose()不会触发该错误,仅调用stream_get_meta_data()时报错
约束条件
- 禁止在
$zip->close()执行完成前关闭或删除任何待压缩的临时文件,否则会导致压缩包内文件损坏
问题根因
报错是PHP临时文件资源的自动回收机制导致的:tmpfile()创建的临时文件句柄属于PHP自动管理的资源,除了手动调用fclose()会释放资源外,当资源失去有效引用、或脚本运行到对应生命周期节点时,PHP会自动回收这类资源。你在执行$zip->close()的过程中,存入数组的临时文件资源已经被PHP底层自动释放为无效资源,此时再用无效资源调用stream_get_meta_data()获取文件路径,自然会抛出无效流资源的错误。而fclose()本身对已释放的资源做了兼容处理,传入无效资源不会触发报错,和你观察到的现象完全吻合。
修复方案
核心思路是在临时文件资源有效的时候提前缓存文件路径,不要等资源释放后再从流中读取路径,修改后的代码完全符合约束要求:
修改服务层addFiles方法
// addFiles function foreach ($files as $file) { $fileName = 'FILE_'.$file->getId().'.'.$file->getExtension(); $tempFile = tmpfile(); $content = file_get_contents('uriFromSdk'); fwrite($tempFile, $content); // 资源有效时直接获取临时文件路径并缓存 $tempPath = stream_get_meta_data($tempFile)['uri']; $zipArchive->addFile($tempPath, $fileName); // 同时存储资源句柄和提前获取的文件路径 $filesToClose[] = [ 'handle' => $tempFile, 'path' => $tempPath ]; } return $filesToClose;
修改控制器层清理逻辑
$zip = new ZipArchive(); $zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE); $filesToClose = $this->advancedService->addFiles($files, $zip); $zip->close(); foreach ($filesToClose as $tempFile) { // 句柄仍有效则手动关闭,已自动释放则跳过 if (is_resource($tempFile['handle'])) { fclose($tempFile['handle']); } // 直接使用提前缓存的路径删除文件,无需再读取流元数据 if (file_exists($tempFile['path'])) { unlink($tempFile['path']); } } // return response
该方案不会在zip打包完成前触碰临时文件,完全避免压缩包损坏的问题;同时跳过了资源释放后读取流元数据的操作,从根源解决报错。
内容的提问来源于stack exchange,提问作者talhaamir
相关产品推荐
相关产品推荐

