Laravelで作成したアプリをHerokuにアップデートする方法②(Amazon S3の活用)

テーマ

どうも、naoto555です。
Laravelで作成したアプリをHerokuにアップデートする方法①では、Herokuにアプリをアップロードするところまで行いましたが、Gitで管理されていない画像(例えば各ユーザーのプロフィール画像やECサイトでユーザーが登録した商品画像など)は、HEROKU上から削除されてしまいます。そのため、アプリで画像を保存し、表示する場合は、別のストレージを使用する必要があります。

わたしの場合、普段プログラミングにAWS Cloud9を利用しているため、同じくAmazonが提供する「Amazon S3」というストレージを使用して、Laravelアプリの画像をHerokuにデプロイしたアプリに表示していく方法を紹介していこうと思います。(2024.1記載)

Amazon S3の設定

まずは、ルートユーザーでAWSにログインします。 →AWSログインページ

次に、検索からS3を検索し選択


バケットを作成

span style=”color: #000000;”>バケットの名前を決めます。既存の他の誰かの作ったバケット名と被らないように設定します。今回は“test-app-s3-2023”に設定したいと思います。
既存の他の誰かの作ったバケット名と被った時は作成エラーになるので、他の人が作ってない名前を考えてみてください。
AWSリージョンは、“アジアパシフィック(東京)ap-northeast-1”にしました。

ここで設定したバケット名リージョンコードは、後で環境変数設定時に使用するのでメモ帳などにコピペしておいてください。

 

下にスクロールし、赤い四角で囲んだ箇所にチェックを入れていき、一番下のバケットを作成をクリック。


無事作成できたら、作成したバケットをクリックして、アクセス許可設定をクリック

 

そのまま下にスクロールすると、アクセスコントロールリスト(ACL)という項目があるので、編集をクリック


下の画像の赤い四角で囲んだ箇所にチェックを入れていき、変更の保存をクリック

S3にアクセスするためのIAMユーザーを作成

Amazon S3の設定をしたので、画像を保存するためのバケットは準備できました。引き続き、次は画像をアップロードするための設定をIAMで行っていきます。

AWSのマネージメントコンソールからIAMと入力

赤い枠の通りに設定していき、ユーザーを作成していきます


ユーザー名を入力し、アクセスの種類の選択をします。ユーザー名は何でもOKですが複数アプリでS3を使うときに、どのアプリがどのユーザー名でS3を使っているのか分からなくならないように分かりやすいユーザー名にしましょう。

ここで設定したIAMのユーザー名は、あとで手元のアプリと紐づける必要があるのでメモ帳などでコピペしておきましょう。今回はユーザー名を”test-app-user”とします。}

ユーザー作成後に、アクセスキーを発行していきます。

セキュリティ認証情報のタブを選択し、コマンドラインインターフェースを選択

HerokuにデプロイしたアプリとS3ストレージの紐づけ

アプリから、Amazon S3のファイルを読込みできるようにしていきます。
まずは、S3を使用できるように、コマンドを打ちライブラリーをインストール

composer require league/flysystem-aws-s3-v3

※もし、Laravel8.X以前のVerの場合は、league/flysystemのVer2がサポートされていないので、league/flysystemのVer1をインストールするようオプションで”^1.0“をつけてください。

composer require league/flysystem-aws-s3-v3:"^1.0"

アクセスキーとIAMキーはコマンドをどのように打つかを示すための架空のキーになります。各種設定が以下のものだとして、HerokuのアプリとS3の紐づけ方法を解説していきます。アプリ名はherokuにデプロイした際に「heroku apps:create -a <アプリ名>」で設定したアプリ名になります。

アプリ名 appname
S3バケット名 test-app-s3-2023
リージョンコード ap-northeast-1
IAMアクセスキー AKIXXXXXXXXXXX
IAMシークレットキー aaaaaaaaaa/bb/cccccccc/dddd

上記の各種設定をターミナルで打ちこんでいき、HerokuにpushしたアプリとS3を紐づけます。

IAMアクセスキーの入力 (heroku config:set AWS_ACCESS_KEY_ID=<IAMアクセスキー> -a <アプリ名>)

#IAMアクセスキーの入力
heroku config:set AWS_ACCESS_KEY_ID=AKIXXXXXXXXXXX -a appname


IAMシークレットキーの入力 (heroku config:set AWS_SECRET_ACCESS_KEY=<IAMシークレットキー> -a <アプリ名>)

#IAMのシークレットキーの入力
heroku config:set AWS_SECRET_ACCESS_KEY=aaaaaaaaaa/bb/cccccccc/dddd -a appname


リージョンコードの設定 (heroku config:set AWS_DEFAULT_REGION=<リージョンコード> -a <アプリ名>)

#リージョンコードの設定
heroku config:set AWS_DEFAULT_REGION=ap-northeast-1 -a appname


バケットの設定(heroku config:set AWS_BUCKET=<S3バケット名> -a <アプリ名>)

#バケットの設定
heroku config:set AWS_BUCKET=test-app-s3-2023 -a appname


