4
2

More than 3 years have passed since last update.

Laravelアプリの画像アップロード機能をローカル環境と本番環境で条件分岐

Last updated at Posted at 2021-05-21

概要

PHP初学者にとって画像アップロード機能の実装はなかなかの鬼門ですよね。本番環境でS3を用いて画像を表示させるのも時間がかかりました。
という事で、備忘録として記事にしてみます。

今回は、新規投稿ページで画像を保存し詳細ページで画像を表示、編集ページで画像を変更できるように実装していきます。
さらに本番環境(Heroku)で画像が表示できるようにAWSのS3を使用します。
ローカルと本番のコードを条件分岐し、ローカルではstorage/app/public下のフォルダへ、本番ではS3へ画像を保存できるようにしていきます。

環境

  • PHP 7.2.34
  • Laravel 7.30.4
  • mySQL 8.0.23
  • Docker
  • Heroku
  • AWS S3

前提

画像アップロード機能を追加するアプリケーションがあらかじめ作成されていること、HerokuやAWSなどの登録が完了済であることを前提として進めていきます。
あくまでこの記事は画像アップロード機能の実装がメインです。

ローカルで実装

私が参考にした記事はこちらです→https://note.com/koushikagawa/n/n74380a1f3643

既存のテーブルにimageカラム追加

terminal
% php artisan make:migration add_image_url_to_posts_table --table=posts

postsテーブルに画像を保存するためのカラムを追加します。↑のコマンドを実行するとマイグレーションファイルが作成されます。

0000_00_00_000000_add_image_to_posts_table.php
public function up()
{
  Schema::table('posts', function (Blueprint $table) {
    $table->string('image'); // 追加
  });
}

public function down()
{
  Schema::table('posts', function (Blueprint $table) {
    $table->dropColumn('image'); // 追加
  });
}

このように記述しマイグレーションします。

terminal
% php artisan migrate

phpmyadminやSequel Aceでimageカラムができているかを確認してみて下さい。

新規投稿ページにて画像を保存

PostController.php
public function store(Request $request)
{
  $time = date("Ymdhis");
  $post->image = $request->image->storeAs('public/post_images',$time.'_'.Auth::user()->id. '.jpg');
}

画像の保存先はstorage/app/public/post_imagesです。storage/app/public下にpost_imagesフォルダを作成しておいて下さい(post_imagesは任意の名前です)。

create.blade.php
<form action="{{ route('posts.store') }}" method="POST" enctype="multipart/form-data">
  <p>
    <input type="file" name="image">
  </p>
</form>

enctype="multipart/form-data"がないと画像保存ができないので忘れずに。
039725bc405157e523d8d0ee9a6a48e6.png
画像を選択し投稿すると
8a0041462fdfe3d1343d228ad2a28e09.png
Sequel Aceにて、imageにデータがちゃんと入っています。

詳細ページに画像を表示

PostController.php
public function show(Post $post)
{
  return view('posts.show',[
    'post' => $post,
    'image' => str_replace('public/', 'storage/', $post->image) // 追加
  ]);
}

showメソッドにこのように追加します。str_replace関数で'public/'と'storage/'を置換しています。
laravelではpublic下に画像を直接置くのではなく、storage下に置くことを推奨しているそうです。

terminal
% php artisan storage:link

public下からstorage下にシンボリックリンクを張ります。

show.blade.php
@if ($image)
  <p><img class="img-fluid" src ="/{{ $image }}"></p>
@endif

Bootstrapを導入し、class="img-fluid"を追加すると画像が良き大きさになるのでオススメです。
ce7c0aaffbccfd2f884d99a9e3c2ec83.jpg
画像が表示できました。

編集ページで画像を変更

PostController.php
use Illuminate\Support\Facades\Storage; // 追加

public function update(Request $request)
{
  if($request->hasFile('image')) {
    Storage::delete('public/post_images/' . $post->image); // 画像削除
    $time = date("Ymdhis");
    $post->image = $request->image->storeAs('public/post_images', $time.'_'.Auth::user()->id. '.jpg');
  }
}

保存されてある画像を削除し新たに画像を保存します。

edit.blade.php
<form action="{{ route('posts.update') }}" method="POST" enctype="multipart/form-data">
  <p class="card-text">
    <input type="file" name="image">
  </p>
</form>

createと一緒ですね。
これで画像アップロード機能が実装できました。

本番環境で画像アップロード

AWSのS3に移動しバケットを作成します。

a3ddd4d79078faac6a5002e66ac8dfd4.png
バケット名を入力します↑
68f82c67a05f1686d6bdf1c40e774e95.png
バケットの設定をこのようにして「バケットを作成」を押します。

Herokuに環境変数を設定

terminal
% heroku config:set APP_ENV=production
% heroku config:set AWS_ACCESS_KEY_ID=◯◯◯◯◯◯◯◯◯◯
% heroku config:set AWS_BUCKET=バケット名
% heroku config:set AWS_DEFAULT_REGION=ap-northeast-1
% heroku config:set AWS_SECRET_ACCESS_KEY=△△△△△△△△△△

APP_ENVは条件分岐する上で必要になります。
アクセスキーとシークレットキーも忘れずに設定して下さい。

ローカル環境と本番環境の記述を条件分岐

ローカルと本番の記述を条件分岐するにあたって参考にさせて頂きました↓
https://qiita.com/qwe001/items/96a83fadcfaeb3cd4a6e

PostController.php
public function store(Request $request)
{
  if (app()->isLocal()) {
    // ローカル環境
    $time = date("Ymdhis");
    $post->image = $request->image->storeAs('public/post_images', $time.'_'.Auth::user()->id. '.jpg');
  } else {
    // 本番環境
    $image = $request->file('image');
    $path = Storage::disk('s3')->putFile('/', $image, 'public');
    $post->image = $path;
  }       
}

public function update(Request $request)
{
  if($request->hasFile('image')) {
    if (app()->isLocal()) {
      // ローカル環境
      Storage::delete('public/post_images/' . $post->image);
      $time = date("Ymdhis");
      $post->image = $request->image->storeAs('public/post_images', $time.'_'.Auth::user()->id. '.jpg');
    } else {
      // 本番環境
      $image = $request->image;
      Storage::disk('s3')->delete($image);
      $path = Storage::disk('s3')->putFile('/', $image, 'public');
      $post->image = $path;
    }
  }
}

ローカルか本番かの判断は、ローカルではenvファイルのAPP_ENV=lacal(デフォルトですでに設定されています)、本番は先ほど設定した環境変数のAPP_ENV=productionによってされているようです。

show.blade.php
@if ($image)
  @if (App::environment('local'))
    // ローカル環境
    <p><img class="img-fluid" src="/{{ $image }}"></p>
  @else
    // 本番環境
    <p><img class="img-fluid" src="https://バケット名.s3.ap-northeast-1.amazonaws.com/{{ $image }}"></p>
  @endif
@endif

こちらでは違う書き方で条件分岐させてもらいました。いずれにしろ、挙動確認では問題なかったので試して頂ければと思います。
設定したバケット名を忘れずに当てはめて下さい。

PostController.php
public function show(Post $post)
{
  return view('posts.show', [
    'post' => $post,
    'image' => str_replace('public/', 'storage/', $post->image),
  ]);
}

showメソッドなのですが、このままでも本番環境で問題なく動きます。本番環境において、public下とstorage下が置換されたところで何の意味も影響もないでしょうから。
ただ、「本番環境のコードだけでいい!」「条件分岐もしない!」という方もいるかもしれません、

PostController.php
public function show(Post $post)
{
  return view('posts.show', [
    'post' => $post,
    'image' => $post->image // 変更
  ]);
}

その場合はこのコードの方がスッキリして簡潔です。

別に条件分岐しなくても本番環境のコードだけでいいと思います。ただ、それだと(S3設定以降の)ローカルで保存した画像も一緒にS3のバケットに保存されてごっちゃになります。
それが嫌なので条件分岐してみました。

私はこれでローカルでも本番でも問題なく動いたので参考にして頂ければと思います。
最後までこの記事を読んで頂き、ありがとうございました。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2