Skip to content

파일 업로드

Livewire는 컴포넌트 내에서 파일 업로드를 강력하게 지원합니다.

먼저, 컴포넌트에 WithFileUploads 트레이트를 추가하세요. 이 트레이트를 컴포넌트에 추가하면, 파일 입력란에도 다른 입력 타입과 마찬가지로 wire:model을 사용할 수 있으며, 나머지는 Livewire가 알아서 처리합니다.

아래는 사진 업로드를 처리하는 간단한 컴포넌트 예시입니다:

php
<?php

namespace App\Livewire;

use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Validate;

class UploadPhoto extends Component
{
    use WithFileUploads;

    #[Validate('image|max:1024')] // 최대 1MB
    public $photo;

    public function save()
    {
        $this->photo->store(path: 'photos');
    }
}
blade
<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>

"upload" 메서드는 예약어입니다

위 예제에서 "upload" 메서드 대신 "save" 메서드를 사용하는 것을 주목하세요. 이것은 흔히 발생하는 실수입니다. "upload"라는 용어는 Livewire에서 예약되어 있습니다. 컴포넌트에서 메서드나 속성 이름으로 사용할 수 없습니다.

개발자 입장에서는 파일 입력을 다루는 것이 다른 입력 타입을 다루는 것과 다르지 않습니다: <input> 태그에 wire:model을 추가하면 나머지는 모두 자동으로 처리됩니다.

하지만, Livewire에서 파일 업로드가 동작하기 위해 내부적으로 더 많은 일이 일어납니다. 사용자가 업로드할 파일을 선택할 때 어떤 일이 일어나는지 살펴보겠습니다:

  1. 새 파일이 선택되면, Livewire의 JavaScript가 서버의 컴포넌트에 임시 "서명된" 업로드 URL을 요청합니다.
  2. URL을 받으면, JavaScript가 실제로 해당 서명된 URL로 "업로드"를 수행하여, Livewire가 지정한 임시 디렉터리에 파일을 저장하고, 새 임시 파일의 고유 해시 ID를 반환합니다.
  3. 파일이 업로드되고 고유 해시 ID가 생성되면, Livewire의 JavaScript가 서버의 컴포넌트에 마지막 요청을 보내, 원하는 공개 속성을 새 임시 파일로 "설정"하도록 알립니다.
  4. 이제 공개 속성(이 경우 $photo)이 임시 파일 업로드로 설정되어, 언제든 저장하거나 검증할 준비가 됩니다.

업로드된 파일 저장하기

이전 예제는 가장 기본적인 저장 시나리오를 보여줍니다: 임시로 업로드된 파일을 애플리케이션의 기본 파일 시스템 디스크의 "photos" 디렉터리로 이동합니다.

하지만, 저장되는 파일의 이름을 커스터마이즈하거나, 파일을 저장할 특정 스토리지 "디스크"(예: S3)를 지정하고 싶을 수도 있습니다.

원본 파일 이름

임시 업로드의 원본 파일 이름은 ->getClientOriginalName() 메서드를 호출하여 접근할 수 있습니다.

Livewire는 업로드된 파일을 저장할 때 Laravel이 사용하는 동일한 API를 따르므로, Laravel의 파일 업로드 문서를 참고하셔도 됩니다. 하지만, 아래에 몇 가지 일반적인 저장 시나리오와 예시를 소개합니다:

php
public function save()
{
    // 기본 파일 시스템 디스크의 "photos" 디렉터리에 파일 저장
    $this->photo->store(path: 'photos');

    // 설정된 "s3" 디스크의 "photos" 디렉터리에 파일 저장
    $this->photo->store(path: 'photos', options: 's3');

    // "photos" 디렉터리에 "avatar.png"라는 파일명으로 저장
    $this->photo->storeAs(path: 'photos', name: 'avatar');

    // 설정된 "s3" 디스크의 "photos" 디렉터리에 "avatar.png"라는 파일명으로 저장
    $this->photo->storeAs(path: 'photos', name: 'avatar', options: 's3');

    // 설정된 "s3" 디스크의 "photos" 디렉터리에 "public" 가시성으로 저장
    $this->photo->storePublicly(path: 'photos', options: 's3');

    // 설정된 "s3" 디스크의 "photos" 디렉터리에 "avatar.png"라는 이름과 "public" 가시성으로 저장
    $this->photo->storePubliclyAs(path: 'photos', name: 'avatar', options: 's3');
}

여러 파일 처리하기

Livewire는 <input> 태그에 multiple 속성이 있는지 감지하여 자동으로 여러 파일 업로드를 처리합니다.

예를 들어, 아래는 $photos라는 배열 속성을 가진 컴포넌트입니다. 폼의 파일 입력란에 multiple을 추가하면, Livewire가 새 파일을 이 배열에 자동으로 추가합니다:

