#23 FilamentPHP応用

ファイルアップロードの応用——画像リサイズ・複数ファイル・S3連携

FileUploadの基本おさらい

FilamentのFileUploadコンポーネントはLivewireのファイルアップロード機能をベースにしており、ドラッグ&ドロップ対応・プレビュー付きのUIが標準で提供されます。

use Filament\Forms\Components\FileUpload;

FileUpload::make('thumbnail')
    ->label('サムネイル')
    ->image()
    ->disk('public')
    ->directory('posts/thumbnails')
    ->visibility('public')

画像リサイズの設定

FileUpload::make('avatar')
    ->label('アバター画像')
    ->image()
    ->imageEditor()                  // ブラウザ内で画像を編集(トリミング可能)
    ->imageEditorAspectRatios([
        null,       // フリー
        '1:1',      // 正方形
        '16:9',     // ワイドスクリーン
        '4:3',      // 標準
    ])
    ->imageCropAspectRatio('1:1')    // デフォルトのアスペクト比
    ->imageResizeTargetWidth(400)    // リサイズ後の幅(px)
    ->imageResizeTargetHeight(400)   // リサイズ後の高さ(px)
    ->imageResizeMode('cover')       // cover | contain | force
    ->maxSize(2048)                  // 最大ファイルサイズ(KB)
    ->acceptedFileTypes(['image/jpeg', 'image/png', 'image/webp'])

->imageEditor()を有効にするとアップロード後に画像の切り抜きや回転ができるUIが表示されます。

複数ファイルのアップロード

FileUpload::make('product_images')
    ->label('商品画像')
    ->image()
    ->multiple()
    ->maxFiles(8)
    ->minFiles(1)
    ->reorderable()                  // ドラッグで並び替え可能
    ->appendFiles()                  // 既存ファイルに追加(上書きではなく)
    ->downloadable()                 // ダウンロードボタンを表示
    ->openable()                     // 別タブで開くボタンを表示
    ->disk('public')
    ->directory('products/images')
    ->imageResizeTargetWidth(1200)
    ->imageResizeTargetHeight(900)

複数ファイルの値はJSON配列でDBに保存されます。モデルのキャストを設定してください。

// app/Models/Product.php
protected $casts = [
    'product_images' => 'array',
];

S3へのアップロード設定

.envにS3の設定を追加します。

AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_DEFAULT_REGION=ap-northeast-1
AWS_BUCKET=your-bucket-name
AWS_URL=https://your-bucket.s3.ap-northeast-1.amazonaws.com

config/filesystems.phpでS3ディスクを設定します(Laravelデフォルトで含まれています)。

FileUpload::make('document')
    ->label('ドキュメント')
    ->disk('s3')                         // S3ディスクを指定
    ->directory('documents')
    ->visibility('private')              // 非公開ファイルの場合
    ->acceptedFileTypes([
        'application/pdf',
        'application/msword',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    ])
    ->maxSize(10240)                     // 10MB
    ->downloadable()

プライベートファイルのダウンロードには署名付きURLが必要です。

// モデルのアクセサでS3署名付きURLを生成
public function getDocumentUrlAttribute(): ?string
{
    if (! $this->document) {
        return null;
    }
    return Storage::disk('s3')->temporaryUrl(
        $this->document,
        now()->addMinutes(30)
    );
}

Spatie MediaLibraryとの連携

より高度なメディア管理が必要な場合はspatie/laravel-medialibraryとの連携プラグインを使います。

composer require filament/spatie-laravel-media-library-plugin
php artisan media-library:install

モデルにInteractsWithMediaトレイトを追加します。

// app/Models/Post.php

use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

class Post extends Model implements HasMedia
{
    use InteractsWithMedia;

    public function registerMediaCollections(): void
    {
        $this->addMediaCollection('thumbnail')
            ->singleFile()           // 1ファイルのみ
            ->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/webp']);

        $this->addMediaCollection('gallery')
            ->acceptsMimeTypes(['image/jpeg', 'image/png']);
    }

    public function registerMediaConversions(?Media $media = null): void
    {
        $this->addMediaConversion('thumb')
            ->width(400)
            ->height(300)
            ->sharpen(10);

        $this->addMediaConversion('large')
            ->width(1200)
            ->height(900);
    }
}

フォームでSpatieMediaLibraryFileUploadを使います。

use Filament\Forms\Components\SpatieMediaLibraryFileUpload;

SpatieMediaLibraryFileUpload::make('thumbnail')
    ->label('サムネイル')
    ->collection('thumbnail')       // MediaCollectionの名前
    ->image()
    ->imageEditor()
    ->conversion('thumb'),          // プレビューにサムネイル変換を使用

SpatieMediaLibraryFileUpload::make('gallery')
    ->label('ギャラリー')
    ->collection('gallery')
    ->multiple()
    ->reorderable()
    ->maxFiles(10),

ファイルアップロードのバリデーション

FileUpload::make('resume')
    ->label('履歴書(PDF)')
    ->acceptedFileTypes(['application/pdf'])
    ->maxSize(5120)                  // 5MB
    ->rules([
        'file',
        'mimetypes:application/pdf',
        'max:5120',
    ])

アップロード時のファイル名カスタマイズ

FileUpload::make('document')
    ->getUploadedFileNameForStorageUsing(
        fn (TemporaryUploadedFile $file, callable $get): string =>
            Str::slug($get('title')) . '-' . now()->format('YmdHis') . '.' . $file->getClientOriginalExtension()
    )

コツ・注意点・ハマりポイント

コツ: ->storeFileNamesIn('original_filenames')を使うと元のファイル名を別カラムに保存できます。ユーザーが元のファイル名でダウンロードできるように保持したい場合に有用です。

注意点: S3にプライベートファイルをアップロードする場合、FilamentのデフォルトのプレビューURLはS3の公開URLを使おうとします。プライベートファイルのプレビューには->getUploadedFileUrlUsing()で署名付きURLを返すカスタマイズが必要です。

ハマりポイント: Livewireのファイルアップロードは一時的にstorage/app/livewire-tmp/に保存されます。フォームを送信しないとstorage/app/public/への移動が行われません。フォームキャンセル時に一時ファイルが残り続けるため、php artisan livewire:cleanupや定期Cronでの一時ファイルクリーニングを設定してください。

まとめ

FilamentのFileUploadはブラウザ内画像編集・複数ファイル・S3ストレージまで幅広くカバーしています。Spatie MediaLibraryプラグインと組み合わせると、複数サイズのサムネイル自動生成・コレクション管理・並び替えなど本格的なメディア管理が実現できます。