LaravelでS3に画像をアップロードして表示・削除する

AWS上にLaravelでアプリケーションを構築する場合、画像などのファイルをS3に置くことは良い方法であると思います。
LaravelのファイルシステムにはS3のドライバーが利用できるようになっており、簡単にS3のファイル操作を行うことができます。

今回は、Laravelのファイルシステムを利用して、S3に画像をアップロードして表示する方法をご紹介します。
ソースコードはこちらにアップしていますので、併せてご参照ください。

アプリケーションの概要

まず、今回作成するものの概要について説明します。

URL設計

URLの設計は下記のようにすることとします。

URLMethodAction説明
/imagesGETImagesController@index画像の一覧画面
/images/newGETImagesController@store画像のアップロード画面
/imagesPOSTImagesController@create画像のアップロード処理
/images/{filename}GETImagesController@show画像の表示
/images/{filename}DELETEImagesController@destroy画像の削除処理

S3にアップロードした画像の表示には、S3をパブリックホスティングするのではなく、WEBサーバー経由で画像を取得して表示します。

実装方法

LaravelのファイルシステムでS3を利用するための準備

まずS3のバケットの作成と、S3を利用するためのIAMユーザーを作成しましょう。
作成したら.envファイルに下記を追記します。

# .env

AWS_ACCESS_KEY_ID=*****
AWS_SECRET_ACCESS_KEY=*****
AWS_DEFAULT_REGION=*****
AWS_BUCKET=*****

LaravelでS3ドライバを利用する場合、ライブラリをインストールする必要があります。下記のコマンドを実行してインストールします。

$ composer require league/flysystem-aws-s3-v3

画像のアップロード画面とアップロード処理の実装

これで準備は完了しましたので、実装を行なっていきます。
まず最初に画像のアップロード処理周りを実装します。

画像のアップロード画面にフォームを設置します。

# resources/views/images/create.blade.php

<form method="post" action="{{ action('ImagesController@store') }}" enctype="multipart/form-data">
    {{ csrf_field() }}

    <fieldset>
        <p>
            <input id="file" type="file" name="image" />

            @if ($errors->has('image'))
                {{ $errors->first('image') }}
            @endif
        </p>
    </fieldset>

    <input class="btn btn-primary" type="submit" value="アップロード" />
</form>

該当のソースファイルはこちら

LaravelはCSRF保護をミドルウェアで行なっていますので、{{ csrf_field() }}によりフォームにCSRFトークンを埋め込んであげる必要があります。また、ファイルをアップロードするのでmultipart/form-dataを設定します。

次に、フォームからファイルを受け取りS3にアップロードする処理を実装します。

# app/Http/Controllers/ImagesController.php

public function store(Request $request)
{
    $params = $request->validate([
        'image' => 'required|file|image|max:4000',
    ]);

    $file = $params['image'];
    $fileContents = file_get_contents($file->getRealPath());

    $disk = Storage::disk('s3');
    $disk->put($file->hashName(), $fileContents);

    return redirect()->action('ImagesController@index');
}

該当のソースファイルはこちら

Storageファサードを使い、S3にディスクとしてファイル操作を行います。
今回のサンプルでは、ハッシュ化したファイル名をバケットの直下に配置し、成功した場合は画像の一覧画面にリダイレクトします。

以上で、画像をアップロードする処理が完成しました。次にアップロードした画像の表示と削除処理を実装していきます。

画像の表示と削除

S3の画像一覧

画像の一覧画面は、上記の画像のように、

  • S3のバケット直下にある画像をリストで表示
  • それぞれの画像に対応する削除ボタンを設置

とします。

まずは、画像を表示する処理を実装します。

# app/Http/Controllers/ImagesController.php

public function show($filename)
{
    $disk = Storage::disk('s3');
    try {
        $contents = $disk->get($filename);
        $mimeType = $disk->mimeType($filename);
    } catch (\Exception $e) {
        return response(['message' => $e->getMessage()], 404);
    }
    return response($contents)->header('Content-Type', $mimeType);
}

該当のソースファイルはこちら

ファイル名を受け取りS3のファイルを取得して、Content-Typeを設定して内容を返します。
また、ファイルが存在しない場合は404を返します。

画像が表示できるようになりましたので、次に一覧画面を作成します。
一覧画面のアクションにて、S3直下のファイルを全て取得してViewに渡しします。

# app/Http/Controllers/ImagesController.php
public function index()
{
    $disk = Storage::disk('s3');
    $files = $disk->files('/');
    return view('images.index', ['files' => $files]);
}

該当のソースファイルはこちら

一覧画面のViewで、ファイルのリストを回しながら、画像のsrcを/images/{filename}になるように設定します。
また、削除ボタンはDELETEメソッドを利用するため、<form>〜</form>の箇所ように設定します。

# resources/views/images/index.blade.php

<div class="cards">
    @foreach ($files as $file)
        <div class="card" style="width: 24%;">
            <img class="card-img-top" src="{{ url("/images/${file}") }}" style="height: auto;">

            <div class="card-body">
                <form action="{{ url("/images/${file}") }}" method="POST">
                    {{ csrf_field() }}
                    {{ method_field('DELETE') }}

                    <button class="btn btn-danger">削除</button>
                </form>
            </div>
        </div>
    @endforeach
</div>

該当のソースファイルはこちら

最後に、削除処理を実装して完了です。

# app/Http/Controllers/ImagesController.php

public function destroy($filename)
{
    $disk = Storage::disk('s3');
    $disk->delete($filename);
    return redirect()->action('ImagesController@index');
}

該当のソースファイルはこちら

$filenameで指定するS3上のファイルを削除して、一覧画面にリダイレクトします。

おわりに

以上でLaravelでS3に画像をアップロードして表示および削除の処理は全て完了です。
以前紹介したリサイズの方法と組み合わせて、リサイズした画像をアップロードしたりすることも可能です。
Laravelのファイルシステムを利用すればS3のファイル操作の実装も楽々ですね。

この記事と関連している記事