0
0

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 laravel-medialibraryで S3に画像保存

Last updated at Posted at 2024-10-12

AWSのアクセスキーを取得

※IAMが作成されている前提です。
AWSコンソール画面に行き、IAMを選択します。サイドバーのユーザーを選択し、任意のユーザーを選択します。

セキュリティ認証情報のタブからアクセスキーの欄に行き、アクセスキーを作成するをクリックします。

※アクセスキーは一度しか表示されないので.envファイルに貼り付けてください。

.env
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=

バケット作成

S3からバケットを作成します。

スクリーンショット 2024-10-10 9.26.37.png

一意のバケット名を入力

パブリックアクセスを全てブロックを外します

その他はデフォルトの設定でいいです。

そして下部までいってバケットを作成ボタンを押してください。

エラー時の対応

ポリシーの設定を行わないと画像にアクセスした際に権限エラーとなります。

ポリシーの設定

バケット->アクセス許可 からバケットポリシーを設定します。
ポリシージェネレーターをクリックします。

  • Step1。Select Type of Policyを S3 Bucket Polictにします。
  • Step2。Prubcipalに* を入力します。
  • Step3。ActionsからGetObjectとGetObjectVersionを選択します。
  • Step4。ARMを入力してください。最後尾に/* をつけることを忘れないでください。
{
    "Version": "2012-10-17",
    "Id": "Policy********",
    "Statement": [
        {
            "Sid": "Stmt********",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject",
                "s3:GetObjectVersion"
            ],
            "Resource": "arn:aws:s3:::hoge/*"
        }
    ]
}

実装

AWS_ACCESS_KEY_ID=取得したアクセスキー
AWS_SECRET_ACCESS_KEY=取得したシークレットキー
AWS_DEFAULT_REGION=ap-northeast-1
AWS_BUCKET=作成したバケット名
AWS_USE_PATH_STYLE_ENDPOINT=false

Laravel-medialibraryを導入

composer require "spatie/laravel-medialibrary"
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider"

php artisan migrate

flysystem-aws-s3を導入

composer require league/flysystem-aws-s3-v3:^3.0

モデルに記述

使用するモデルにHasMediaInteractsWithMediaをインポートします。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;

class User extends Model implements HasMedia //こちら
{
    use InteractsWithMedia;//こちら
}

View

スクリーンショット 2024-10-12 14.15.09.png

<form class="space-y-6" action="{{ route('user.image_upload') }}" method="POST" enctype="multipart/form-data" >
    @method('PUT')
    <div class="col-span-full">
        <label for="cover-photo" class="block text-sm font-medium leading-6 text-gray-900">画像</label>
        <div class="mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 px-6 py-10">
            <div class="text-center">
                <svg class="mx-auto h-12 w-12 text-gray-300" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
                    <path fill-rule="evenodd" d="M1.5 6a2.25 2.25 0 012.25-2.25h16.5A2.25 2.25 0 0122.5 6v12a2.25 2.25 0 01-2.25 2.25H3.75A2.25 2.25 0 011.5 18V6zM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0021 18v-1.94l-2.69-2.689a1.5 1.5 0 00-2.12 0l-.88.879.97.97a.75.75 0 11-1.06 1.06l-5.16-5.159a1.5 1.5 0 00-2.12 0L3 16.061zm10.125-7.81a1.125 1.125 0 112.25 0 1.125 1.125 0 01-2.25 0z" clip-rule="evenodd" />
                </svg>
                <div>
                    <label for="image">画像を選択:</label>
                    <input type="file" id="image" name="image" accept="image/*">
                </div>
                <div class="file-name" id="file-name">選択されたファイル: なし</div>
                    <p class="text-xs leading-5 text-gray-600">PNG, JPG, GIF up to 10MB</p>
                </div>
            </div>
        </div>
    </div>
    <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
        追加
    </button>
</form>

ルートを作成

Route::controller(UserImageController::class)->group(function () {
    Route::get('image/create', 'index')->name('user.image_upload_form');
    Route::post('image/store', 'store')->name('user.image_upload');
});

コントローラーを作成

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class UserImageController extends Controller
{

    /**
     * 画像追加フォームを表示
     */
    public function create(): View
    {
        return view('user.image_upload');
    }

     /**
     * 商品を追加する
     */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg',
        ]);

        $image = User::create($validated);

        // メディアの追加
        if ($request->hasFile('image')) {
            $filePath = Str::uuid() . '.' . $request->file('image')->getClientOriginalExtension();
            $item->addMediaFromRequest('image')
                 ->usingFileName($filePath)
                 ->toMediaCollection('images', 's3');
        }

        return redirect()->route('user.image_upload_form');
    }
}

このままですと、[id]/ファイル名のように作成されてしまいます。今回の意図はコレクション名/ファイル名で保存したいのでカスタムパスジェネレーターを作成して設定を行います。

Laravel Medialibrary の設定ファイルを作成

php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="config"

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

<?php

namespace App\PathGenerators;

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

class CustomPathGenerator implements PathGenerator
{
    /**
     * メディアファイルの基本パスを取得します。
     *
     * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media
     * @return string
     */
    public function getPath(Media $media): string
    {
        // モデルの短縮クラス名(例: 'User')を取得
        $modelName = class_basename($media->model);

        // モデル名を複数形に変換し、小文字にする(例: 'users')
        $folderName = Str::plural(Str::snake($modelName));

        return "{$folderName}/";
    }

    /**
     * メディアの変換ファイルのパスを取得します。
     *
     * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media
     * @return string
     */
    public function getPathForConversions(Media $media): string
    {
        $modelName = class_basename($media->model);
        $folderName = Str::plural(Str::snake($modelName));

        return "{$folderName}/conversions/";
    }

    /**
     * レスポンシブ画像のパスを取得します。
     *
     * @param \Spatie\MediaLibrary\MediaCollections\Models\Media $media
     * @return string
     */
    public function getPathForResponsiveImages(Media $media): string
    {
        $modelName = class_basename($media->model);
        $folderName = Str::plural(Str::snake($modelName));

        return "{$folderName}/responsive/";
    }
}

カスタムパスジェネレータを登録

configmedia-library.php
<?php

return [
  'path_generator' => App\PathGenerators\CustomPathGenerator::class,
]

表示の仕方

@if($recruit->hasMedia('images')
    <img src="{{ $recruit->getFirstMediaUrl('images') }}"  class="w-32 h-32 object-contain">
@endif


## 更新処理
```php
public function update(Request $request, User $user)
    {
        $validated = $request->validate([
            'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg',
        ]);
        
        $user->update($validated);

        // メディアの更新
        if ($request->hasFile('image')) {
            // 既存の画像を削除(オプション)
            if ($user->hasMedia('images')) {
                $user->clearMediaCollection('images');
            }

            // 新しい画像をアップロード
            $user->addMediaFromRequest('image')
                     ->usingFileName(Str::uuid() . '.' . $request->file('image')->getClientOriginalExtension())
                     ->toMediaCollection('images', 's3');
        }
    }
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?