【laravel + MinIO】ローカルホスト内でS3を体験してみた

:::note warn
エンジニア初心者の投稿であるため、間違っている箇所が多くあるかと思います。
間違っているなどあればご教授いただけると幸いです。
:::

# はじめに
 laravelを使った画像の受け渡しAPI作成を行なっているときに、『MinIO』と呼ばれるストレージ・サーバーがあることを知りました。調べてみると、『MinIO』はAWSのS3を擬似体験（表現が正しくないかもしれません）をできるということでした。今回は安易に『MinIO』に手を出した初心者の記録を残したいと思います。同じように『MinIO』を使ってみたい方の参考になればと思います。

 # MinIOとは
 openStandiaに書かれているところから引用させていただくと
 >MinIOは、Go言語で実装されたAmazon S3クラウド・ストレージ・サービスと互換性のあるオブジェクト・ストレージ・サーバーです。
 >MinIOはAmazon S3と互換性があるため、Amazon S3と全く同じインタフェース(AWS CLIや、AWS SDK)からもアクセスが可能です(ただし一部、MinIOではサポートしていないAPIもあります)。そのため、本番環境ではAmazon S3を使用するが、開発時には、MinIOで開発／テストを行うなどの用途で利用することができます。
 
 
 https://openstandia.jp/oss_info/minio/

 簡単にいうと、Amazon S3を擬似体験できる！と解釈しています、私は！

 # Laravelを使って、MinIOのデータをやり取りする手順
 :::note alert
今回説明する方法は実際に実装するにはセキュリティ面で問題が多いです。個人で利用することをお勧めします。
:::

### MiniO以外の環境設定
Laravel 9系を使用します。具体的な環境構築が完了している前提で話を進めさせていただきます。
docker環境を用いるので、私が構築の際に利用したサイトを貼っておきます。

https://qiita.com/ucan-lab/items/56c9dc3cf2e6762672f4

### MiniOの環境設定

MiniOの環境設定についても以下のものを見るとわかりやすいです。下記のサイト通りに作成をすると利用が可能になります。

https://qiita.com/A-Kira/items/5bb9b3108ba12f533e49

この画面が表示できていれば準備万端です。
ここから画像のアップロードと表示を説明します。


![スクリーンショット 2024-06-10 23.08.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784901/b6e6422a-2148-166f-1571-4425f921dc79.png)


### 画像のアップロードと表示

#### DBの設定
```php:app bash
php artisan make:model Post --migration
```

今回は特にPost.phpについては変更を加えません。簡易的な作成なためご許しを・・・。
日付_create_posts_table.phpはDBとやり取りをするために変更を加えます。画像を呼び出すために必要なるので必ず設定してください。

```php:日付_create_posts_table.php
return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
    //変更箇所
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('image');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
};
```
マイグレーションファイルの設定は完了したので、DBに反映させます。

```php:app bash
php artisan migrate
```

DB内に入って、Postsテーブルができていることを確認してください。
ここまでできていれば用意完璧です。

![スクリーンショット 2024-06-10 23.23.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784901/0004abb7-d8bf-0d61-e3f1-64b7100d930f.png)

#### 画像をアップロードする画面の作成
簡易的なものであるため、見た目がひどいですが、温かい目で見守ってください。
今回はindex.blade.phpという名前のファイルに作成します。

```php:index.blade.php
<form action="{{ route('upload') }}" method="post" enctype="multipart/form-data">
    {{ csrf_field() }}
    <input type="file" name="file">
    <button type="submit">保存</button>
</form>

<a href="{{ route('show') }}">ここをクリックしてな</a>
```
ザックリ説明すると、画像を送信するためのformタグがあり、画像を表示する画面へ遷移するaタグがある形になっています。routeの中身である、「update」と「show」は後ほど記述します。

:::note warn
ここで注意してほしい点が一つあります。formタグの属性である【enctype="multipart/form-data"】は忘れずにつけてください。ファイルの送信に利用する属性です。
:::


#### 画像を表示する画面の作成
今回はshow.blade.phpという名前のファイルに作成します。
```php:show.blade.php
<img src="{{ $memo['image'] }}"/>
```
これだけ！？と思う方がいるかと思いますが、簡易版です。温かい目で…(n回目)。

#### webファイルの修正（ルーティングの修正）
Viewの部分が完成したので、一度確認の意味を込めてルーティングを設定して、画面に表示できるようにしたいと思います。
```php:web.php
//画像をアップロードする画面のルーティング
Route::get('/', function () {
return view('index');
});
```
この設定をしたときにこの画面が出てくれば大成功です（私はポートの関係で8088になっています）。

![スクリーンショット 2024-06-10 23.48.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784901/5be8ecff-b71d-f8b4-ef9c-532b048b546d.png)

#### controllerの設定
今回のcontroller名はPhotControllerに設定をします。

