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()メソッドを使えば、簡単にテストすることができます。

動作確認

最後に、動作確認。

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