Laravel-medialibraryを使用して簡単にファイルを保存する(画像,動画,PDFなど)

# この記事の目的
今回Laravel-medialibraryを使うことになり予習として。あまりLaravel-medialibraryの記事を見かけなかったので必死でドキュメント読みました。
誰にでもわかる備忘録として書いていきたいと思います。

2023/12/1　追記してます。記事の最後をご確認ください。

# Laravel-medialibraryとは

データベースやクラウドストレージなどのバックエンドに画像やファイルを保存、管理するための便利な機能を提供するLaravel用のパッケージです。

　**-Laravel-medialibraryでできること-**
　**・ファイルのアップロードと保存**
　**・モデルとメディアの関連付け**
　**・ファイルのバージョニング（複数のバージョンを保存）**
　**・画像のリサイズ、クロップ、フィルター処理、回転などの操作**
　**・メディアファイルの管理と検索**

そのほかにも様々な機能があるようです。詳しくは[ドキュメント](https://spatie.be/docs/laravel-medialibrary/v10/introduction)をご確認ください。

# 環境
・Laravel 10.x
・spatie/laravel-medialibrary
・MySQL

# インストール～設定
Laravel-medialibraryをインストールします。
今回はバージョン指定していませんが、互換性のあるバージョンを指定してください。
```
composer require "spatie/laravel-medialibrary"
```

マイグレーションファイルと設定ファイルを作成します。
```
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider"
```
database\migrations\xxxx_xx_xx_xxxxxx_create_media_table.phpとconfig\media-library.phpが作成されました。

このマイグレーションファイルはメディアの情報を管理するためのテーブルになります。
メディアを紐づけるモデルとマイグレーションファイル手動で作成する必要があります。
今回は最低限のデータを保存するテーブルにしておきますね。
```
php artisan make:Model File -m
```

モデルファイルでMedialibraryと紐づけます。
```php:App/Models/File.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

// 追加
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;


class File extends Model implements HasMedia // implements~ 追加
{
    use InteractsWithMedia; // 追加

    protected $guarded =['id'];
}

```

```php:database/migration/xxxx_xx_xx_xxxxxx_create_files_table.php
<?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('files', function (Blueprint $table) {
            $table->id();
            $table->string('name'); // 追加
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('files');
    }
};
```
マイグレーションします。
```
php artisan migrate
```


次に保存する場所の設定をしていきます。
AWS S3などのクラウドストレージサービスも利用できますが、今回はローカルに保存します。
デフォルトの保存先はpublicです。

今回はstorage/app/public/mediaというフォルダを作成し、そこに保存されるように設定します。

保存先を設定します。
これで、保存先mediaを指定すればこのパスに保存できます。
```php:config/filesystems.php
    'disks' => [

        // 追加
        'media' => [
            'driver' => 'local',
            'root'   => public_path('media'),
            'url'    => env('APP_URL').'/media',
        ],
        // 追加ここまで

    ],
```

保存先mediaをLaravel-medialibraryで使用できるようにします。
```php:config/media-library.php
<?php

return [

    'disk_name' => 'media', // 修正

 // ...省略
       ];
```

設定を変更したので、キャッシュをクリアしておきます。
```
php artisan config:clear
```

設定は以上になります。

# メディアのアップロード
簡単に保存できるのか試してみましょう。
保存用の簡単なフォームをウェルカムページで作ってみます。

ウェルカムページを編集します。
```php:welcome.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Laravel</title>

        <!-- Fonts -->
        <link rel="preconnect" href="https://fonts.bunny.net">
        <link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />

    </head>
    <body class="antialiased">


            {{-- メディアアップロードの記述　ここから --}}
            <form action="/store" method="post" enctype="multipart/form-data">
            @csrf
                <label for="name">ファイル名</label>
                <input type="text" name="name" id="name">
                <input type="file" name="file" id="file">
                <button type="submit">アップロード</button>
            </form>
            
            @if(session('success'))
                <div class="alert alert-success">
                    {{ session('success') }}
                </div>
            @endif
            {{-- ここまで --}}

    </body>
</html>
```

保存用のコントローラーを作成します。
```
php artisan make:Controller FileController
```

コントローラーを編集します。
```php:FileController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Models\File; // 追加

class FileController extends Controller
{
    // 追加
    public function store(Request $request)
    {

        $file = File::create([
        'name' => $request->name
        ]);

        $file->addMedia($request->file('file'))
        ->toMediaCollection('media');

        return redirect('/')->with('success', 'アップロードしました');
    }
    // 追加ここまで
}
```
FileモデルにaddMedia($request->file('file'))でファイルをアップロードし紐づけます。
toMediaCollection('media')はコレクション名です。
例えばサムネイル画像ならtoMediaCollection('thumbnail')等でいいと思います。
ファイルの種類分けに使うものだと思っています。

次に、ルートを記述します。
```route/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\FileController; // 追加

Route::get('/', function () {
    return view('welcome');
});

Route::post('/store',[FileController::class,'store']); // 追加
```

これでアップロードの準備が整いました。
アップロードしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3367698/f4947e96-409b-5917-fb96-762d4cbc5525.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3367698/cacf73a3-922c-84c3-ed86-be119ca180bf.png)
public/mediaに今アップロードした画像が保存されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3367698/aab66816-49eb-e0a3-61f6-3c1603a98b15.png)
mediaテーブルに画像の情報が保存されました。


