正确包名是league/flysystem,v3需单独安装适配器如league/flysystem-aws-s3-v3,listcontents()返回迭代器需iterator_to_array转换,路径须用directory_separator,visibility仅支持public/private。

composer install flysystem 时提示 “Package not found”
不是 Flysystem 包名写错了,就是用了过时的包名。v2 起官方主包是 league/flysystem,旧教程里常见的 league/flysystem-bundle(Symfony 专用)或 thephpleague/flysystem(v1 命名)都可能触发这个错误。
实操建议:
- 运行
composer require league/flysystem:^3.0(推荐 v3,Laravel 10+/PHP 8.1+ 默认适配) - 若项目还在用 Laravel 8 或 PHP 7.4,改用
composer require league/flysystem:^2.5 - 别手敲包名——直接去 Packagist 页面 复制最新稳定版命令
- 装完检查
vendor/league/flysystem/src/是否存在,避免因网络中断导致半截安装
Laravel 里用 Flysystem 读写 OSS/S3/FTP 却报 “Adapter not found”
Flysystem v3 不再内置任何适配器,league/flysystem 主包只提供抽象层。S3、Aliyun OSS、FTP 这些都要单独装对应扩展包,否则 new S3Adapter(...) 会直接报错 Class not found。
实操建议:
- 阿里云 OSS:装
composer require league/flysystem-aws-s3-v3(OSS 兼容 S3 API)或更轻量的composer require aliyuncs/oss-sdk-php+ 自定义 adapter - S3:必须装
composer require league/flysystem-aws-s3-v3,且确认 AWS SDK v3 已加载(它会自动带进来) - FTP/SFTP:选
composer require league/flysystem-ftp或league/flysystem-sftp-v3,注意后者依赖ext-ssh2扩展 - 所有适配器初始化前,先
use LeagueFlysystem{Adapter, Filesystem},别漏掉命名空间
Flysystem v2 升 v3 后 listContents() 返回结构变了
v3 把原来返回 array of arrays 的 listContents() 改成返回 IteratorAggregate,直接 foreach 没问题,但若你之前写了 count($list) 或 array_keys($list),就会报错或空结果。
实操建议:
- 把
$files = $filesystem->listContents('path');改成$files = iterator_to_array($filesystem->listContents('path')); - 如果只是遍历,保留
foreach ($filesystem->listContents('path') as $item)更省内存 - v3 中
visibility字段统一为'public'/'private',v2 的'protected'已废弃,别硬匹配 - 别依赖返回数组的 key 顺序——v3 的迭代器不保证顺序,需显式
usort()
多个平台共用同一套 Flysystem 代码却出现路径分隔符或大小写问题
Windows 下本地磁盘路径用 ,Linux/macOS 用 /;S3/OSS 对路径大小写敏感,而本地文件系统(尤其 NTFS)不敏感——这些差异会让同一段代码在不同环境行为不一致。
实操建议:
- 所有路径拼接用
dirname(__DIR__) . DIRECTORY_SEPARATOR . 'uploads',别硬写'./uploads'或'.uploads' - 上传前统一转小写并过滤非法字符:
str_replace([' ', '\', ':'], '-', strtolower($filename)) - 本地开发用
LeagueFlysystemLocalLocalFilesystemAdapter时,设置writeFlags为LOCK_EX防并发写乱 - 测试阶段在 CI 中跑一次 Linux 环境的 E2E 测试,比靠本地模拟更早暴露路径问题
Flysystem 的抽象很干净,但跨平台的真实麻烦从来不在接口设计里,而在 adapter 初始化那一刻的配置细节、环境变量注入方式、以及你有没有在 CI 里真正用目标平台跑过 put() 和 read()。










