Help us understand the problem. What is going on with this article?

AWS S3 Laravelアプリから画像ファイルをアップロードして表示する

目次

  • AWS EC2に構築したLaravel環境で作成したLaravelアプリから画像ファイルをS3にアップロードする方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境(下記の環境をEC2に構築した)
項目 情報 備考
MySQL 8.0.20 for Linux on x86_64 コマンド$ mysql --versionで確認
Apache 2.4.43 コマンド$ httpd --versionで確認
PHP 7.4.5 コマンド$ php --versionで確認

前提条件

読後感

  • AWS EC2にてデプロイしたLaravelアプリからのトリガーで画像ファイルをS3の「test」のフォルダ直下に保存することができる。

概要

  1. S3にてバケットを作成
  2. アクセスキーの取得
  3. テーブルの作成
  4. ファイル設置処理の付与
  5. 設定

詳細

  • 下記の説明で使用するコマンドはsshでインスタンスに接続して実行する物とする。
  1. S3にてバケットを作成
    1. 下記のリンクの手順を実施してS3にバケットを作成する。(testフォルダの作成まで実施する。)
  2. アクセスキーの取得
    1. 下記のリンクの手順を実施してアクセスキーとシークレットアクセスキーの取得を行う。
  3. Laravelアプリ側の設定

    1. 下記コマンドを実行してApacheのドキュメントルートまで移動する。

      $ cd /var/www/html/
      
    2. 下記コマンドを実行してスワップ領域の値を設定する。(/bin/dd: failed to open ‘/var/swap.1’: Text file busyと出力されてしまった方はAWSのEC2コンソールより一旦インスタンスを再起動する。Linuxの物理マシンで実施中の方もLinux物理マシンを再起動する。※インスタンス再起動後はssh接続のコマンドが変化するため注意する。)

      $ sudo /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count=1024
      $ sudo /sbin/mkswap /var/swap.1
      $ sudo /sbin/swapon /var/swap.1
      
    3. sshを用いてインスタンスに接続し、下記コマンドを実行してライブラリのインストールを行う。(エラーThe following exception is caused by a lack of memory or swap, or not having swap configuredが発生した方はこちら→composerを用いたインストール中にメモリ系のエラーが出た話)

      $ cd /var/www/html/test
      $ composer require league/flysystem-aws-s3-v3 ~1.0
      
    4. 下記コマンドを実行してLaravelの設定ファイルを開く。

      $ vi .env
      
    5. 下記の様に設定ファイルを修正する。

      • 修正前

        /var/www/html/test/.env
        AWS_ACCESS_KEY_ID=
        AWS_SECRET_ACCESS_KEY=
        AWS_DEFAULT_REGION=us-east-1
        AWS_BUCKET=
        
      • 修正後

        /var/www/html/test/.env
        AWS_ACCESS_KEY_ID=AWSのアクセスキーを記載する
        AWS_SECRET_ACCESS_KEY=AWSのシークレットアクセスキーを記載する
        AWS_DEFAULT_REGION=ap-northeast-1
        AWS_BUCKET=先に作成したバケット名
        
      • 修正後の.envファイルの全体を下記に記載する。

        /var/www/html/test/.env
        APP_NAME=test
        APP_ENV=local
        APP_KEY=base64:ZJYPq8Ij/UIWowQG+H4Gwz+zXjs/vEn85Yfv45qQa88=
        APP_DEBUG=true
        APP_URL=http://localhost
        
        LOG_CHANNEL=stack
        
        DB_CONNECTION=mysql
        DB_HOST=127.0.0.1
        DB_PORT=3306
        DB_DATABASE=test
        DB_USERNAME=root
        DB_PASSWORD=**************
        
        BROADCAST_DRIVER=log
        CACHE_DRIVER=file
        QUEUE_CONNECTION=sync
        SESSION_DRIVER=file
        SESSION_LIFETIME=120
        
        REDIS_HOST=127.0.0.1
        REDIS_PASSWORD=null
        REDIS_PORT=6379
        
        MAIL_MAILER=smtp
        MAIL_HOST=smtp.mailtrap.io
        MAIL_PORT=2525
        MAIL_USERNAME=null
        MAIL_PASSWORD=null
        MAIL_ENCRYPTION=null
        MAIL_FROM_ADDRESS=null
        MAIL_FROM_NAME="${APP_NAME}"
        
        AWS_ACCESS_KEY_ID=AWSのアクセスキーを記載する
        AWS_SECRET_ACCESS_KEY=AWSのシークレットアクセスキーを記載する
        AWS_DEFAULT_REGION=ap-northeast-1
        AWS_BUCKET=先に作成したバケット名
        
        PUSHER_APP_ID=
        PUSHER_APP_KEY=
        PUSHER_APP_SECRET=
        PUSHER_APP_CLUSTER=mt1
        
        MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
        MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
        
  4. テーブルの作成

    1. アプリ名ディレクトリで下記コマンドを実行してS3に格納する画像ファイルのパスとログイン中のユーザIDを格納するimagesテーブルを作成するマイグレーションファイルとモデルファイルを作成する。

      $ php artisan make:model Image --migration
      
    2. 下記にファイルが作成された事を確認する。

      • アプリ名ディレクトリ/app/Image.php
      • アプリ名ディレクトリ/database/migrations/YYYY_MM_DD_XXXXXX_create_images_table.php
    3. アプリ名ディレクトリで下記コマンドを実行してマイグレーションファイルを開く。

      $ vi database/migrations/YYYY_MM_DD_XXXXXX_create_images_table.php
      
    4. 開いたマイグレーションファイルを下記の様に記載する。

      アプリ名ディレクトリ/database/migrations/YYYY_MM_DD_XXXXXX_create_images_table.php
      <?php
      
      use Illuminate\Database\Migrations\Migration;
      use Illuminate\Database\Schema\Blueprint;
      use Illuminate\Support\Facades\Schema;
      
      class CreateImagesTable extends Migration
      {
          /**
           * Run the migrations.
           *
           * @return void
           */
          public function up()
          {
              Schema::create('images', function (Blueprint $table) {
                  $table->id();
                  //下記を追記する
                  $table->foreignId('user_id');
                  $table->string('path');
                  //上記までを追記する
                  $table->timestamps();
              });
          }
      
          /**
           * Reverse the migrations.
           *
           * @return void
           */
          public function down()
          {
              Schema::dropIfExists('images');
          }
      }
      
    5. アプリ名ディレクトリで下記コマンドを実行してマイグレーションを行う。

      $ php artisan migrate
      
  5. ファイル設置処理の付与

    1. アプリ名ディレクトリで下記コマンドを実行してルーティングファイルを開く。

      $ vi routes/web.php
      
    2. 開いたルーティングファイルを下記の様に修正する。(そのほかのルーティング情報が下記記載と一致していなくても追記部分だけ追記する。)

      アプリ名ディレクトリ/routes/web.php
      <?php
      
      use Illuminate\Support\Facades\Route;
      
      /*
      |--------------------------------------------------------------------------
      | Web Routes
      |--------------------------------------------------------------------------
      |
      | Here is where you can register web routes for your application. These
      | routes are loaded by the RouteServiceProvider within a group which
      | contains the "web" middleware group. Now create something great!
      |
      */
      
      Route::get('/', function () {
          return view('welcome');
      });
      
      Auth::routes();
      
      Route::get('/home', 'HomeController@index')->name('home');
      
      //下記を追記
      //画像ファイルをアップロードするボタンを設置するページへのルーティング
      Route::get('/upload/image', 'ImageController@input');
      //画像ファイルをアップロードする処理のルーティング
      Route::post('/upload/image', 'ImageController@upload');
      //アップロードした画像ファイルを表示するページのルーティング
      Route::get('/output/image', 'ImageController@output');
      //上記までを追記
      
    3. アプリ名ディレクトリで下記コマンドを実行してコントローラファイルを作成する。

      $ php artisan make:controller ImageController
      
    4. 下記のコントローラファイルが作成される。

      • アプリ名ディレクトリ/app/Http/Controllers/ImageController.php
    5. アプリ名ディレクトリで下記コマンドを実行してコントローラファイルを開く。

      $ vi app/Http/Controllers/ImageController.php
      
    6. 開いたコントローラファイルを下記の様に修正する。

      アプリ名ディレクトリ/app/Http/Controllers/ImageCotroller.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      //下記を追加する
      use Illuminate\Support\Facades\Auth;
      use Illuminate\Support\Facades\Storage;
      
      class ImageUploadController extends Controller
      {
          //下記を追加
          public function input()
          {
              return view('images.input');
          }
      
          public function upload(Request $request)
          {        
              $this->validate($request, [
                  'file' => [
                      // 必須
                      'required',
                      // アップロードされたファイルであること
                      'file',
                      // 画像ファイルであること
                      'image',
                      // MIMEタイプを指定
                      'mimes:jpeg,png',
                  ]
              ]);
      
              if ($request->file('file')->isValid([])) {
                  //バリデーションを正常に通過した時の処理
                  //S3へのファイルアップロード処理の時の情報を変数$upload_infoに格納する
                  $upload_info = Storage::disk('s3')->putFile('/test', $request->file('file'), 'public');
                  //S3へのファイルアップロード処理の時の情報が格納された変数$upload_infoを用いてアップロードされた画像へのリンクURLを変数$pathに格納する
                  $path = Storage::disk('s3')->url($path);
                  //現在ログイン中のユーザIDを変数$user_idに格納する
                  $user_id = Auth::id();
                  //モデルファイルのクラスからインスタンスを作成し、オブジェクト変数$new_image_dataに格納する
                  $new_image_data = new Image();
                  //プロパティ(静的メソッド)user_idに変数$user_idに格納されている内容を格納する
                  $new_image_data->user_id = $user_id;
                  //プロパティ(静的メソッド)に変数$pathに格納されている内容を格納する
                  $new_image_data->path = $path;
                  //インスタンスの内容をDBのテーブルに格納する
                  $new_image_data->save();
      
                  return redirect('/');
              }else{
                  //バリデーションではじかれた時の処理
                  return redirect('/upload/image');
              }
          }
      
          public function output()
          {
              //現在ログイン中のユーザIDを変数$user_idに格納する
              $user_id = Auth::id();
              //imagesテーブルからuser_idカラムが変数$user_idと一致するレコード情報を取得し変数$user_imagesに格納する
              $user_images = Image::whereUser_id($user_id)->get();
              return view('images.output', ['user_images' => $user_images]);
          }
          //上記までを追記
      }
      
    7. アプリ名ディレクトリで下記コマンドを実行してビューファイルを格納するディレクトリを作成する。

      $ mkdir resources/views/images
      
    8. アプリ名ディレクトリで下記コマンドを実行してビューファイルを作成する。

      $ touch resources/views/images/input.blade.php
      $ touch resources/views/images/output.blade.php
      
    9. アプリ名ディレクトリで下記コマンドを実行して画像をアップロードするビューファイルを開く。

      $ vi resources/views/images/input.blade.php
      
    10. 開いたビューファイルを下記の様に記載する。

      アプリ名ディレクトリ/resources/views/images/input.blade.php
      @extends('layouts.app')
      
      @section('content')
      
      <!-- トリガー -->
      <form action="/upload/image" method="POST" enctype="multipart/form-data">
          @csrf
      
          <label for="photo">画像ファイル:</label>
          <input type="file" class="form-control" name="file">
          <br>
          <input type="submit">
      </form>
      @endsection
      
    11. アプリ名ディレクトリで下記コマンドを実行して画像をアップロードするビューファイルを開く。

      $ vi resources/views/images/output.blade.php
      
    12. 開いたビューファイルを下記の様に記載する。

      アプリ名ディレクトリ/resources/views/images/output.blade.php
      @extends('layouts.app')
      
      @section('content')
          <a href="/upload">画像のアップロードに戻る</a>
          <br>
          @foreach ($user_images as $user_image)
              <img src="{{ $user_image['path'] }}">
              <br>
          @endforeach
      @endsection
      
  6. 確認

    1. ブラウザからAWSの下記のページ(コンソール)にアクセスする。

      インスタンス___EC2_Management_Console_と_ec2-user_ip-172-31-9-5__.png

    2. 「接続」をクリックする。

      インスタンス___EC2_Management_Console.png

    3. 下記画像の矢印が指しているURLをコピーして別のブラウザのURL入力欄に貼り付ける。

      インスタンス___EC2_Management_Console.png

    4. 下記のページが表示されることを確認する。

      Laravel.png

    5. 先に表示したLaravelアプリのホーム画面のURLに/upload/imageを末尾に追記したURLにアクセスし、先にに作成したファイルを送信するトリガーのボタンが存在するページを表示する。

      Laravel.png

    6. 任意の画像ファイルを選択し「送信」ボタンを押下する。(送信ボタン押下後にエラーが403エラーが発生する場合は.envファイルの記載に誤りがあることが多い)

      Laravel.png

    7. S3のバケットのテストファイル直下に下記の様にファイルが格納されていることを確認する。(ファイル名はランダムになる。)

      S3_Management_Console_と_ec2-user_ip-172-31-12-2__var_www_html_test_と_下書き一覧_-_Qiita_と_credentials__1_.png

    8. 先に表示したLaravelアプリのホーム画面のURLに/output/imageを末尾に追記したURLにアクセスし、先にアップロードした画像が表示されていることを確認する。

参考文献

miriwo
web系のバックエンドの知識を習得中!
https://miriwo.hatenablog.com/
boomtechcafe
埼玉県の朝霞台を拠点にエンジニアの勉強会、交流会等のイベントを企画しています! 朝霞台駅か北朝霞駅が全然行けちゃうぜってエンジニアの方がいたら是非! もくもく会とかゲーム大会とかもやれたらな~って思ってます。
https://boom-teck-cafe.connpass.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした