Laravelを使った画像アップロード実装法3選

#はじめに
Laravelで画像アップロードする方法を色々試してみたので簡単にまとめました。
最終的にHerokuにデプロイして使えるようにします。
今回Laravelのバージョンは5.8です。:point_right:[Laravel 5.8 インストール](https://readouble.com/laravel/5.8/ja/installation.html)

.envファイル修正後、以下のコマンドでモデルとコントローラー、マイグレーションを一気に作成します。

```bash

$ php artisan make:model Image -m -c -r
```
`-m`で作成したモデルに関連したマイグレーションファイルを同時に作成し、さらに`-c`でコントローラーも作成します。`-r`はResourcefulなアクションを自動で定義したコントローラーを作成してくれます。

#Contents
以下の流れで説明していきます。

1. [ファイルシステムを利用して画面に表示](https://qiita.com/RitaChan/items/d59001430f50789c570f#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6%E7%94%BB%E9%9D%A2%E3%81%AB%E8%A1%A8%E7%A4%BA)
2. [DBに画像のバイナリデータを直接入れて表示(base64)](https://qiita.com/RitaChan/items/d59001430f50789c570f#db%E3%81%AB%E7%94%BB%E5%83%8F%E3%81%AE%E3%83%90%E3%82%A4%E3%83%8A%E3%83%AA%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E7%9B%B4%E6%8E%A5%E5%85%A5%E3%82%8C%E3%81%A6%E8%A1%A8%E7%A4%BAbase64)
3. [S3に画像アップロードして表示](https://qiita.com/RitaChan/items/d59001430f50789c570f#s3%E3%81%AB%E7%94%BB%E5%83%8F%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%97%E3%81%A6%E8%A1%A8%E7%A4%BA)

#ファイルシステムを利用して画面に表示
マイグレーションファイルは以下のようにします。

```2019_08_06_131550_create_images_table.php
public function up()
{
    Schema::create('images', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->string('image');
        $table->timestamps();
    });
}
```

次にフォームをサクッと作成します。

```php:index.blade.php
<form method="POST" action="{{ route('image.store') }}" enctype="multipart/form-data">
@csrf
    <input id="image" type="file" name="image">
    <button type="submit">
       アップロード
    </button>
</form>
@foreach($images as $image)
    <div>
        <img src="{{ asset('storage/' . $image->image) }}" alt="image" style="width: 30%; height: auto;"/>
    </div>
@endforeach
```
ここで重要なのは`enctype="multipart/form-data"`ですね。ファイルをアップロードをする場合に必ず必要になります。
あとは、`@foreach($images as $image)`ですが、この`$images`は以下のControllerのindexメソッドの`['images'=>$images]`と結びついています。

そして、コントローラーを実装。必要な部分だけのせます。

```ImageController.php
class ImageController extends Controller
{
    public function index()
    {
        $images = Image::all();
        return view('index', ['images'=>$images]);
    }

    public function store(Request $request)
    {
        $image = new Image();
        $uploadImg = $request->image;
        if($uploadImg->isValid()) {
            $filePath = $uploadImg->store('public');
            $image->image = str_replace('public/', '', $filePath);
        }
        $image->save();
        return redirect('/');
    }
}
```
最後にRouteの追加ですね。

```web.php
<?php
Route::get('/', 'ImageController@index');
Route::post('/', 'ImageController@store')->name('image.store');
```
完了です。でもこのままだとLaravelのデフォルトでは storage/app/public/ 配下はWebからのアクセスが許可されていません。
Webからのアクセスを許可するために以下のコマンドを打ちます。

```bash

$ php artisan storage:link
```
あとはローカルで動作確認してみてください。以下の画像のようにできているかと思います。
<img width="70%" alt="スクリーンショット 2019-08-07 18.03.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330545/7ba183b8-5171-0ca6-2cf9-32c90369f016.png">

phpMyAdminでも確認してみました。
<img width="914" alt="スクリーンショット 2019-08-07 18.04.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330545/543fcd69-7fc9-61d2-a9b2-34a72073dcab.png">

できてますね！よーし、Herokuにデプロイだ！としたいところなんですが、これだとダメなんです...。
Herokuのdynoは1日1回再起動するらしく、アップロードしたファイルも全消去されて表示されなくなります:angel:

てことで、別の方法を考えます。:point_down:
#DBに画像のバイナリデータを直接入れて表示(base64)
今回のマイグレーションファイルは以下のようにします。

```2019_08_06_131550_create_images_table.php
public function up()
{
    Schema::create('images', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->longText('image'); //ここが変わった
        $table->timestamps();
    });
}
```
次にフォームを少し変えます。

```php:index.blade.php
<form method="POST" action="{{ route('image.store') }}" enctype="multipart/form-data">
@csrf
    <input id="image" type="file" name="image">
    <button type="submit">
       アップロード
    </button>
</form>
@foreach($images as $image)
    <div>
        <img src="data:image/png;base64,{{ $image->image }}" alt="image" style="width: 30%; height: auto;">
    </div>
@endforeach
```
imgタグのsrc属性に`data:image/png;base64,`をつけてあげることによって、base64でエンコードされた画像を表示するという記法になります。

そしてコントローラーも変えていきましょう。今回も必要な部分だけのせます。

```ImageController.php
class ImageController extends Controller
{
    public function index()
    {
        $images = Image::all();
        return view('index', ['images'=>$images]);
    }

    public function store(Request $request)
    {
        $image = new Image();
        $image->image = base64_encode(file_get_contents($request->image));
        $image->save();
        return redirect('/');
    }
}
```
storeメソッドが少し短くなりましたね。
受け取った画像データを`base64_encode`でエンコードしてDBに格納しています。
それを上のindex.blade.phpのimgタグのところで表示している流れです。

web.phpは変更ないので早速ローカルで動作確認してみてください。以下の画像のようにできているかと思います。
<img width="70%" alt="スクリーンショット 2019-08-07 23.29.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330545/004bb75c-3c13-3523-4037-7da6eeebe16e.png">

phpMyAdminの方も確認しました。めちゃくちゃ長い文字列が格納されてました。。。
<img width="1032" alt="スクリーンショット 2019-08-07 23.30.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330545/dc75ff49-dde7-eba1-7be9-1757164f098e.png">


これならHerokuにデプロイしても画像はしっかり表示されます。
...ですが、base64でエンコードするとサイズがその分増えます。そうするとHerokuの無料枠のデータベースサイズは5 MBなので大きいサイズの画像をたった２枚アップロードするだけで終了です。
![D-PxjlwU0AAB0DU.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330545/012a8924-cb9c-8755-4689-2b8da7df2e63.jpeg)

なら課金すんべ！と思って料金調べました。→[ClearDB MySQL - Add-ons - Heroku Elements
](https://elements.heroku.com/addons/cleardb)
ん〜〜〜高スギィ！！！！

てことで、別の方法を考えます。:point_down:
#S3に画像アップロードして表示
Amazon Simple Storage Service(Amazon S3)を利用するにあたって、[こちら](https://qiita.com/tiwu_official/items/ecb115a92ebfebf6a92f)の方の記事がとても参考になります。これでS3へアップロードできるようにします。(S3の環境情報を登録まで)

※追加で「ブロックパブリックアクセス (バケット設定)」の部分のチェックボックスは全て外しといてください。ブロックすると画像が表示されませんでした。また、システムのアクセス許可の管理は「付加しない」で良いと思います。
<img width="80%" alt="スクリーンショット 2019-08-08 4.34.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330545/55d78514-f05a-5f67-71e6-13142b59e7e1.png">


その後、マイグレーションファイルを以下のようにします。

```2019_08_06_131550_create_images_table.php
public function up()
{
    Schema::create('images', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->string('image');
        $table->timestamps();
    });
}
```
そしてまたフォームを少し変えます。

```php:index.blade.php
<form method="POST" action="{{ route('image.store') }}" enctype="multipart/form-data">
@csrf
    <input id="image" type="file" name="image">
    <button type="submit">
       アップロード
    </button>
</form>
@foreach($images as $image)
    <div>
        <img src="{{ $image->image }}" alt="image" style="width: 30%; height: auto;"/>
    </div>
@endforeach
```
最後にコントローラーを変えていきましょう。今回も必要な部分だけのせます。

```ImageController.php
<?php

namespace App\Http\Controllers;

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

class ImageController extends Controller
{
    public function index()
    {
        $images = Image::all();
        return view('index', ['images'=>$images]);
    }

    public function store(Request $request)
    {
        $image = new Image();
        $uploadImg = $image->image = $request->file('image');
        $path = Storage::disk('s3')->putFile('/', $uploadImg, 'public');
        $image->image = Storage::disk('s3')->url($path);
        $image->save();
        return redirect('/');
    }
}

```
今回はstoreメソッドでStorageファサードを使用しているので、必ず`use Illuminate\Support\Facades\Storage;`を追加してあげてください。

また、putFileの第一引数はバケット内の階層です。フォルダを作成しなければ上と同じでOKです。
例でバケット内に”Test”フォルダを作成しその中に画像を入れたいとすると、第一引数は`'/test'`としないといけません。
第二引数は受け取った画像が入った変数。
第三引数は`'public'`で他からのアクセスを全般的に許可することとなり、ファイルのURLによるアクセスが可能となります。

以上で実装完了です。まず、ローカルで動作確認してみましょう！以下の画像のようになっていると思います。
<img width="70%" alt="スクリーンショット 2019-08-08 16.45.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330545/7fbc68d0-0b00-d48c-ccf5-b9ceee209650.png">

phpMyAdminの方も確認しました。imageテーブルにファイルのURLが格納されていますね。
<img width="943" alt="スクリーンショット 2019-08-08 16.46.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330545/d5c7160a-abe5-cdf4-19fc-b612a54a1f85.png">

Amazon S3のバケット内も確認してみました。
<img width="1436" alt="スクリーンショット 2019-08-08 16.45.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330545/6017a625-8023-05aa-9197-28bbf4d2f037.png">

しっかり実装できていますね。ではHerokuにデプロイしていきましょう！！
Herokuについては、[こちら](https://qiita.com/tamappe/items/a175596e9aec725e1d2d)の方の記事がとても参考になります。
#画像アップロードするにあたって躓いたところ
一部の画像だけなぜかアップロードができない現象が起きました。
その一部の画像はサイズがでかく、php.iniがデフォルトでupload_max_filesizeが2Mになっていることでした。
ここはHerokuを使ってLaravelの画像アップロードする際に気をつけないといけない部分ですね。
てことで、php.ini の設定を変えましょう:point_down:

php.ini の設定を適用するためには public ディレクトリに`.user.ini`というファイルを作成し、そこに以下の設定を書き込みます。

```ini:.user.ini
post_max_size = 20M
upload_max_filesize = 20M
```
#さいごに
ここまで実装できるまでに、多くのサイトを参考にさせて頂きました。有難う御座います。
最後の「S3に画像アップロードして表示」を使って「みんなで共有！ランチマップ」を自作してみました！
ぜひ利用してみて下さい！

- [みんなで共有！ランチマップ](https://lunchmap-app.herokuapp.com)
- [GitHubURL](https://github.com/RitaChan0401/LunchMapApp)