文件上传
Livewire 为在组件中上传文件提供了强大支持。
首先,在组件中添加 WithFileUploads trait。添加后,就可以像对待其他输入类型一样,在文件输入上使用 wire:model,其余交给 Livewire 处理。
下面是一个处理照片上传的简单组件示例:
<?php // resources/views/components/⚡upload-photo.blade.php
use Livewire\Attributes\Validate;
use Livewire\WithFileUploads;
use Livewire\Component;
new class extends Component {
use WithFileUploads;
#[Validate('image|max:1024')] // 1MB Max
public $photo;
public function save()
{
$this->validate();
$this->photo->store(path: 'photos');
}
};<form wire:submit="save">
<input type="file" wire:model="photo">
@error('photo') <span class="error">{{ $message }}</span> @enderror
<button type="submit">Save photo</button>
</form>WARNING
「upload」方法名已被保留
注意上面的示例使用「save」方法而不是「upload」方法。这是一个常见的「坑」。「upload」一词已被 Livewire 保留,不能用作组件中的方法名或属性名。
从开发者角度看,处理文件输入与处理其他输入类型并无不同:在 <input> 标签上加上 wire:model,其余都由系统代劳。
不过,为了让 Livewire 中的文件上传正常工作,底层还有更多事情在发生。用户选择要上传的文件时大致流程如下:
- 选择新文件时,Livewire 的 JavaScript 会先向服务器上的组件发起一次请求,获取临时的「签名」上传 URL。
- 收到 URL 后,JavaScript 会向该签名 URL 执行真正的「上传」,将文件存入 Livewire 指定的临时目录,并返回新临时文件的唯一哈希 ID。
- 文件上传完成并生成唯一哈希 ID 后,Livewire 的 JavaScript 会再向服务器上的组件发起一次最终请求,告诉它将该公共属性「设为」这个新临时文件。
- 此时,公共属性(本例中为
$photo)已指向该临时上传文件,随时可以进行存储或验证。
存储已上传文件
前面的示例演示了最基本的存储场景:将临时上传的文件移动到应用默认文件系统磁盘上的「photos」目录。
不过,你可能想自定义所存文件的文件名,甚至指定特定的存储「磁盘」(例如 S3)。
TIP
原始文件名
你可以通过调用临时上传的 ->getClientOriginalName() 方法获取原始文件名。
Livewire 遵循 Laravel 存储上传文件时使用的同一套 API,因此可以随时查阅 Laravel 的文件上传文档。下面是一些常见的存储场景与示例:
public function save()
{
// Store the file in the "photos" directory of the default filesystem disk
$this->photo->store(path: 'photos');
// Store the file in the "photos" directory in a configured "s3" disk
$this->photo->store(path: 'photos', options: 's3');
// Store the file in the "photos" directory with the filename "avatar.png"
$this->photo->storeAs(path: 'photos', name: 'avatar');
// Store the file in the "photos" directory in a configured "s3" disk with the filename "avatar.png"
$this->photo->storeAs(path: 'photos', name: 'avatar', options: 's3');
// Store the file in the "photos" directory, with "public" visibility in a configured "s3" disk
$this->photo->storePublicly(path: 'photos', options: 's3');
// Store the file in the "photos" directory, with the name "avatar.png", with "public" visibility in a configured "s3" disk
$this->photo->storePubliclyAs(path: 'photos', name: 'avatar', options: 's3');
}处理多个文件
Livewire 通过检测 <input> 标签上的 multiple 属性,自动处理多文件上传。
例如,下面是一个带有名为 $photos 的数组属性的组件。在表单的文件输入上加上 multiple 后,Livewire 会自动将新文件追加到该数组:
<?php // resources/views/components/⚡upload-photos.blade.php
use Livewire\Attributes\Validate;
use Livewire\WithFileUploads;
use Livewire\Component;
new class extends Component {
use WithFileUploads;
#[Validate(['photos.*' => 'image|max:1024'])]
public $photos = [];
public function save()
{
$this->validate();
foreach ($this->photos as $photo) {
$photo->store(path: 'photos');
}
}
};<form wire:submit="save">
<input type="file" wire:model="photos" multiple>
@error('photos.*') <span class="error">{{ $message }}</span> @enderror
<button type="submit">Save photo</button>
</form>文件验证
如前所述,用 Livewire 验证文件上传与在标准 Laravel 控制器中处理文件上传相同。
关于文件验证的更多信息,请参阅 Laravel 的文件验证文档。
临时预览 URL
用户选择文件后,通常应在提交表单并存储文件之前向其展示该文件的预览。
Livewire 通过在已上传文件上使用 ->temporaryUrl() 方法,让这件事变得轻而易举。
INFO
临时 URL 仅限图片
出于安全原因,临时预览 URL 仅支持图片 MIME 类型的文件。
下面来看一个带图片预览的文件上传示例:
<?php // resources/views/components/⚡upload-photo.blade.php
use Livewire\Attributes\Validate;
use Livewire\WithFileUploads;
use Livewire\Component;
new class extends Component {
use WithFileUploads;
#[Validate('image|max:1024')]
public $photo;
// ...
};<form wire:submit="save">
@if ($photo) <!-- [tl! highlight:2] -->
<img src="{{ $photo->temporaryUrl() }}">
@endif
<input type="file" wire:model="photo">
@error('photo') <span class="error">{{ $message }}</span> @enderror
<button type="submit">Save photo</button>
</form>如前所述,Livewire 将临时文件存储在非公开目录中;因此,通常没有简单办法向用户暴露临时的公开 URL 用于图片预览。
不过,Livewire 通过提供一个临时的签名 URL 解决了这个问题——该 URL 伪装成已上传的图片,使页面可以向用户显示图片预览。
该 URL 有保护机制,不会展示临时目录之上的文件。而且由于经过签名,用户无法滥用该 URL 预览系统中的其他文件。
TIP
S3 临时签名 URL
若已将 Livewire 配置为使用 S3 存储临时文件,调用 ->temporaryUrl() 会直接生成指向 S3 的临时签名 URL,这样图片预览就不会从你的 Laravel 应用服务器加载。
测试文件上传
你可以使用 Laravel 现有的文件上传测试辅助工具来测试文件上传。
下面是用 Livewire 测试 UploadPhoto 组件的完整示例:
<?php
namespace Tests\Feature\Livewire;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use App\Livewire\UploadPhoto;
use Livewire\Livewire;
use Tests\TestCase;
class UploadPhotoTest extends TestCase
{
public function test_can_upload_photo()
{
Storage::fake('avatars');
$file = UploadedFile::fake()->image('avatar.png');
Livewire::test(UploadPhoto::class)
->set('photo', $file)
->call('upload', 'uploaded-avatar.png');
Storage::disk('avatars')->assertExists('uploaded-avatar.png');
}
}下面是使上述测试通过所需的 upload-photo 组件示例:
<?php // resources/views/components/⚡upload-photo.blade.php
use Livewire\WithFileUploads;
use Livewire\Component;
new class extends Component {
use WithFileUploads;
public $photo;
public function upload($name)
{
$this->photo->storeAs('/', $name, disk: 'avatars');
}
// ...
};关于测试文件上传的更多信息,请参阅 Laravel 的文件上传测试文档。
直接上传到 Amazon S3
如前所述,Livewire 会将所有文件上传先存入临时目录,直到开发者永久存储该文件。
默认情况下,Livewire 使用默认文件系统磁盘配置(通常是 local),并将文件存储在 livewire-tmp/ 目录中。
因此,即使你之后选择将上传文件存入 S3 存储桶,文件上传也始终会经过你的应用服务器。
若希望绕过应用服务器,改为将 Livewire 的临时上传存入 S3 存储桶,请在 .env 文件中将 LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK 环境变量设为 s3(或使用 s3 驱动的其他自定义磁盘):
LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK=s3现在,用户上传文件时,文件实际上不会存储在你的服务器上,而是直接上传到 S3 存储桶中的 livewire-tmp/ 子目录。
TIP
或者,你也可以用 php artisan livewire:config 发布 Livewire 的配置文件,以完全控制 temporary_file_upload 配置。
配置自动文件清理
Livewire 的临时上传目录会很快堆满文件;因此,必须配置 S3 清理超过 24 小时的文件。
要配置此行为,请在使用 S3 存储桶进行文件上传的环境中运行以下 Artisan 命令:
php artisan livewire:configure-s3-upload-cleanup现在,任何超过 24 小时的临时文件都会由 S3 自动清理。
INFO
若未使用 S3 存储文件,Livewire 会自动处理文件清理,无需运行上述命令。
加载指示器
虽然文件上传的 wire:model 底层工作方式与其他 wire:model 输入类型不同,但显示加载指示器的接口是相同的。
你可以使用 wire:loading 显示限定在该文件上传范围内的加载指示器:
<input type="file" wire:model="photo">
<div wire:loading wire:target="photo">Uploading...</div>或者更简单地使用 Livewire 自动的 data-loading 属性:
<div>
<input type="file" wire:model="photo">
<div class="not-data-loading:hidden">Uploading...</div>
</div>现在,文件上传期间会显示「Uploading...」消息,上传完成后隐藏。
进度指示器
每次 Livewire 文件上传操作都会在对应的 <input> 元素上派发 JavaScript 事件,允许自定义 JavaScript 拦截这些事件:
| Event | Description |
|---|---|
livewire-upload-start | 上传开始时派发 |
livewire-upload-finish | 上传成功完成时派发 |
livewire-upload-cancel | 上传被提前取消时派发 |
livewire-upload-error | 上传失败时派发 |
livewire-upload-progress | 上传进行中时派发,事件中包含上传进度百分比 |
下面是将 Livewire 文件上传包在 Alpine 组件中以显示上传进度条的示例:
<form wire:submit="save">
<div
x-data="{ uploading: false, progress: 0 }"
x-on:livewire-upload-start="uploading = true"
x-on:livewire-upload-finish="uploading = false"
x-on:livewire-upload-cancel="uploading = false"
x-on:livewire-upload-error="uploading = false"
x-on:livewire-upload-progress="progress = $event.detail.progress"
>
<!-- File Input -->
<input type="file" wire:model="photo">
<!-- Progress Bar -->
<div x-show="uploading">
<progress max="100" x-bind:value="progress"></progress>
</div>
</div>
<!-- ... -->
</form>取消上传
若上传耗时较长,用户可能想取消。你可以用 Livewire 的 JavaScript 函数 $cancelUpload() 提供此功能。
下面是在 Livewire 组件中用 wire:click 处理点击事件、创建「Cancel Upload」按钮的示例:
<form wire:submit="save">
<!-- File Input -->
<input type="file" wire:model="photo">
<!-- Cancel upload button -->
<button type="button" wire:click="$cancelUpload('photo')">Cancel Upload</button>
<!-- ... -->
</form>按下「Cancel upload」后,文件上传请求会被中止,文件输入会被清空。用户现在可以用另一个文件再次尝试上传。
或者,你也可以像这样从 Alpine 调用 cancelUpload(...):
<button type="button" x-on:click="$wire.cancelUpload('photo')">Cancel Upload</button>JavaScript 上传 API
与第三方文件上传库集成时,往往需要比简单的 <input type="file" wire:model="..."> 元素更多的控制权。
针对这些场景,Livewire 暴露了专用的 JavaScript 函数。
这些函数存在于一个 JavaScript 组件对象上,可在 Livewire 组件模板中通过便捷的 $wire 对象访问:
<script>
let file = $wire.el.querySelector('input[type="file"]').files[0]
// Upload a file...
$wire.upload('photo', file, (uploadedFilename) => {
// Success callback...
}, () => {
// Error callback...
}, (event) => {
// Progress callback...
// event.detail.progress contains a number between 1 and 100 as the upload progresses
}, () => {
// Cancelled callback...
})
// Upload multiple files...
$wire.uploadMultiple('photos', [file], successCallback, errorCallback, progressCallback, cancelledCallback)
// Remove single file from multiple uploaded files...
$wire.removeUpload('photos', uploadedFilename, successCallback)
// Cancel an upload...
$wire.cancelUpload('photos')
</script>配置
由于 Livewire 在开发者验证或存储之前会临时保存所有文件上传,它对所有文件上传假定了一些默认处理行为。
全局验证
默认情况下,Livewire 会用以下规则验证所有临时文件上传:file|max:12288(必须是小于 12MB 的文件)。
若要自定义这些规则,可以在应用的 config/livewire.php 文件中进行:
'temporary_file_upload' => [
// ...
'rules' => 'file|mimes:png,jpg,pdf|max:102400', // (100MB max, and only accept PNGs, JPEGs, and PDFs)
],全局中间件
临时文件上传端点默认会分配限流中间件。你可以通过以下配置选项自定义该端点使用的中间件:
'temporary_file_upload' => [
// ...
'middleware' => 'throttle:5,1', // Only allow 5 uploads per user per minute
],临时上传目录
临时文件会上传到指定磁盘的 livewire-tmp/ 目录。你可以通过以下配置选项自定义该目录:
'temporary_file_upload' => [
// ...
'directory' => 'tmp',
],另见
- 表单 — 在表单中处理文件上传
- 验证 — 验证已上传文件
- 加载状态 — 显示上传进度指示器
- wire:model — 将文件输入绑定到属性