バケットのURL (heroku config:set AWS_URL=https://s3-<リージョンコード>.amazonaws.com/<S3バケット名>/)

#バケットのURL
heroku config:set AWS_URL=https://s3-ap-northeast-1.amazonaws.com/test-app-s3-2023/


S3の設定 (heroku config:set FILESYSTEM_DRIVER=s3 -a <アプリ名>)

#S3の設定
heroku config:set FILESYSTEM_DRIVER=s3 -a appname

S3に画像をアップロードする機能を作成

今回はS3にユーザーアイコンの画像をアップロードできるようにしていきます。また、Herokuにpushした本番環境とAWSのローカル環境で保存先や参照先を分けるために、APP::environment(['production']のときと、APP::environment(['local']のときで場合分けをしています。
・$env=0:本番環境(APP::environment([‘production’]))
・$env=1:ローカル環境(APP::environment([‘local’]))

 

以下のサンプルコードでは以前の記事でご紹介した、GIF画像作成アプリ(こちらの記事)を改造して、本番(HEROKU)環境ならS3に”test_prod.gif”という名前で保存し、ローカル環境なら/public/storage/gifsに”test_local.gif”という名前で保存するものにしていこうと思います。ルーティングやビューからのフォーム送信、それをコントローラで受け取る部分に関しては(こちらの記事)を参照ください。
※また、ローカルでストレージに保存するには「php artisan storage:link」のコマンドが必要なのでこちらもお忘れなく!

コントローラの編集

app/Http/Controllers/GifController.php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\App;


    public function store(Request $request)
    {
        //中略
        
        //環境変数の読込
        $env = App::envitonment(['production']) ? 0 : 1;

        // GIF画像のフォーマット
        $filename = ($env == 0) ? 'test_prod.gif' : 'test_local.gif';
        $gif->optimizeimagelayers();
        $gif_file = $gif->getImagesBlob();
        $gif->destroy();

        // GIF画像を保存する
        if($env==0){
            //S3に保存
            Storage::disk('s3')->put($filename, $gif_file, 'public');
        }else{
            //publicフォルダに保存
            Storage::disk('public')->put('/gifs/'.$filename, $gif_file);
        }

     return view('gifs.create', ['env' => $env, 'message' => 'GIF画像を作成しました。']);
    }

 

Storage::disk('s3')->put($filename, $gif_file, 'public');

上記の部分で、S3に画像を保存しています。上記では ”S3バケットにGIFファイルを保存しています。
putの第1~3の引数の意味は以下のものになります。

・第一引数: S3のディレクトリの指定
・第二引数: アップロードするファイル
・第三引数: ’public’ を指定しておくことで、保存したファイルをアプリ上で閲覧許可にすることができます

S3に保存した画像を表示/ダウンロードリンクを作成する 

以下は、保存されたファイルをビューに表示する方法です。

保存した画像の表示
resources/views/gifs/create.blade.php

@if($env==0)
    <img src="{{ Storage::disk('s3')->url('test_prod.gif') }}">
@else
    <img src="{{ secure_asset('storage/gifs/test_local.gif') }}">
@endif

 

本番環境とAWSのローカル環境で条件分岐させて表示させていますが、S3に保存したファイルの表示は以下のタグになります。
今回はバケットの直下に”test_prod.gif“を保存したので、"->url('test_prod.gif')"と書きましたが、バッケット内にフォルダがあってそのフォルダ内にファイルがある場合は"->url('folderName/test_prod.gif')"のようにスラッシュで区切ってください。

<img src="{{ Storage::disk('s3')->url('test_prod.gif') }}" id="gif_image">

ダウンロードリンクの作成①(ビュー)
resources/views/gifs/create.blade.php

@if($env==0)
    <a href="{{ route('gifs.download') }}" download="test_prod.gif">ダウンロード</a>
@else
    <a href="{{ asset('storage/gifs/test_local.gif') }}" download="test_local.gif">ダウンロード</a>
@endif@endif

 

以下の部分がダウンロードリンクになります。

<a href="{{ route('gifs.download') }}" download="test_prod.gif">ダウンロード</a>

S3に保存されたファイルの場合、
<a href="{{ Storage::disk('s3')->url('test_prod.gif') }}" download="test_local.gif">ダウンロード</a>
の様に、タグにそのままダウンロード属性を設定することができないのでダウンロードリンクを作成するため少し面倒なことをしないといけません。

これは、asset() ヘルパーではラウザーから直接アクセスできるため、ダウンロード用のリンクとして download 属性を使用することができる一方で、Storageファサードを使用してファイルを提供する場合、提供されるファイルはパブリックディレクトリ内に直接保存されているわけではなく、アプリケーションが保持するストレージに保存されている違いによるものです。

ダウンロードリンクの作成②(コントローラ)

app/Http/Controllers/GifController.php

use Illuminate\Http\Response;

public function downloadFile()
{
    $filename = 'test_prod.gif';
    $filePath = Storage::disk('s3')->url($filename);

    $response = new Response(file_get_contents($filePath));
    $response->headers->set('Content-Type', 'application/octet-stream');
    $response->headers->set('Content-Disposition', 'attachment; filename="'.$filename.'"');

    return $response;
}

 

上記のコードでは、S3上のGIF画像ファイルをダウンロードするための処理を行っています。
S3上に保存されたファイルを$responseに格納して返しています。
ヘッダーに設定している、「Content-Type」と「Content-Disposition」はビュー(HTML)に、データの種類やダウンロード時の挙動とファイル名を設定して適切に表示やダウンロードを行うための設定になります。

ダウンロードリンクの作成③(ルーティング)
最後に、ルーティングを編集してビューで呼び出している、"{{ route('gifs.download') }}"とコントローラーのアクションを紐づければ完了です。

routes/web.php

Route::get('/gifdownload', [GifController::class, 'downloadFile'])->name('gifs.download');

完成図 

以下、実際にHerokuにデプロイしたアプリの動作確認の動画になります。表示もダウンロードもうまくいきました。