```php:app bash
php artisan make:controller PhotController
```
コマンドで作成できたPhotController.phpを編集します。
また、今回はlaravelを用いてMiniOを操作するので、AWS SDKをインストールする必要があります。これをインストールすることで画像の保存を【Storage::disk('s3')】という簡単なコードで行うことができるようになります。
```php:app bash
composer require league/flysystem-aws-s3-v3:^3.0
```

:::note warn
今回の例ではlaravel 9系の場合でのコマンドです。laravelのバージョンによっては使えない可能性があるので、エラーが発生した場合はまず、依存関係を確かめてください。
:::





```php:PhotController.php
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
//Storageは呼び出さないと利用ができません
use Illuminate\Support\Facades\Storage;

class PhotController extends Controller
{
    public function upload(Request $request)
    {
        //index.blade.phpのformタグで受け取った画像を変数にいれる
        $file = $request->file('file');

        //この一文だけでMiniOには保存ができます。
        //Storage::disk('s3')->putFile('/', $file, 'public');
        
        //今回はDBにURLを保存するため、先頭に$pathをつけることで画像のパスを変数に代入するとができます
        $path = Storage::disk('s3')->putFile('/', $file, 'public');
        
        //パスからURLに変更しています
        $path = Storage::disk('minio')->url($path);

        //ここについては後述しますが、非常に重要なポイントです。
        $pathArray = explode('/', $path);
        $pathArray[2] = 'localhost:9000';
        $newPathArray = implode('/', $pathArray);

        //DBに画像を開くためのURLを挿入します
        Post::insert([
            'image' => $newPathArray,
        ]);

        //index.blade.phpに戻るための処理です
        return redirect('/');
    }

}
```
先ほど出てきたこちらのコードについて説明します。長ったらしく、URLを変更していますが意味があります。
```php:PhotController.php
//①パスをそのまま取得している
$path = Storage::disk('minio')->url($path);

//②パスの一部を修正している
$pathArray = explode('/', $path);
$pathArray[2] = 'localhost:9000';
$newPathArray = implode('/', $pathArray);
```

```
//①のコードで取得できるパス
http://minio:9000/develop/◯◯.jpg

//②のコードで取得できるパス
http://localhost:9000/develop/◯◯.jpg
```
見ていただくとわかるように『minio:9000』を『localhost:9000』に変更しています。
これはminioに直接レスポンスが送れない仕様になっているので、docker経由でレスポンスを行っています。
これについてはこちらの記事がわかりやすいです。

https://n-s.tokyo/2019/04/minio-tips/

:::note warn
実際の実務では一定期間有効なURLを発行することで画像の受け渡しをしており、今回のような受け取り方はよろしくないとのことでした。
:::

画像を表示するためのcontrollerも必要になるため、定義していきます。上記の内容に追加で付け足してください。
```php:PhotController.php
public function show()
{
    //idが1である画像を表示するための記述です。
    $memo = Post::where('id', 1)->first();
    return view('show', compact('memo'));
}
```

#### web.phpに追加
controllerの設定ができたので、web.phpにルーティングをしましょう。
```php:web.php
//画像をアップロードする画面のルーティング
Route::get('/', function () {
return view('index');
});

Route::namespace('App\Http\Controllers')->group(function () {
//画像を送信するためのルーティング
    Route::post('/upload', 'PhotController@upload')->name('upload');
//画像を表示する画面のルーティング
    Route::get('/show', 'PhotController@show')->name('show');
});
```

#### 実際に試してみる
ここまでうまくできていればおそらく画像の受け渡しが可能になっているはずです。実際に画像を使って見てみましょう。

ファイルを選択を押して、画像を挿入する。
![スクリーンショット 2024-06-11 15.54.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784901/656069d7-d3bb-9c13-764c-8f43845001b9.png)

選択されていませんが画像の名前に変更される。保存をクリック。
![スクリーンショット 2024-06-11 15.56.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784901/f66e439d-c00b-a9ec-741e-7454cbdd83a9.png)

mysqlのpostsにURLが送信される（idが5になっていますが、初めての方は1になります）。
![スクリーンショット 2024-06-11 15.58.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784901/9c170776-3516-ab08-166e-f3f7bf7c27ca.png)

MiniOにアクセスすると画像が増えています。
![スクリーンショット 2024-06-11 16.00.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784901/903b91ea-b684-e63a-e577-8f2290623198.png)

もとの画面に戻って「ここをクリックしてな」をクリック。
![スクリーンショット 2024-06-11 15.54.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784901/5584054d-be76-7cab-7a44-e3e1acd73be8.png)

見事写真が表示されます。
![スクリーンショット 2024-06-11 16.04.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784901/0716bbdf-1a03-34bc-05b2-7780b0da96ee.png)


## おわりに
MiniOを使って簡単な画像の受け渡しを再現しました。今回の実装は自分で遊ぶようのものになっているのでセキュリティ面がガバガバです。実際に実務で使うレベルには程遠いですが、MiniOの使い方を学ぶにはちょうどいい重さの実装でした。AWSはものすごい勢いでアップデートしており、今の方法がいつまで使えるかわかりませんが（現在2024/6/11）サッと試したい方は真似して作ってみてください。