# メディアの取得
保存された画像を表示してみます。
ルート、コントローラー、ビューを編集します。

```route/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\FileController;


// 削除
// Route::get('/', function () {
//     return view('welcome');
// });

Route::get('/',[FileController::class,'index']); // 追加
Route::post('/store',[FileController::class,'store']);
```

```php:FileController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\File;

class FileController extends Controller
{
    public function store(Request $request)
    {
        
        $file = File::create([
            'name' => $request->name
        ]);
        
        $file->addMedia($request->file('file'))
        ->toMediaCollection('media');
        
        return redirect('/')->with('success', 'アップロードしました');
    }
    
    // 追加
    public function index()
    {
        $file = File::find(1);
        
        // ファイルが存在したら画像を取得
        if ($file) {
            $fileImage = 'media/' . File::find(1)->getMedia('media')->first()->id . '/' . File::find(1)->getMedia('media')->first()->file_name;
        } else {
            $fileImage = null;
        }

        return view('welcome', ['fileImage' => $fileImage]);
    }
    // 追加ここまで
}
```
getMedia('media')を使って保存したメディア情報を取得しています。
ローカルなので画像のパスを取得するのにややこしい書き方になってしまいましたが、S3などの外部ストレージを使用する場合は、getMedia('media')->first()->getUrl();を使ったら良いかと思います。(試していないので間違っていたら申し訳ありません)


```php:welcome.blade.php
　　
　　// ...省略
    <body class="antialiased">


            {{-- メディアアップロードの記述　ここから --}}
            <form action="/store" method="post" enctype="multipart/form-data">
            @csrf
                <label for="name">ファイル名</label>
                <input type="text" name="name" id="name">
                <input type="file" name="file" id="file">
                <button type="submit">アップロード</button>
            </form>

            @if(session('success'))
                <div class="alert alert-success">
                    {{ session('success') }}
                </div>
            @endif
            {{-- ここまで --}}

            {{-- 画像表示の記述　ここから --}}
            @isset($fileImage)
                <img src="{{ asset($fileImage) }}" alt="画像" width="300">
            @endisset
            {{-- ここまで --}}

    </body>
```

では表示してみましょう！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3367698/9f49385c-9c80-bef4-5f0d-045421a6bd09.png)

表示できました！
画像は保存する際に編集したりもできるはずですが今回はここまでにします。アップロードするファイルはもちろん画像だけではないです。動画やPDFファイルなども保存可能です。ストレージも変更できます。
ぜひ試してみてください！


# 2023/12/1　追記
だいぶ前に書いた記事なんで凄くやり方が拙い・・・
画像の取得は正直もっと楽でした。以下修正。
コントローラーからurl渡さなくても、bladeファイルで表示できます。

```php:FileController.php
    
    // 追加
    public function index()
    {
        $file = File::find(1);
        
        // ファイルが存在したら画像を取得
        // ここ編集してます。
        return view('welcome', ['file' => $file]);
    }
    // 追加ここまで
}
```



```php:welcome.blade.php
　　
　　// ...省略
    <body class="antialiased">


            {{-- メディアアップロードの記述　ここから --}}
            <form action="/store" method="post" enctype="multipart/form-data">
            @csrf
                <label for="name">ファイル名</label>
                <input type="text" name="name" id="name">
                <input type="file" name="file" id="file">
                <button type="submit">アップロード</button>
            </form>

            @if(session('success'))
                <div class="alert alert-success">
                    {{ session('success') }}
                </div>
            @endif
            {{-- ここまで --}}

            {{-- 画像表示の記述　修正 --}}
            @isset($fileImage)
                <img src="{{ $file->getFirstMediaUrl('media') }}" alt="画像" width="300">
            @endisset
            {{-- ここまで --}}

    </body>
```
コレクション名指定した場合、getFirstMediaUrl('media')ここに記述しないと取得できないので注意です！お試しください。