LoginSignup
1
0

Laravel laravel-medialibraryで 画像保存からAlpine.jsでプレビューするまで

Last updated at Posted at 2024-03-23

画像保存ライブラリにlaravel-medialibraryを使用しました。

画像を持たせたいモデルにカラムを追加

今回はカラム名をlogoにします。
ファイルのpathを保存するためstringとなっています。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name')->nullable();
            $table->string('logo')->nullable();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('users');
    }
};

導入

composer require "spatie/laravel-medialibrary:^11.0.0"
php artisan vendor:publish --provider="Spatie\MediaLibraryPro\MediaLibraryProServiceProvider" --tag="media-library-pro-migrations"
php artisan migrate

以下のマイグレーションファイルが作成されます。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('media', function (Blueprint $table) {
            $table->id();

            $table->morphs('model');
            $table->uuid('uuid')->nullable()->unique();
            $table->string('collection_name');
            $table->string('name');
            $table->string('file_name');
            $table->string('mime_type')->nullable();
            $table->string('disk');
            $table->string('conversions_disk')->nullable();
            $table->unsignedBigInteger('size');
            $table->json('manipulations');
            $table->json('custom_properties');
            $table->json('generated_conversions');
            $table->json('responsive_images');
            $table->unsignedInteger('order_column')->nullable()->index();

            $table->nullableTimestamps();
        });
    }
};

メディアをモデルに関連付けるには、モデルが次のインターフェイスと特性を実装する必要があります。

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;//追加
use Spatie\MediaLibrary\InteractsWithMedia; //追加

class User extends Model implements HasMedia//追加
{
    use InteractsWithMedia;//追加
}

カスタムパスジェネレータを作成

デフォルトでは、ファイルはメディア オブジェクトの ID を名前として使用するディレクトリ内に保存されます。

media
---- 1
------ file.jpg
------ conversions
--------- small.jpg
--------- medium.jpg
--------- big.jpg

ですが、モデル名/id/logs/ファイル名にしたかったので保存先をカスタマイズしました。

カスタム パス ジェネレーターを作成します。

getPathメソッドで保存先のディレクトリを指定しています。

PathGeneratorインターフェースをオーバーライドした、CustomPathGeneratorクラスを作成します。

app/PathGenerators/CustomPathGenerator.php
<?php

namespace App\PathGenerators;

use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\MediaLibrary\Support\PathGenerator\PathGenerator;

class CustomPathGenerator implements PathGenerator
{
    /**
     * 関連付けされているモデル名を取得し、例company/インスタンスのid/logos/。のような保存先パスを指定。
     */
    public function getPath(Media $media): string
    {
        return strtolower(class_basename($media->model_type)) . '/' . $media->model_id . '/' . $media->collection_name . '/';
    }

    /**
     * 指定されたメディアオブジェクトの変換ファイル(サムネイル、リサイズされた画像など)を保存するためのパスを返す。
     */
    public function getPathForConversions(Media $media): string
    {
        return $this->getPath($media).'conversions/';
    }

    /**
     * 異なる画面サイズに対応するためのイメージを保存するパスを生成。
     */
    public function getPathForResponsiveImages(Media $media): string
    {
        return $this->getPath($media).'responsive-images/';
    }
}

次に、このカスタムパスジェネレータをLaravel MediaLibraryに登録する必要があります。これを行うには、config/medialibrary.php 設定ファイルを編集し、path_generator キーの値をカスタムパスジェネレータクラスに設定します。

'path_generator' => App\PathGenerators\CustomPathGenerator::class,
user
---- 1
------ logs
--------- small.jpg
---- 2
------ logs
--------- sample.jpg

モデルのidごとに任意のディレクトリ名(この場合はlogs)で保存されるようになります。

コントローラー保存処理

全体のコード

public function update(Request $request)
{

    //validation処理省略


    if ($request->hasFile('logo')) {
            // 既存のロゴ画像を削除して新しい画像を保存
            $company->clearMediaCollection('logos');
            $company->addMediaFromRequest('logo')->toMediaCollection('logos');
        }


    //リダイレクト処理
}

今回はプロフィールにロゴを持たせたので、更新処理に記述しました。
工夫した点は、このままだと既存の画像をファイルに残したまま新しく画像を追加してしまい、ストレージを余分に使用してしまいます。

if ($request->hasFile('logo')) {
        $company->addMediaFromRequest('logo')->toMediaCollection('logos');
    }

無駄なストレージを使うのを防ぐために下記を追加して、一旦全て削除し上書きされるようにしました。

  $company->clearMediaCollection('logos');

viewに表示

blade.php
<img src="{{ $company->getFirstMediaUrl('logos') }}">

Alpine.jsでファイル選択時にプレビュー

Alpine.jsを導入してある前提です。

通常はファイル選択しても、画像は表示されずファイル名のみが表示されます。
画像機能としては果たしていますが、選択した画像が表示される方がUXがいいと思い実装しました。
また、tailwindcssでレイアウトをしました。

まず、logoPreviewを定義します。input type=fileがクリックされると@changeによって画像のURLを取得するロジックが発火され、logoPreviewに画像のURLが入ります。

blade.php
//画像がある場合は初期値にその画像のURLをlogoPreviewに入れいます
 <div x-data="{ logoPreview: '{{ $user->hasMedia('logos') ? $user->getFirstMediaUrl('logos') : '' }}' }" class="sm:grid sm:grid-cols-3 sm:items-center sm:gap-4 sm:py-6">
        <label for="photo" class="block text-sm font-medium leading-6 text-gray-900">ロゴ</label>
        <div class="mt-2 sm:col-span-2 sm:mt-0">
            <div class="flex items-center gap-x-3">
                <label for="logo" class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500">
                    <span>画像をアップロード</span>
                    <input id="logo" name="logo" type="file" class="sr-only" @change="
                        logoPreview = $event.target.files.length > 0 ? URL.createObjectURL($event.target.files[0]) : '';
                        document.getElementById('logo-name').textContent = $event.target.files[0].name;">
                    <img x-show="logoPreview" :src="logoPreview" class="mt-2" style="max-width: 200px; max-height: 200px;">
                </label>
                <span id="logo-name" class="text-sm text-gray-600" x-text="logoPreview ? '' : 'ロゴがアップロードされていません。'"></span>
            </div>
            <x-input-error :messages="$errors->get('logo')" class="mt-1" />
        </div>
    </div>

スクリーンショット 2024-03-23 23.24.45.png

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