php
use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Validate;

class UploadPhotos extends Component
{
    use WithFileUploads;

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

    public function save()
    {
        foreach ($this->photos as $photo) {
            $photo->store(path: 'photos');
        }
    }
}
blade
<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 컨트롤러에서 파일 업로드를 처리하는 것과 동일합니다.

S3가 올바르게 구성되어 있는지 확인하세요

파일과 관련된 많은 검증 규칙은 파일에 접근할 수 있어야 합니다. S3에 직접 업로드할 때, S3 파일 객체가 공개적으로 접근 가능하지 않으면 이러한 검증 규칙이 실패합니다.

파일 검증에 대한 자세한 내용은 Laravel의 파일 검증 문서를 참고하세요.

임시 미리보기 URL

사용자가 파일을 선택한 후, 일반적으로 폼을 제출하고 파일을 저장하기 전에 해당 파일의 미리보기를 보여주는 것이 좋습니다.

Livewire는 업로드된 파일의 ->temporaryUrl() 메서드를 사용하여 이 작업을 매우 쉽게 만듭니다.

임시 URL은 이미지로 제한됩니다

보안상의 이유로, 임시 미리보기 URL은 이미지 MIME 타입의 파일에만 지원됩니다.

이미지 미리보기가 포함된 파일 업로드 예제를 살펴보겠습니다:

php
use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Validate;

class UploadPhoto extends Component
{
    use WithFileUploads;

    #[Validate('image|max:1024')]
    public $photo;

    // ...
}
blade
<form wire:submit="save">
    @if ($photo) 
        <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을 악용하여 시스템의 다른 파일을 미리볼 수 없습니다.

S3 임시 서명 URL

임시 파일 저장소로 S3를 사용하도록 Livewire를 구성했다면, ->temporaryUrl()을 호출하면 이미지 미리보기가 Laravel 애플리케이션 서버가 아닌 S3에서 직접 로드되도록 S3에 대한 임시 서명 URL이 생성됩니다.

파일 업로드 테스트하기

Laravel의 기존 파일 업로드 테스트 헬퍼를 사용하여 파일 업로드를 테스트할 수 있습니다.

아래는 Livewire로 UploadPhoto 컴포넌트를 테스트하는 전체 예제입니다:

php
<?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');
    }
}

아래는 위 테스트를 통과시키기 위해 필요한 UploadPhoto 컴포넌트 예시입니다:

php
use Livewire\Component;
use Livewire\WithFileUploads;

class UploadPhoto 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 버킷에 직접 저장하고 싶다면, 애플리케이션의 config/livewire.php 설정 파일에서 해당 동작을 구성할 수 있습니다. 먼저, livewire.temporary_file_upload.disks3(또는 s3 드라이버를 사용하는 다른 커스텀 디스크)로 설정하세요:

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

이제 사용자가 파일을 업로드하면, 파일은 실제로 서버에 저장되지 않고, S3 버킷의 livewire-tmp/ 하위 디렉터리에 직접 업로드됩니다.

Livewire 설정 파일 퍼블리싱

파일 업로드 디스크를 커스터마이즈하기 전에, 다음 명령어를 실행하여 Livewire의 설정 파일을 애플리케이션의 /config 디렉터리에 먼저 퍼블리싱해야 합니다:

shell
php artisan livewire:publish --config

자동 파일 정리 구성하기

Livewire의 임시 업로드 디렉터리는 빠르게 파일로 가득 찰 수 있으므로, 24시간이 지난 파일을 S3에서 자동으로 정리하도록 구성하는 것이 중요합니다.

이 동작을 구성하려면, S3 버킷을 파일 업로드에 사용하는 환경에서 다음 Artisan 명령어를 실행하세요:

shell
php artisan livewire:configure-s3-upload-cleanup

이제 24시간이 지난 임시 파일은 S3에서 자동으로 정리됩니다.

INFO

S3를 파일 저장소로 사용하지 않는 경우, Livewire가 파일 정리를 자동으로 처리하므로 위 명령어를 실행할 필요가 없습니다.

로딩 인디케이터

파일 업로드를 위한 wire:model은 내부적으로 다른 wire:model 입력 타입과 다르게 동작하지만, 로딩 인디케이터를 표시하는 인터페이스는 동일하게 유지됩니다.

아래와 같이 파일 업로드에 범위를 둔 로딩 인디케이터를 표시할 수 있습니다:

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

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

이제 파일이 업로드되는 동안 "Uploading..." 메시지가 표시되고, 업로드가 끝나면 숨겨집니다.

로딩 상태에 대한 자세한 내용은 로딩 상태 문서를 참고하세요.

진행률 인디케이터

모든 Livewire 파일 업로드 작업은 해당 <input> 요소에서 JavaScript 이벤트를 디스패치하여, 커스텀 JavaScript가 이벤트를 가로챌 수 있도록 합니다:

이벤트설명
livewire-upload-start업로드가 시작될 때 디스패치됨
livewire-upload-finish업로드가 성공적으로 끝나면 디스패치됨
livewire-upload-cancel업로드가 조기에 취소되면 디스패치됨
livewire-upload-error업로드가 실패하면 디스패치됨
livewire-upload-progress업로드 진행률 퍼센트가 포함된 이벤트

아래는 Livewire 파일 업로드를 Alpine 컴포넌트로 감싸서 업로드 진행률 바를 표시하는 예시입니다:

blade
<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"
    >
        <!-- 파일 입력 -->
        <input type="file" wire:model="photo">

        <!-- 진행률 바 -->
        <div x-show="uploading">
            <progress max="100" x-bind:value="progress"></progress>
        </div>
    </div>

    <!-- ... -->
</form>

업로드 취소하기

업로드가 오래 걸릴 경우, 사용자가 업로드를 취소하고 싶을 수 있습니다. 이 기능은 JavaScript에서 Livewire의 $cancelUpload() 함수를 사용하여 제공할 수 있습니다.

아래는 wire:click을 사용하여 클릭 이벤트를 처리하는 Livewire 컴포넌트에서 "Cancel Upload" 버튼을 만드는 예시입니다:

blade
<form wire:submit="save">
    <!-- 파일 입력 -->
    <input type="file" wire:model="photo">

    <!-- 업로드 취소 버튼 -->
    <button type="button" wire:click="$cancelUpload('photo')">Cancel Upload</button>

    <!-- ... -->
</form>

"Cancel upload"를 누르면 파일 업로드 요청이 중단되고 파일 입력란이 초기화됩니다. 사용자는 이제 다른 파일로 업로드를 다시 시도할 수 있습니다.

또는, Alpine에서 아래와 같이 cancelUpload(...)를 호출할 수도 있습니다:

blade
<button type="button" x-on:click="$wire.cancelUpload('photo')">Cancel Upload</button>

JavaScript 업로드 API

서드파티 파일 업로드 라이브러리와 통합하려면 단순한 <input type="file" wire:model="..."> 요소보다 더 많은 제어가 필요할 수 있습니다.

이러한 경우를 위해 Livewire는 전용 JavaScript 함수를 제공합니다.

이 함수들은 JavaScript 컴포넌트 객체에 존재하며, Livewire 컴포넌트의 템플릿 내에서 Livewire의 편리한 $wire 객체를 통해 접근할 수 있습니다:

blade
@script
<script>
    let file = $wire.el.querySelector('input[type="file"]').files[0]

    // 파일 업로드...
    $wire.upload('photo', file, (uploadedFilename) => {
        // 성공 콜백...
    }, () => {
        // 에러 콜백...
    }, (event) => {
        // 진행률 콜백...
        // event.detail.progress는 업로드 진행에 따라 1~100 사이의 숫자를 포함합니다
    }, () => {
        // 취소 콜백...
    })

    // 여러 파일 업로드...
    $wire.uploadMultiple('photos', [file], successCallback, errorCallback, progressCallback, cancelledCallback)

    // 여러 개 업로드된 파일 중 하나 제거...
    $wire.removeUpload('photos', uploadedFilename, successCallback)

    // 업로드 취소...
    $wire.cancelUpload('photos')
</script>
@endscript

설정

Livewire는 개발자가 파일을 검증하거나 저장하기 전에 모든 파일 업로드를 임시로 저장하므로, 모든 파일 업로드에 대해 기본 처리 동작을 가정합니다.

전역 검증

기본적으로 Livewire는 모든 임시 파일 업로드를 다음 규칙으로 검증합니다: file|max:12288 (12MB 미만의 파일이어야 함).

이 규칙을 커스터마이즈하고 싶다면, 애플리케이션의 config/livewire.php 파일에서 설정할 수 있습니다:

php
'temporary_file_upload' => [
    // ...
    'rules' => 'file|mimes:png,jpg,pdf|max:102400', // (최대 100MB, PNG, JPEG, PDF만 허용)
],

전역 미들웨어

임시 파일 업로드 엔드포인트에는 기본적으로 쓰로틀링 미들웨어가 할당되어 있습니다. 아래 설정 옵션을 통해 이 엔드포인트에 사용할 미들웨어를 정확히 커스터마이즈할 수 있습니다:

php
'temporary_file_upload' => [
    // ...
    'middleware' => 'throttle:5,1', // 사용자당 분당 5회 업로드만 허용
],

임시 업로드 디렉터리

임시 파일은 지정된 디스크의 livewire-tmp/ 디렉터리에 업로드됩니다. 아래 설정 옵션을 통해 이 디렉터리를 커스터마이즈할 수 있습니다:

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

나를 위한 문서 한글화