LaravelでS3に画像アップロードする方法

LaravelでS3に画像アップロードする方法

こんにちは!

突然ですが、皆さんは画像保存する場合にどのように保存していますか?

Webサーバーに保存するというのは一般的かもしれません。

ただ、Amazon S3に画像を保存すると、Webサーバーに画像保存する必要がなくなり、容量を圧迫せずに済んだりと何かとメリットが大きいです。

Amazon S3を活用するメリットは、一般的には以下のようなものがあるとされています。

  • スケーラビリティ・・・大規模なトラフィックやデータ増加に耐えられる
  • 低コスト・・・使用した分だけ課金される従量課金制。コストを最小限に抑えられる
  • 高速なデータ転送・・・高速かつ安定したデータ転送ができる
  • セキュリティとアクセス制御・・・バケットポリシーやIAMロールを使用して、データへのアクセスを制御できる

などなど。

そこで、今回はLaravelを使ったS3への画像アップロード方法をご紹介していきます。

このようにお考えの方には参考になるかと思います。

それでは、早速見ていきましょう!

前提

  • AWSアカウントをすでに持っていること

LaravelからS3へ画像アップロードする方法

バケットを作成する

まずは、S3にバケットを作成していきます。

AWSの「Amazon S3」に入り、以下の「バケットを作成」をクリック。

バケットの設定を行う

    • バケットタイプ・・・汎用
    • バケット名・・・任意の名前
    • オブジェクト所有者・・・ACL 無効
    • ブロックパブリックアクセス・・・「パブリックアクセスをすべてブロック」を解除
    • バケットのバージョニング・・・無効にする
    • タグ・・・なし
    • デフォルトの暗号化・・・Amazon S3 マネージドキーを使用したサーバー側の暗号化 (SSE-S3)。バケットキーは有効にする

    これらを設定し、「バケットを作成」をクリック。

    バケットポリシーの編集

      1. 対象のバケットを選択
      2. 「プロパティ」タブを開く
      3. ARNをコピーしメモしておく
      4. 「アクセス許可」タブを開く
      5. バケットポリシーの編集ボタンをクリック
      6. ポリシージェネレータを開く

      ポリシージェネレータでは、以下のように入力します。

      • Select Type of Policy・・・S3 Bucket Policy
      • Effect・・・Allow
      • Principal・・・「*」を入力
      • AWS Service・・・Amazon S3
      • Actions・・・All Actionsにチェック

      入力したら、「Add Statement」をクリックします。

      続いて「Generate Policy」をクリックします。

      Policy JSON Documentが出てくるのでコピーして閉じる。

      バケットポリシー編集画面に戻ってコピーしたPolicyをペーストし、変更を保存する。

      IAMユーザーを作成

      次に、IAM > ユーザーに入って、「ユーザーの作成」をクリックします。

      ユーザー名には適当なユーザー名を入力します。

      次に、以下のように設定します。

      • 許可のオプション・・・「ポリシーを直接アタッチする」を選択
      • 許可ポリシー・・・「AmazonS3FullAccess」を選択

      最後に確認画面が出てくるので、問題なければ「ユーザーの作成」をクリック。

      ユーザーが作成されるので、詳細画面に入って、「アクセスキーを作成」をクリック。

      ユースケースに「コマンドラインインターフェース(CLI)」を選択します。

      設定タグ値には任意の説明を入れて、「アクセスキーを作成」をクリック。

      アクセスキーとシークレットキーが作成されるので、CSVダウンロードして漏れないように大切に保管してください。

      このアクセスキーとシークレットキーは後ほど使っていきます。

      LaravelプロジェクトにS3用パッケージをインストールする

      以下のコマンドからFlysystem S3パッケージをインストールします

        $ composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies

        S3用の設定を追加

          「config/filesystems.php」に以下を追記します。

          's3' => [
              'driver'                  => 's3',
              'key'                     => env('AWS_ACCESS_KEY_ID'),
              'secret'                  => env('AWS_SECRET_ACCESS_KEY'),
              'region'                  => env('AWS_DEFAULT_REGION'),
              'bucket'                  => env('AWS_BUCKET'),
              'url'                     => env('AWS_URL'),
              'endpoint'                => env('AWS_ENDPOINT'),
              'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
              'throw'                   => false,
          ],

          環境変数の設定

            AWS用の環境変数を設定します。

            AWS_ACCESS_KEY_ID=アクセスキー
            AWS_SECRET_ACCESS_KEY=シークレットキー
            AWS_DEFAULT_REGION=ap-northeast-1(東京リージョンの場合)
            AWS_BUCKET=バケット名
            AWS_USE_PATH_STYLE_ENDPOINT=false

            アクセスキーには作成しておいたIAMユーザーのアクセスキーとシークレットキーを記述します。

            S3へのアップロード処理

              以下、S3へのアップロード処理の一例です。

              public function store(Request $request)
              {
                ...
                // s3に保存
                $path = Storage::disk('s3')->put('images', $request->file('image'));    
                $post->image_path = Storage::disk('s3')->url($path);    
                $post->save();    
                return redirect()->route('post.index');
              }

              Storageファサードのメソッドはputを使っていますが、そのほかにもputFileputFileAsメソッドなどがあります。

              putはメモリ消費が一番大きいのでここは適時検討してください。

              フォーム

                <form action="{{ route('post.store') }}" method="post" enctype="multipart/form-data">    
                @csrf    
                  <div class="form-group">        
                    <label for="post-image">投稿画像</label>        
                    <input type="file" name="image" id="post-image">    
                  </div>    
                  ...    
                  <button type="submit">投稿</button>
                </form>

                formタグに「enctype="multipart/form-data"」の記述を忘れないよう注意です。

                これを忘れると動きません。

                画像表示

                  @foreach ($posts as $post)	
                  <div class="card">	    
                    <div class="card-img">	        
                      <img src="{{ $post->image_path ? $post->image_path : 'img/sample-img.jpeg' }}" alt="サンプル画像">	    
                    </div>	    
                  ...	
                  </div>
                  @endforeach

                  DBに保存しておいた画像パスを取得してsrc属性に入れています。取得できなかった場合は、事前に用意しておいたサンプル画像を差し込んでいます。

                  テストコード

                    use Illuminate\Support\Facades\Storage; // 追加
                    use Illuminate\Http\UploadedFile; // 追加
                    
                    public function test_store(): void
                    {    
                      Storage::fake('s3');    
                      $file = UploadedFile::fake()->image('test.jpg');    // login_userが認証を通過しているとする    
                      $this->assertAuthenticatedAs($this->login_user);    
                      $response = $this->actingAs($this->login_user)->post(route('post.store'), [        
                        'title' => 'Test Title',        
                        'body' => 'Test Body',        
                        'image' => $file,    
                      ]);    
                      $response->assertStatus(302);    
                      Storage::disk('s3')->assertExists('images/' . $file->hashName());    
                      $response->assertRedirect(route('post.index')); 
                    }

                    Storage::fake()メソッドとUploadedFile::fake()メソッドを使えば、簡単にテストすることができます。

                    動作確認

                      最後に、動作確認。

                      画像表示ができました!素晴らしい!👏