简介
Laravel 借助 Frank de Jonge 开发的 Flysystem PHP 包,提供强大的文件系统抽象。其 Flysystem 集成提供操作本地文件系统、SFTP 和 Amazon S3 的简洁驱动。各存储系统使用相同 API,因此在本地开发环境与生产服务器之间切换存储方式也很方便。
配置
文件系统配置位于 config/filesystems.php。在这里可以配置所有文件系统“磁盘”,每个磁盘代表一种存储驱动及存储位置。配置文件包含各受支持驱动的示例,你可以按存储需求和凭据修改。 local 驱动操作运行 Laravel 应用的服务器上的本地文件;sftp 驱动用于基于 SSH 密钥的文件传输;s3 驱动用于向 Amazon S3 云存储服务写入文件。
Note
可以配置任意数量的磁盘,也可以让多个磁盘使用同一种驱动。
本地驱动
使用 local 驱动时,所有文件操作都相对于 filesystems 配置中的 root 目录。默认目录是 storage/app/private,所以下例会写入 storage/app/private/example.txt:
use Illuminate\Support\Facades\Storage;
Storage::disk('local')->put('example.txt', 'Contents');
公开磁盘
应用 filesystems 配置中的 public 磁盘用于存储可公开访问的文件。默认情况下,它使用 local 驱动,文件保存在 storage/app/public。
如果 public 磁盘使用 local 驱动,并且需要通过网页访问文件,应建立从 public/storage 指向源目录 storage/app/public 的符号链接: 可以使用 Artisan 的 storage:link 命令创建链接:
php artisan storage:link
保存文件并创建符号链接后,可以用 asset 辅助函数生成文件 URL:
echo asset('storage/file.txt');
也可以在 filesystems 配置中定义其他符号链接。运行 storage:link 时会创建所有配置的链接:
'links' => [
public_path('storage') => storage_path('app/public'),
public_path('images') => storage_path('app/images'),
],
storage:unlink 命令可以移除已配置的符号链接:
php artisan storage:unlink
驱动的前置依赖
配置 S3 驱动
使用 S3 驱动前,需要通过 Composer 安装 Flysystem S3 包:
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies
config/filesystems.php 已包含 S3 磁盘配置数组。通常通过下面这些由配置文件引用的环境变量设置 S3 信息和凭据:
AWS_ACCESS_KEY_ID=<your-key-id>
AWS_SECRET_ACCESS_KEY=<your-secret-access-key>
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=<your-bucket-name>
AWS_USE_PATH_STYLE_ENDPOINT=false
这些环境变量采用与 AWS CLI 相同的命名方式,便于使用。
配置 FTP 驱动
使用 FTP 驱动前,需要通过 Composer 安装 Flysystem FTP 包:
composer require league/flysystem-ftp "^3.0"
Laravel 的 Flysystem 集成支持 FTP,但框架默认的 config/filesystems.php 没有包含 FTP 示例配置。需要时可以使用下例:
'ftp' => [
'driver' => 'ftp',
'host' => env('FTP_HOST'),
'username' => env('FTP_USERNAME'),
'password' => env('FTP_PASSWORD'),
// Optional FTP Settings...
// 'port' => env('FTP_PORT', 21),
// 'root' => env('FTP_ROOT'),
// 'passive' => true,
// 'ssl' => true,
// 'timeout' => 30,
],
配置 SFTP 驱动
使用 SFTP 驱动前,需要通过 Composer 安装 Flysystem SFTP 包:
composer require league/flysystem-sftp-v3 "^3.0"
Laravel 的 Flysystem 集成支持 SFTP,但默认的 config/filesystems.php 没有 SFTP 示例配置。需要时可以参考下例:
'sftp' => [
'driver' => 'sftp',
'host' => env('SFTP_HOST'),
// Settings for basic authentication...
'username' => env('SFTP_USERNAME'),
'password' => env('SFTP_PASSWORD'),
// Settings for SSH key-based authentication with encryption password...
'privateKey' => env('SFTP_PRIVATE_KEY'),
'passphrase' => env('SFTP_PASSPHRASE'),
// Settings for file / directory permissions...
'visibility' => 'private', // `private` = 0600, `public` = 0644
'directory_visibility' => 'private', // `private` = 0700, `public` = 0755
// Optional SFTP Settings...
// 'hostFingerprint' => env('SFTP_HOST_FINGERPRINT'),
// 'maxTries' => 4,
// 'passphrase' => env('SFTP_PASSPHRASE'),
// 'port' => env('SFTP_PORT', 22),
// 'root' => env('SFTP_ROOT', ''),
// 'timeout' => 30,
// 'useAgent' => true,
],
限定路径、只读及读穿文件系统
限定路径磁盘会自动为所有路径加上指定前缀。创建这类磁盘前,需要通过 Composer 安装额外的 Flysystem 包:
composer require league/flysystem-path-prefixing "^3.0"
通过定义使用 scoped 驱动的磁盘,可以为已有磁盘创建限定路径的实例。例如,为现有 s3 磁盘限定一个路径前缀后,所有文件操作都会使用这个前缀:
's3-videos' => [
'driver' => 'scoped',
'disk' => 's3',
'prefix' => 'path/to/videos',
],
只读磁盘不允许执行写入操作。使用 read-only 配置项前,需要通过 Composer 安装额外的 Flysystem 包:
composer require league/flysystem-read-only "^3.0"
然后在一个或多个磁盘的配置数组中加入 read-only:
's3-videos' => [
'driver' => 's3',
// ...
'read-only' => true,
],
读穿磁盘支持不停机迁移文件。读取时,Laravel 先检查主磁盘;如果文件只存在于后备磁盘,则从后备磁盘读取,并复制到主磁盘,供后续请求使用:
'assets' => [
'driver' => 'read-through',
'primary' => 's3',
'fallback' => 'legacy-s3',
],
写入操作和目录列表针对主磁盘。存在性及元数据检查可使用任一磁盘,不会触发复制。复制后备文件到主磁盘失败时,默认仍能完成读取;如需抛出异常,可将 throw_on_promotion_failure 设为 true。
兼容 Amazon S3 的文件系统
默认的 filesystems 配置包含一个 s3 磁盘。 除了 Amazon S3,它也能用于兼容 S3 的服务,例如 RustFS、DigitalOcean Spaces、Vultr Object Storage、Cloudflare R2 和 Hetzner Cloud Storage。 通常将凭据修改为目标服务的凭据后,只需更新 endpoint 配置项。该值一般由 AWS_ENDPOINT 环境变量定义:
'endpoint' => env('AWS_ENDPOINT', 'https://rustfs:9000'),
获取磁盘实例
可以使用 Storage 门面操作任意已配置磁盘。例如,通过 put 在默认磁盘保存头像。如果调用门面方法前没有调用 disk,该操作会自动交给默认磁盘:
use Illuminate\Support\Facades\Storage;
Storage::put('avatars/1', $content);
如果应用使用多个磁盘,可以通过 Storage 门面的 disk 方法操作指定磁盘上的文件:
Storage::disk('s3')->put('avatars/1', $content);
按需创建磁盘
有时需要在运行时根据指定配置创建磁盘,而不将其写入 config/filesystems.php。为此可以向 Storage 的 build 方法传入配置数组:
use Illuminate\Support\Facades\Storage;
$disk = Storage::build([
'driver' => 'local',
'root' => '/path/to/root',
]);
$disk->put('image.jpg', $content);
读取文件
get 方法读取文件内容,返回原始字符串。所有文件路径都应相对于磁盘的 root:
$contents = Storage::get('file.jpg');
如果文件内容是 JSON,可以使用 json 读取并解码:
$orders = Storage::json('orders.json');
exists 判断磁盘上是否存在某个文件:
if (Storage::disk('s3')->exists('file.jpg')) {
// ...
}
missing 判断磁盘上是否缺少某个文件:
if (Storage::disk('s3')->missing('file.jpg')) {
// ...
}
下载文件
download 生成响应,强制浏览器下载指定路径的文件。第二个参数是下载时向用户展示的文件名,第三个参数可传入 HTTP 标头数组:
return Storage::download('file.jpg');
return Storage::download('file.jpg', $name, $headers);
文件 URL
url 获取指定文件的 URL。使用 local 驱动时,通常在路径前加上 /storage,返回相对 URL;使用 s3 驱动时,返回完整的远程 URL:
use Illuminate\Support\Facades\Storage;
$url = Storage::url('file.jpg');
使用 local 驱动时,需要公开访问的文件应放在 storage/app/public;同时在 public/storage 创建指向该目录的符号链接。
自定义 URL 主机
要修改 Storage 门面生成的 URL 主机,可以在磁盘配置数组中增加或修改 url:
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],
临时 URL
temporaryUrl 可以为 local 和 s3 驱动存储的文件创建临时 URL。参数为文件路径,以及表示到期时间的 DateTime 实例:
use Illuminate\Support\Facades\Storage;
$url = Storage::temporaryUrl(
'file.jpg', now()->plus(minutes: 5)
);
启用本地临时 URL
如果应用开发开始于 local 驱动支持临时 URL 之前,可能需要手动启用。请在 config/filesystems.php 的 local 磁盘配置中加入 serve:
'local' => [
'driver' => 'local',
'root' => storage_path('app/private'),
'serve' => true, // [tl! add]
'throw' => false,
],
S3 请求参数
如需指定额外的 S3 请求参数,可以将参数数组作为 temporaryUrl 的第三个参数:
$url = Storage::temporaryUrl(
'file.jpg',
now()->plus(minutes: 5),
[
'ResponseContentType' => 'application/octet-stream',
'ResponseContentDisposition' => 'attachment; filename=file2.jpg',
]
);
自定义临时 URL
通过 buildTemporaryUrlsUsing 可以自定义指定磁盘的临时 URL 生成方式。例如,某个控制器允许下载通常不支持临时 URL 的磁盘上的文件时,这个方法就很有用。通常在服务提供者的 boot 方法中调用:
<?php
namespace App\Providers;
use DateTime;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Storage::disk('local')->buildTemporaryUrlsUsing(
function (string $path, DateTime $expiration, array $options) {
return URL::temporarySignedRoute(
'files.download',
$expiration,
array_merge($options, ['path' => $path])
);
}
);
}
}
临时上传 URL
Warning
临时上传 URL 仅受 s3 和 local 驱动支持。 如需让客户端直接上传文件,可以用 temporaryUploadUrl 创建临时上传 URL。它接收路径和表示到期时间的 DateTime,返回关联数组,可以解构得到上传 URL 和上传请求应携带的标头:
use Illuminate\Support\Facades\Storage;
['url' => $url, 'headers' => $headers] = Storage::temporaryUploadUrl(
'file.jpg', now()->plus(minutes: 5)
);
这个方法主要适合无服务器环境,让客户端应用直接向 Amazon S3 等云存储系统上传文件。
文件元数据
除了读写内容,Laravel 也能提供文件本身的信息。例如,size 返回文件大小,单位为字节:
use Illuminate\Support\Facades\Storage;
$size = Storage::size('file.jpg');
lastModified 返回文件最后修改时间的 UNIX 时间戳:
$time = Storage::lastModified('file.jpg');
mimeType 返回文件的 MIME 类型:
$mime = Storage::mimeType('file.jpg');
文件路径
path 获取指定文件的路径。使用 local 驱动时返回绝对路径;使用 s3 驱动时返回相对于 S3 存储桶的路径:
use Illuminate\Support\Facades\Storage;
$path = Storage::path('file.jpg');
保存文件
put 可以向磁盘保存文件内容。也可以传入 PHP resource,利用 Flysystem 的底层流支持。所有路径都应相对于磁盘配置的 root:
use Illuminate\Support\Facades\Storage;
Storage::put('file.jpg', $contents);
Storage::put('file.jpg', $resource);
写入失败
如果 put 或其他写操作无法将文件写入磁盘,会返回 false:
if (! Storage::put('file.jpg', $contents)) {
// The file could not be written to disk...
}
可以在磁盘配置数组中定义 throw。将其设为 true 后,put 等写方法在写入失败时抛出 League\Flysystem\UnableToWriteFile:
'public' => [
'driver' => 'local',
// ...
'throw' => true,
],
另一种选择是在磁盘配置中定义 report。将其设为 true 后,写入失败时,Laravel 会通过应用的异常处理器记录底层异常,但不抛出异常,也不改变写操作的返回值:
'public' => [
'driver' => 'local',
// ...
'report' => true,
],
如果没有定义 throw 或 report,写入失败时磁盘会静默返回 false,底层异常既不会抛出,也不会记录。
在文件开头或末尾写入
prepend 和 append 分别在文件开头和末尾写入内容:
Storage::prepend('file.log', 'Prepended Text');
Storage::append('file.log', 'Appended Text');
复制与移动文件
copy 将已有文件复制到磁盘上的新位置;move 用于重命名文件或将其移到新位置:
Storage::copy('old/file.jpg', 'new/file.jpg');
Storage::move('old/file.jpg', 'new/file.jpg');
copyToDisk 和 moveToDisk 可以将文件复制或移动到另一磁盘。默认沿用源路径;第三个参数可指定目标路径:
Storage::disk('local')->copyToDisk('s3', 'reports/report.csv');
Storage::disk('local')->moveToDisk(
's3', 'reports/report.csv', 'archive/report.csv'
);
自动流式传输
将文件以流的形式写入存储可以明显减少内存占用。若希望 Laravel 自动管理传输,可以使用 putFile 或 putFileAs。它们接受 Illuminate\Http\File 或 Illuminate\Http\UploadedFile 实例,将文件流式写入目标位置:
use Illuminate\Http\File;
use Illuminate\Support\Facades\Storage;
// Automatically generate a unique ID for filename...
$path = Storage::putFile('photos', new File('/path/to/photo'));
// Manually specify a filename...
$path = Storage::putFileAs('photos', new File('/path/to/photo'), 'photo.jpg');
putFile 的几个要点:这里仅指定目录,没有指定文件名。默认会生成唯一 ID 作为文件名,根据 MIME 类型确定扩展名,并返回包含生成文件名的路径,方便保存到数据库。 putFile 和 putFileAs 还可接收参数来指定文件“可见性”。当文件保存在 Amazon S3 等云磁盘,并且需要通过生成的 URL 公开访问时,这很有用:
Storage::putFile('photos', new File('/path/to/photo'), 'public');
文件上传
网页应用中,文件存储最常见的用途之一是保存用户上传的照片和文档。Laravel 在上传文件实例上提供 store 方法,调用时传入希望保存文件的路径即可:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserAvatarController extends Controller
{
/**
* Update the avatar for the user.
*/
public function update(Request $request): string
{
$path = $request->file('avatar')->store('avatars');
return $path;
}
}
这里仅指定目录,没有指定文件名。默认的 store 会生成唯一 ID 作为文件名,根据 MIME 类型确定扩展名,并返回包含文件名的路径,方便保存到数据库。 也可以调用 Storage 门面的 putFile,完成与上例相同的存储操作:
$path = Storage::putFile('avatars', $request->file('avatar'));
指定文件名
如果不希望自动分配文件名,可以使用 storeAs;它依次接受路径、文件名和可选磁盘参数:
$path = $request->file('avatar')->storeAs(
'avatars', $request->user()->id
);
也可以使用 Storage 门面的 putFileAs 完成同样的操作:
$path = Storage::putFileAs(
'avatars', $request->file('avatar'), $request->user()->id
);
Warning
文件路径中的不可打印字符与无效 Unicode 字符会自动移除。因此,传入 Laravel 文件存储方法前,可以先清理路径。路径通过 League\Flysystem\WhitespacePathNormalizer::normalizePath 进行规范化。
指定磁盘
默认情况下,上传文件的 store 使用默认磁盘。要指定其他磁盘,将其名称作为第二个参数传入:
$path = $request->file('avatar')->store(
'avatars/'.$request->user()->id, 's3'
);
使用 storeAs 时,磁盘名称可以作为第三个参数传入:
$path = $request->file('avatar')->storeAs(
'avatars',
$request->user()->id,
's3'
);
上传文件的其他信息
通过 getClientOriginalName 和 getClientOriginalExtension 可以获取上传文件的原始名称和扩展名:
$file = $request->file('avatar');
$name = $file->getClientOriginalName();
$extension = $file->getClientOriginalExtension();
但这两个方法不安全:恶意用户可能篡改文件名和扩展名。因此通常应优先使用 hashName 和 extension 获取上传文件的名称及扩展名:
$file = $request->file('avatar');
$name = $file->hashName(); // Generate a unique, random name...
$extension = $file->extension(); // Determine the file's extension based on the file's MIME type...
文件可见性
在 Laravel 的 Flysystem 集成中,可见性是对不同平台文件权限的抽象。文件可声明为 public 或 private;public 表示通常允许其他人访问。例如,使用 S3 驱动时,可以获取公开文件的 URL。
通过 put 写入时可以设置可见性:
use Illuminate\Support\Facades\Storage;
Storage::put('file.jpg', $contents, 'public');
文件保存后,可以使用 getVisibility 和 setVisibility 获取或修改可见性:
$visibility = Storage::getVisibility('file.jpg');
Storage::setVisibility('file.jpg', 'public');
对于上传文件,可以通过 storePublicly 和 storePubliclyAs 以 public 可见性保存:
$path = $request->file('avatar')->storePublicly('avatars', 's3');
$path = $request->file('avatar')->storePubliclyAs(
'avatars',
$request->user()->id,
's3'
);
图像处理
如需在保存之前调整上传图像大小、裁剪或转换格式,可以使用 Laravel 的图像处理功能:
$path = $request->image('avatar')
->cover(400, 400)
->toWebp()
->storePublicly('avatars', 'public');
也可以从已有磁盘文件创建图像实例:
$image = Storage::disk('public')->image('avatars/photo.jpg');
本地文件与可见性
使用 local 驱动时,public 可见性对应目录的 0755 权限与文件的 0644 权限。可以在 filesystems 配置中修改映射:
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'permissions' => [
'file' => [
'public' => 0644,
'private' => 0600,
],
'dir' => [
'public' => 0755,
'private' => 0700,
],
],
'throw' => false,
],
删除文件
delete 接受一个文件名或文件数组:
use Illuminate\Support\Facades\Storage;
Storage::delete('file.jpg');
Storage::delete(['file.jpg', 'file2.jpg']);
必要时可以指定删除操作使用的磁盘:
use Illuminate\Support\Facades\Storage;
Storage::disk('s3')->delete('path/file.jpg');
目录
获取目录内的所有文件
files 返回指定目录内的文件数组。要包括子目录中的文件,使用 allFiles:
use Illuminate\Support\Facades\Storage;
$files = Storage::files($directory);
$files = Storage::allFiles($directory);
获取目录内的所有子目录
directories 返回指定目录内的子目录数组。要递归包含更深层的目录,使用 allDirectories:
$directories = Storage::directories($directory);
$directories = Storage::allDirectories($directory);
创建目录
makeDirectory 创建指定目录及所需的父目录:
Storage::makeDirectory($directory);
删除目录
deleteDirectory 移除指定目录及其中全部文件:
Storage::deleteDirectory($directory);
测试
Storage 门面的 fake 可以轻松生成虚拟磁盘。结合 Illuminate\Http\UploadedFile 的文件生成工具,可以大幅简化上传测试。例如:
<?php
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
test('albums can be uploaded', function () {
Storage::fake('photos');
$response = $this->json('POST', '/photos', [
UploadedFile::fake()->image('photo1.jpg'),
UploadedFile::fake()->image('photo2.jpg')
]);
// Assert one or more files were stored...
Storage::disk('photos')->assertExists('photo1.jpg');
Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']);
// Assert one or more files were not stored...
Storage::disk('photos')->assertMissing('missing.jpg');
Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']);
// Assert that the number of files in a given directory matches the expected count...
Storage::disk('photos')->assertCount('/wallpapers', 2);
// Assert that a given directory is empty...
Storage::disk('photos')->assertDirectoryEmpty('/wallpapers');
// Assert that the disk contains no files...
Storage::disk('photos')->assertEmpty();
});
<?php
namespace Tests\Feature;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_albums_can_be_uploaded(): void
{
Storage::fake('photos');
$response = $this->json('POST', '/photos', [
UploadedFile::fake()->image('photo1.jpg'),
UploadedFile::fake()->image('photo2.jpg')
]);
// Assert one or more files were stored...
Storage::disk('photos')->assertExists('photo1.jpg');
Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']);
// Assert one or more files were not stored...
Storage::disk('photos')->assertMissing('missing.jpg');
Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']);
// Assert that the number of files in a given directory matches the expected count...
Storage::disk('photos')->assertCount('/wallpapers', 2);
// Assert that a given directory is empty...
Storage::disk('photos')->assertDirectoryEmpty('/wallpapers');
// Assert that the disk contains no files...
Storage::disk('photos')->assertEmpty();
}
}
默认的 fake 会删除临时目录中的所有文件。如需保留,可改用 persistentFake。更多上传测试信息见 HTTP 测试文档。
Warning
image 方法需要 GD 扩展。
自定义文件系统
Laravel 的 Flysystem 集成内置了多种驱动,但 Flysystem 还为其他存储系统提供适配器。要在 Laravel 中使用这些适配器,可以创建自定义驱动。 定义自定义文件系统需要一个 Flysystem 适配器。先向项目加入社区维护的 Dropbox 适配器:
composer require spatie/flysystem-dropbox
接着,在应用服务提供者的 boot 方法中使用 Storage 门面的 extend 注册驱动:
<?php
namespace App\Providers;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use League\Flysystem\Filesystem;
use Spatie\Dropbox\Client as DropboxClient;
use Spatie\FlysystemDropbox\DropboxAdapter;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// ...
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Storage::extend('dropbox', function (Application $app, array $config) {
$adapter = new DropboxAdapter(new DropboxClient(
$config['authorization_token']
));
return new FilesystemAdapter(
new Filesystem($adapter, $config),
$adapter,
$config
);
});
}
}
extend 的第一个参数是驱动名称,第二个参数是接收 $app 和 $config 的闭包。闭包必须返回 Illuminate\Filesystem\FilesystemAdapter 实例;$config 包含 config/filesystems.php 中该磁盘的配置。
创建并注册该扩展的服务提供者后,就可以在 config/filesystems.php 中使用 dropbox 驱动。
原文:File Storage,Laravel 文档贡献者。文档源自 laravel/docs 13.x,依据该文档仓库的 MIT 许可证将正文译为中文,保留代码。Copyright (c) Taylor Otwell。完整版权及许可声明见随附 LICENSE-source.txt。











暂无评论内容