[Laravel]AWS S3に画像をアップロードする際に発生したエラー

ポートフォリオに画像投稿機能を実装しました。
画像ファイルをAWSのS3に保存する際に、いくつか躓いた点があったので、それをアウトプットしようと思います。
#バージョン
PHP　7.4.2
Laravel　6.20.26
Docker　20.10.6
docker-compose　1.29.1
OS　Windows
#編集前のコード
画像ファイルをS3に保存する前はpublicフォルダに保存していました。
「S3に保存する前に、とりあえず画像機能を実装したい」という方は、下記のコードをご参照ください。
画像投稿に該当する箇所のみ記載しています。
また、[【Laravel 7.x】Laravelで画像投稿機能を実装](https://note.com/code82/n/n5f5300822007)を参考にしつつ、コーディングしています。

```PHP:web.php
//略
Route::get('/', 'ArticleController@index')->name('articles.index');
Route::resource('/articles', 'ArticleController')->except(['index', 'show'])->middleware('auth');
Route::resource('/articles', 'ArticleController')->only(['show']);
//略
```
```PHP:Article.php
//略
class Article extends Model
{
    protected $fillable = [
        'title',
        'body',
    ];
//略
```
```PHP:ArticleRequest.php
    //略
    public function rules()
    {
        return [
            'image' => 'mimes:jpeg,jpg,png,gif|max:10240',
            'title' => 'required|max:50',
            'body' => 'required|max:500',
            'tags' => 'json|regex:/^(?!.*\s).+$/u|regex:/^(?!.*\/).*$/u',
        ];
    }
    //略
```
```PHP:ArticleController.php
    //略
    public function store(ArticleRequest $request, Article $article)
    {
        $article->fill($request->all());
        if ($request->file('image')){
            $filename = $request->file('image')->store('public'); // publicフォルダに保存
            $article->image = str_replace('public/', '', $filename); // 保存するファイル名からpublicを除外
        }
        $article->user_id = $request->user()->id;
        $article->save();

        //略

        return redirect()->route('articles.index');
    }
    //略
```
```PHP:〇〇_create_articles_table.php
    //略
    public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->text('body');
            $table->bigInteger('user_id')->unsigned();
            $table->timestamps();
        });
    }
    //略
```
```PHP:〇〇_add_image_to_articles_table.php(imageカラムを後から追加しています)
//略
class AddImageToArticlesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('articles', function (Blueprint $table) {
            $table->string('image')->nullable()->after('id');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('articles', function (Blueprint $table) {
            $table->dropColumn('image');
        });
    }
}
```
```HTML:index.blade.php
                //略
                <img src="{{ asset('/storage/'.$article->image)}}" alt="">
                //略
```
```HTML:create.blade.php
//略
    <form method="POST" action="{{ route('articles.store') }}" enctype="multipart/form-data">
        @csrf
        <div class="container">
            <div class="row">
                <div class="offset-md-2 col-md-8">
                    //略
                    <div class="form-group">
                        <input type="file" class="from-control-file" id="image" name="image">
                    </div>
                </div>
            </div>
        </div>
        <button type="submit" class="btn aqua-gradient btn-block"><i class="fas fa-pen mr-1"></i>投稿する</button>
    </form>
//略
```
#事前準備
S3に保存するためには、事前にAWSの方でIAMユーザーやS3バケットの設定が必要になります。
[Rails, Laravel(画像アップロード)向けAWS(IAM:ユーザ, S3:バケット)の設定](https://qiita.com/nobu0717/items/4425c02157bc5e88d7b6)の手順で進めてください。（この記事とてもわかりやすかったです！）
#編集箇所
[LaravelでAWS S3へ画像をアップロードする](https://qiita.com/nobu0717/items/51dfcecda90d3c5958b8)を参考にしつつ、コードを修正しました。（この記事も非常にわかりやすかったです！）

```PHP:ArticleController.php
    //略
    public function store(ArticleRequest $request, Article $article)
    {
        $article->fill($request->all());
        if ($request->file('image')){

            //==========ここから削除==========
            $filename = $request->file('image')->store('public'); // publicフォルダに保存
            $article->image = str_replace('public/', '', $filename); // 保存するファイル名からpublicを除外
            //==========ここまで削除==========

            //==========ここから追記==========
            // s3アップロード開始
            $image = $request->file('image');
            // バケットの`myprefix`フォルダへアップロード
            $path = Storage::disk('s3')->putFile('myprefix', $image, 'public');
            // アップロードした画像のフルパスを取得
            $article->image_path = Storage::disk('s3')->url($path);
            //==========ここまで追記==========

        }
        $article->user_id = $request->user()->id;
        $article->save();

        //略

        return redirect()->route('articles.index');
    }
    //略
```
```HTML:index.blade.php
                //略
                <img src="{{ asset('/storage/'.$article->image)}}" alt=""> //この行を削除
                <img src="{{ $article->image_path }}"> //この行を追記
                //略
```
#画像ファイルの保存先をAWS S3に変更した際のエラー
###Class 'App\Http\Controllers\Storage' not found
![img s3 error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/935364/68f856f0-edc8-2733-b304-a87066e21465.png)
一番最初のエラーはこれでした。
Storageが見つからないと言われています。
ArticleController.phpに`use Illuminate\Support\Facades\Storage;`を追記することで、storage facadeをインポートし、このエラーは解決しました。
`use Storage;`でも試してみましたが、問題なく動作しました。
###Class 'League\Flysystem\AwsS3v3\AwsS3Adapter' not found
![aws_s3_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/935364/92cd4258-0410-0ffe-f012-18c887eb8641.png)
次のエラーはこれです。
単純に[flysystem-aws-s3-v3](https://github.com/thephpleague/flysystem-aws-s3-v3)のインストールを忘れていました。
ドキュメントにもちゃんと書いてありますね。（[Laravel 6.x ファイルストレージ](https://readouble.com/laravel/6.x/ja/filesystem.html)）
`$ composer require league/flysystem-aws-s3-v3`でインストールできます。
ただし、バージョンの指定がないと2.0がインストールされてエラーになることもあるらしいので、`$ composer require league/flysystem-aws-s3-v3 ~1.0`にしておいた方が安心ですね。
私の場合はDockerのappコンテナの中で実行したかったので、`$ docker-compose exec app composer require league/flysystem-aws-s3-v3 ~1.0`でインストールしました。
![flysystem install.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/935364/00c6a921-0237-7c4c-5bca-6e716b266ef2.png)
###SQLSTATE[42S22]: Column not found: 1054 Unknown column 'image_path' in 'field list'
![mysql error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/935364/d649ca1b-cbb4-c5eb-a60f-b810300fe2db.png)
最後のエラーはこれです。
「articlesテーブルにimage_pathカラムがない」と言われています。
mysqlでarticlesテーブルのカラムを確認すると、、、

```
mysql> show columns from articles;
+------------+-----------------+------+-----+---------+----------------+
| Field      | Type            | Null | Key | Default | Extra          |
+------------+-----------------+------+-----+---------+----------------+
| id         | bigint unsigned | NO   | PRI | NULL    | auto_increment |
| image      | varchar(255)    | YES  |     | NULL    |                |
| title      | varchar(255)    | NO   |     | NULL    |                |
| body       | text            | NO   |     | NULL    |                |
| user_id    | bigint unsigned | NO   |     | NULL    |                |
| created_at | timestamp       | YES  |     | NULL    |                |
| updated_at | timestamp       | YES  |     | NULL    |                |
+------------+-----------------+------+-----+---------+----------------+
7 rows in set (0.01 sec)
```
確かにないですね。
image_pathカラムではなく、imageカラムが正解でした。
下記のように修正しました。

```PHP:ArticleController.php
    //略
    public function store(ArticleRequest $request, Article $article)
    {
        $article->fill($request->all());
        if ($request->file('image')){

            // s3アップロード開始
            $image = $request->file('image');
            // バケットの`myprefix`フォルダへアップロード
            $path = Storage::disk('s3')->putFile('myprefix', $image, 'public');
            // アップロードした画像のフルパスを取得
            $article->image = Storage::disk('s3')->url($path); //この行を修正

        }
        $article->user_id = $request->user()->id;
        $article->save();

        //略

        return redirect()->route('articles.index');
    }
    //略
```

```HTML:index.blade.php
                //略
                <img src="{{ $article->image }}">
                //略
```
#バケットを確認
これで無事にS3へ保存することができるようになりました。
念のため実際にアップロードして、バケットへ画像がアップロードできているか確認しました。
![s3 check.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/935364/aedeb799-08cd-412e-caaa-a719bd85a5d6.png)
ちゃんと保存できていますね！:grin: