加载状态

未匹配的标注
本文档最新版为 2.x,旧版本可能放弃维护,推荐阅读最新版!

Livewire offers powerful support for uploading files within your components.

First, add the WithFileUploads trait to your component. Once this trait has been added to your component, you can use wire:model on file inputs as if they were any other input type and Livewire will take care of the rest.

Here’s an example of a simple component that handles uploading a photo:

<?php

namespace App\Livewire;

use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Rule;

class UploadPhoto extends Component
{
    use WithFileUploads;

    #[Rule('image|max:1024')] // 1MB Max
    public $photo;

    public function save()
    {
        $this->photo->store('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] The term “upload” is reserved
The term “upload” is reserved by Livewire. You cannot use it as a method or property name.

From the developer’s perspective, handling file inputs is no different than handling any other input type: Add wire:model to the <input> tag and everything else is taken care of for you.

However, more is happening under the hood to make file uploads work in Livewire. Here’s a glimpse at what goes on when a user selects a file to upload:

  1. When a new file is selected, Livewire’s JavaScript makes an initial request to the component on the server to get a temporary “signed” upload URL.
  2. Once the URL is received, JavaScript does the actual “upload” to the signed URL, storing the upload in a temporary directory designated by Livewire and returning the new temporary file’s unique hash ID.
  3. Once the file is uploaded and the unique hash ID is generated, Livewire’s JavaScript makes a final request to the component on the server, telling it to “set” the desired public property to the new temporary file.
  4. Now, the public property (in this case, $photo) is set to the temporary file upload and is ready to be stored or validated at any point.

Storing uploaded files

The previous example demonstrates the most basic storage scenario: moving the temporarily uploaded file to the “photos” directory on the application’s default filesystem disk.

However, you may want to customize the file name of the stored file or even specify a specific storage “disk” to keep the file on (such as S3).

Livewire honors the same APIs Laravel uses for storing uploaded files, so feel free to consult Laravel’s file upload documentation. However, below are a few common storage scenarios and examples:

public function save()
{
    // Store the file in the "photos" directory of the default filesystem disk
    $this->photo->store('photos');

    // Store the file in the "photos" directory in a configured "s3" disk
    $this->photo->store('photos', 's3');

    // Store the file in the "photos" directory with the filename "avatar.png"
    $this->photo->storeAs('photos', 'avatar');

    // Store the file in the "photos" directory in a configured "s3" disk with the filename "avatar.png"
    $this->photo->storeAs('photos', 'avatar', 's3');

    // Store the file in the "photos" directory, with "public" visibility in a configured "s3" disk
    $this->photo->storePublicly('photos', 's3');

    // Store the file in the "photos" directory, with the name "avatar.png", with "public" visibility in a configured "s3" disk
    $this->photo->storePubliclyAs('photos', 'avatar', 's3');
}

Handling multiple files

Livewire automatically handles multiple file uploads by detecting the multiple attribute on the <input> tag.

For example, below is a component with an array property named $photos. By adding multiple to the form’s file input, Livewire will automatically append new files to this array:

use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Rule;

class UploadPhotos extends Component
{
    use WithFileUploads;

    #[Rule(['photos.*' => 'image|max:1024'])]
    public $photos = [];

    public function save()
    {
        foreach ($this->photos as $photo) {
            $photo->store('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>

File validation

Like we’ve discussed, validating file uploads with Livewire is the same as handling file uploads from a standard Laravel controller.

[!warning] Ensure S3 is properly configured
Many of the validation rules relating to files require access to the file. When uploading directly to S3, these validation rules will fail if the S3 file object is not publicly accessible.

For more information on file validation, consult Laravel’s file validation documentation.

Temporary preview URLs

After a user chooses a file, you should typically show them a preview of that file before they submit the form and store the file.

Livewire makes this trivial by using the ->temporaryUrl() method on uploaded files.

[!info] Temporary URLs are restricted to images
For security reasons, temporary upload URLs are only supported on files with image MIME types.

Let’s explore an example of a file upload with an image preview:

use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Rule;

class UploadPhoto extends Component
{
    use WithFileUploads;

    #[Rule('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>

As previously discussed, Livewire stores temporary files in a non-public directory; therefore, typically there’s no simple way to expose a temporary, public URL to your users for image previewing.

However, Livewire solves this issue by providing a temporary, signed URL that pretends to be the uploaded image so your page can show an image preview to your users.

This URL is protected against showing files in directories above the temporary directory. And, because it’s signed, users can’t abuse this URL to preview other files on your system.

[!tip] S3 temporary signed URLs
If you’ve configured Livewire to use S3 for temporary file storage, calling ->temporaryUrl() will generate a temporary, signed URL to S3 directly so that image previews aren’t loaded from your Laravel application server.

Testing file uploads

You can use Laravel’s existing file upload testing helpers to test file uploads.

Below is a complete example of testing the UploadPhoto component with Livewire:

<?php

namespace Tests\Feature\Livewire;

use Illuminate\Support\Facades\Storage;
use App\Livewire\UploadPhoto;
use Livewire\Livewire;
use Tests\TestCase;

class UploadPhotoTest extends TestCase
{
    /** @test */
    public function 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');
    }
}

Below is an example of the UploadPhoto component required to make the previous test pass:

use Livewire\Component;
use Livewire\WithFileUploads;

class UploadPhoto extends Component
{
    use WithFileUploads;

    public $photo;

    public function upload($name)
    {
        $this->photo->storeAs('/', $name, disk: 'avatars');
    }

    // ...
}

For more information on testing file uploads, please consult Laravel’s file upload testing documentation.

Uploading directly to Amazon S3

As previously discussed, Livewire stores all file uploads in a temporary directory until the developer permanently stores the file.

By default, Livewire uses the default filesystem disk configuration (usually local) and stores the files within a livewire-tmp/ directory.

Consequently, file uploads are always utilizing your application server, even if you choose to store the uploaded files in an S3 bucket later.

If you wish to bypass your application server and instead store Livewire’s temporary uploads in an S3 bucket, you can configure that behavior in your application’s config/livewire.php configuration file. First, set livewire.temporary_file_upload.disk to s3 (or another custom disk that uses the s3 driver):

return [
    // ...
    'temporary_file_upload' => [
        'disk' => 's3',
        // ...
    ],
];

Now, when a user uploads a file, the file will never actually be stored on your server. Instead, it will be uploaded directly to your S3 bucket within the livewire-tmp/ sub-directory.

[!info] Publishing Livewire’s configuration file
Before customizing the file upload disk, you must first publish Livewire’s configuration file to your application’s /config directory by running the following command:

php artisan livewire:publish --config

Configuring automatic file cleanup

Livewire’s temporary upload directory will fill up with files quickly; therefore, it’s essential to configure S3 to clean up files older than 24 hours.

To configure this behavior, run the following Artisan command from the environment that is utilizing an S3 bucket for file uploads:

php artisan livewire:configure-s3-upload-cleanup

Now, any temporary files older than 24 hours will be cleaned up by S3 automatically.

[!info]
If you are not using S3 for file storage, Livewire will handle file cleanup automatically and there is no need to run the command above.

Loading indicators

Although wire:model for file uploads works differently than other wire:model input types under the hood, the interface for showing loading indicators remains the same.

You can display a loading indicator scoped to the file upload like so:

<input type="file" wire:model="photo">

<div wire:loading wire:target="photo">Uploading...</div>

Now, while the file is uploading, the “Uploading…” message will be shown and then hidden when the upload is finished.

For more information on loading states, check out our comprehensive loading state documentation.

Progress indicators

Every Livewire file upload operation dispatches JavaScript events on the corresponding <input> element, allowing custom JavaScript to intercept the events:

Event Description
livewire-upload-start Dispatched when the upload starts
livewire-upload-finish Dispatched if the upload is successfully finished
livewire-upload-error Dispatched if the upload fails
livewire-upload-progress An event containing the upload progress percentage as the upload progresses

Below is an example of wrapping a Livewire file upload in an Alpine component to display an upload progress bar:

<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-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>

JavaScript upload API

Integrating with third-party file-uploading libraries often requires more control than a simple <input type="file"> tag.

For these cases, Livewire exposes dedicated JavaScript functions.

These functions exist on a JavaScript component object, which can be accessed using Livewire’s convenient @this Blade directive from within your Livewire component’s template:

<script>
    let file = document.querySelector('input[type="file"]').files[0]

    // Upload a file
    @this.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
    })

    // Upload multiple files
    @this.uploadMultiple('photos', [file], successCallback, errorCallback, progressCallback)

    // Remove single file from multiple uploaded files
    @this.removeUpload('photos', uploadedFilename, successCallback)
</script>

Configuration

Because Livewire stores all file uploads temporarily before the developer can validate or store them, it assumes some default handling behavior for all file uploads.

Global validation

By default, Livewire will validate all temporary file uploads with the following rules: file|max:12288 (Must be a file less than 12MB).

If you wish to customize these rules, you can do so inside your application’s config/livewire.php file:

'temporary_file_upload' => [
    // ...
    'rules' => 'file|mimes:png,jpg,pdf|max:102400', // (100MB max, and only accept PNGs, JPEGs, and PDFs)
],

Global middleware

The temporary file upload endpoint is assigned a throttling middleware by default. You can customize exactly what middleware this endpoint uses via the following configuration option:

'temporary_file_upload' => [
    // ...
    'middleware' => 'throttle:5,1', // Only allow 5 uploads per user per minute
],

Temporary upload directory

Temporary files are uploaded to the specified disk’s livewire-tmp/ directory. You can customize this directory via the following configuration option:

'temporary_file_upload' => [
    // ...
    'directory' => 'tmp',
],

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
发起讨论 只看当前版本


暂无话题~