0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravel Livewire でリアクティブなファイルアップロード機能を実装してみる

Last updated at Posted at 2025-10-19

はじめに

この記事では

  • Laravel Livewireを使用したファイルアップロードで実現できること
  • その機能を実装する方法

についてご紹介します。

環境

  • PHP: 8.1.25
  • Laravel: 10.31.0
  • Livewire: 2.12.8

Gitリポジトリ

参考

本記事の内容は、主に以下の公式ドキュメントに基づいています。

できること

Laravel Livewireを使用したファイルアップロードでは、フロントエンド処理を最小限に抑えつつ、以下のSPAライクな実装が可能です。

機能 説明
リアルタイムバリデーション フォームを送信しなくても、ファイルを選択した瞬間にバリデーションすることができる
進捗インジケーター 現在のアップロード状況を表す簡易的なインジケーターを簡単に実装できる
非同期アップロード ページ遷移をせずに、バックグラウンドでファイルをサーバーにアップロードできる

そのため、Laravel標準のMPA(マルチページアプリケーション)と比較して、ユーザーはリアクティブに使用することができるため、ユーザー体験(UX)の向上を期待できます。

実装してみる

以下の要件で実装します。

  • 画像ファイルをアップロードすることができる
  • アップロードしたファイルを一覧で確認することができる

※ Livewire初期設定やルーティングなどは割愛します
※ 検証のため、セキュリティ面は考慮していません

1. Livewireコンポーネントファイルを作成する

コマンドを実行して、コンポーネントの実装に必要なファイルを作成します。

php artisan make:livewire PhotoUploader

以下のファイルが作成されます

  • src/app/Http/Livewire/PhotoUploader.php
  • resources/views/livewire/photo-uploader.blade.php

2. Livewireコンポーネントクラスの実装

src\app\Http\Livewire\PhotoUploader.php
<?php

namespace App\Http\Livewire;

use Illuminate\Support\Facades\Storage;
use Livewire\Component;
use Livewire\WithFileUploads;

class PhotoUploader extends Component
{
    use WithFileUploads;

    public $photo = null;
    public array $uploadedPhotos = [];

    /**
     * Livewireライフサイクルフック
     * アップロード済み画像一覧を設定する
     */
    public function mount()
    {
        $this->uploadedPhotos = $this->getUploadedPhotos();
    }

    public function render()
    {
        return view('livewire.photo-uploader');
    }

    /**
     * リアルタイムバリデーション
     * 
     * Livewireライフサイクルフックにより、ファイル選択時に呼び出されることで、
     * リアクティブなバリデーションが実現できる。
     */
    public function updatedPhoto()
    {
        $this->validatePhoto();
    }

    /**
     * ファイル保存処理
     * 
     * ファイル自体は選択時にサーバーの一時ディレクトリにアップロードされているため、
     * ここではそのファイルを永続化する処理を行う。
     */
    public function save()
    {
        if (is_null($this->photo)) {
            return;
        }
        $this->resetErrorBag();
        $this->validatePhoto();
        $this->photo->store('public/photos');
        $this->photo = null;
        $this->uploadedPhotos = $this->getUploadedPhotos();
    }

    /**
     * アップロード済みファイル一覧を取得する
     */
    private function getUploadedPhotos()
    {
        $uploadedPhotos = [];
        $files = Storage::files('public/photos');
        // リンクに変換
        foreach ($files as $file) {
            $uploadedPhotos[] = Storage::url($file);
        }
        return $uploadedPhotos;
    }

    /**
     * ファイルのバリデーション
     */
    private function validatePhoto()
    {
        $this->validate([
            'photo' => ['file', 'max:1024', 'mimes:jpg,jpeg,png'],
        ]);
    }
}

3. Bladeコンポーネントの実装

resources/views/livewire/photo-uploader.blade.php
<div>
    <h1>アップロード</h1>
    <form wire:submit.prevent="save">
        <p>
            <input type="file" wire:model="photo">
        </p>
        {{-- 進捗インジケータ --}}
        <div wire:loading wire:target="photo">アップロード中...</div>
        @error('photo')
            <p style="color:red;">
                {{ $message }}
            </p>
        @enderror
        <p>
            <button type="submit">保存</button>
        </p>
    </form>
    <h1>アップロード済み画像一覧</h1>
    @forelse ($uploadedPhotos as $uploadedPhoto)
        <ul>
            <img src="{{ $uploadedPhoto }}"></img></li>
        </ul>
    @empty
        <span>アップロードされたファイルはありません。</span>
    @endforelse
</div>

上記を実装することで、以下の画面が作成されます

image.png

動かしてみる

リアルタイムバリデーション

ファイルを選択した瞬間にバリデーションされることを確認できます。
※ 下記はCSVファイルを選択した例

image.png

進捗インジケータ

画像ファイルを選択すると、一瞬だけ進捗インジケータが出現します。
image.png

今回の実装では「アップロード中か否か」のみの状況を取得しましたが、Alpine.jsと組み合わせることにより、より詳細な状況を取得することが可能になるようです。
大容量のファイルを扱う際に、導入を検討するのが良さそうです。

非同期のファイルアップロード

ファイルを選択して保存ボタンをクリックすると、
アップロード済み画像一覧へリアクティブに画像が追加されることを確認できます。

image.png

また、storage/app/public/photos/配下にファイルが作成されていることから、ファイルが永続化されることも確認できます。

プロパティ変数$photoの型について

プロパティ変数の$photoは初期状態はnull、ファイル選択時にLivewire\TemporaryUploadedFileが設定されるようでした

検証ソース
public function updatedPhoto()
{
  dd($this->photo);
  ...

image.png

しかし、public ?TemporaryUploadedFile $photo;で型を指定すると、

Cannot assign string to property App\Http\Livewire\PhotoUploader::$photo of type ?Livewire\TemporaryUploadedFile

のエラーが出ました。

それもそもはずで、Livewireのプロパティで指定できる型は限定されており、Livewire\TemporaryUploadedFileは該当していないためですね。

参考

型を指定しなければエラーが起きない理由は、Livewire\WithFileUploadsTraitあたりがhydrate/dehydrate時などに、よしなにハンドリングしてくれているからだと推測しています。

そのため、型は無理に指定しない方が良さそうです。

Livewire\TemporaryUploadedFileについて

Livewire\TemporaryUploadedFileクラスを使用することで、
store()メソッドで永続化したり、
temporaryUrl()メソッドを使用することで、プレビュー用の一時的なURLを発行したり
とファイルを簡単に扱うことができます。

所感

Livewireで提供されているファイルアップロード機能を使用することで、モダンなUXが簡単に実装できることを実感しました。
しかし、モダンJS系と比較すると実現できる幅や利便性は少し下がってしまうかなとも感じました。
(例えば、input fileで表示される、選択中ファイルのファイルパスは、バインドしている変数と連動して変化してくれないなど...)
細かな課題はあるものの、実装コストの低さを鑑みれば、実用性は十分あるものだと感じています。

最後までご覧いただきありがとうございました!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?