flysystem v2 应捕获 filenotfoundexception、fileexistsexception 等子类异常,在业务入口统一处理;避免静默失败需预检存在性、开启 debug 查底层错误、s3 场景核验策略与凭据;上传失败须清理临时文件并记录完整上下文。

在 Symfony 5.4 中使用 Flysystem 读写文件失败,核心不是“怎么 catch”,而是**提前识别哪些操作会抛什么异常、在哪一层捕获最合理、以及如何让错误可定位可恢复**。Flysystem v2 的异常体系清晰,但若不按规范处理,容易静默失败或堆栈难读。
明确 Flysystem v2 的关键异常类型
Flysystem v2 统一使用 League\Flysystem\FilesystemException 作为基类,实际抛出的是其子类。常见且必须捕获的有:
- FileNotFoundException:读取或重命名时源路径不存在
- FileExistsException:write 或 rename 时目标已存在(默认不覆盖)
- UnableToWriteFileException:权限不足、磁盘满、S3 bucket 策略拒绝写入等
- UnableToReadFileException:读取失败,如 private S3 object 无权限、本地文件不可读
- InvalidVisibilityProvidedException:visibility 值非法(如传了 'hidden')
推荐的捕获位置与写法
不要在每个 write/read 调用外都套 try-catch。应集中在业务逻辑边界处,比如上传处理、文件迁移、模板生成等入口方法中统一拦截。示例:
try {
$this->filesystem->write('uploads/'.$filename, $content);
} catch (UnableToWriteFileException $e) {
$this->logger->error('Failed to write file', [
'path' => 'uploads/'.$filename,
'reason' => $e->getMessage(),
'cause' => $e->getPrevious() ? $e->getPrevious()->getMessage() : 'none'
]);
throw new \DomainException('文件保存失败,请稍后重试');
} catch (FileExistsException $e) {
// 可选择删除再写,或生成唯一名
$newName = uniqid('conflict_').'.'.$extension;
$this->filesystem->write('uploads/'.$newName, $content);
}
避免静默失败的三个实操要点
- 对关键路径,先调用 $filesystem->fileExists($path) 验证,再操作——尤其 rename 前确认源存在、write 前确认父目录可写
- 本地开发时开启 debug 模式 + full stack trace,Flysystem 异常会带原始底层错误(如 cURL error 60、Permission denied),比只看 HTTP 状态码更有用
- S3 场景下,若报错信息模糊(如 “Unable to write”),立即检查:bucket 策略是否允许 s3:PutObject 到该前缀、credentials 是否有效、visibility 设置是否与策略冲突
上传后写入失败的典型兜底流程
用户通过表单上传临时文件,再 move 到 Flysystem,这是高发失败点。建议结构如下:
- 用 $_FILES['file']['tmp_name'] 读取临时文件流,而非直接 move_uploaded_file
- 用 $filesystem->writeStream($targetPath, fopen(...)) 流式写入,避免内存溢出
- 写入失败时,立刻 unlink($_FILES['file']['tmp_name']) 清理临时文件,防止磁盘占满
- 记录完整上下文:用户 ID、原始文件名、MIME 类型、目标路径、异常类名











