Laravelの画像アップロード処理 - まとめ -

# はじめに
画像の保存の処理で苦戦したため、記録として残します。
少しでもつまづいている方の参考になれば嬉しいです。

# 条件
```bash
mac OS "11.2.3 Big Sur"
Laravel Framework "6.20.27"
PHP "8.0.7"
nginx "1.18"
mysql "8.0" 
```

# 画像の保存方法
・DBには画像のパス（画像がどこに保存されているのか）のみを保存

```php:database
$table->string('cooking_img_file')
// パス名を保存するので、String型
```
・画像本体はStorageに保存



## 保存の流れ
大まかな保存の処理の流れは以下のような感じです。

1. フォームに画像を送付して送信

2. 画像本体はStorageに保存

３. DBには画像のパス（画像がどこに保存されているのか）のみを保存


# 実装の流れ
1. [画像データの保存先を作成](#1-画像データの保存先を作成)
2. [シンボリックリンク](#2-シンボリックリンク)
3. [テーブルにカラムの追加](#3-テーブルにカラムの追加)
4. [画像の保存処理](#4-画像の保存処理)

# 実装

# 1. 画像データの保存先を作成

画像本体は、`Storage/app/public`に保存されるようにします。
今回は、`public`ディレクトリの中にさらに料理写真を保存する`recipe`ディレクトリを作成しています。
![スクリーンショット 2021-06-18 10.26.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1379469/75cd42ab-645b-fe43-3f04-233cf8b0d805.png)
上記のように、画像データがどんどん保存されていきます。

そして、DBにはここまでの道のり、つまり`storage/app/public/recipes/....`の部分を保存するようにします。




# 2. シンボリックリンク

画像を保存するディレクトリは作成しましたが、先ほど説明した`storage/app/public`は、ウェブ上に公開されない<b>非公開ディレクトリ</b>であるため、このままでは保存した画像を画面上で見ることはできません。
そのため、<b>公開ディレクトリ</b>である`public`ディレクトリから<b>非公開ディレクトリ</b>にアクセスして画面上に画像を表示させるようにします。

![スクリーンショット 2021-06-24 22.39.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1379469/9258a786-4e9a-1064-c517-a264eb40042d.png)

<font color="tomato"><b>赤枠</b></font>が公開ディレクトリ、<font color=#3b82c4><b>青枠</b></font>が非公開ディレクトリとなっています。
この<font color=#3b82c4><b>青枠</b></font>の中に保存されている画像を<font color="tomato"><b>赤枠</b></font>からアクセスできるようにして、保存されている画像をウェブ上で見れるようにします。

それを行うには、<b>シンボリックリンク</b>というものでこの<font color="tomato"><b>赤枠</b></font>と<font color=#3b82c4><b>青枠</b></font>を繋げないといけません。

そのリンクを貼る方法は簡単で、以下のコマンドをターミナルでうつだけです。

```
#　php artisan storage:link
```

すると、<font color="tomato"><b>赤枠</b></font>の中にある、`storage`ファイルに`矢印`がつきます。
これでリンクが貼られたことになります。

# 3. テーブルにカラムの追加

## 1. 画像を保存するためテーブルに画像のファイルパスを保存するカラムを追加する

```php
$table->string('cooking_img_file')->nullable();
```

今回、画像データは空でもOKという条件で実装しているため`nullable()`を入れています。

## 3. Blade

```php
<form method="POST" action="{{ route('recipes.store') }}" enctype="multipart/form-data">

  <input type="file" name="cooking_img_file" class="d-none" accept="image/png,image/jpeg,image/gif" />
```

① form に `enctype = "multipart/form-data"`を記述する。
<font color="tomato">これを記述しないと画像がアップロードされない。</font>

② input： type = "file"
送られてくるデータはfileとなるため.


# 4. 画像の保存処理

アップロードされた画像の保存処理の全体像は以下のようになります。

```php
    public function store(RecipeRequest $request, Recipe $recipe)
    {
        $recipe->fill($request->all());

        // ❶ アップロードされたファイル情報を $recipe_image に格納
        $recipe_image = $request->file('cooking_img_file');

        // 画像保存処理
        if($recipe_image) {
            // ❷ `recipe`というディレクトリに $recipe_image のデータを保存する
            $path = Storage::disk('public')->putFile('recipes', $recipe_image);
            // ❸ basenameで $path の最後の部分（＝ファイルの名前のみ）を取得
            $recipeFileName = basename($path);
            // ❹ 取得したファイルの名前のみをDBに保存する
            $recipe->cooking_img_file = $recipeFileName;
        } else {
            $path = null;
        }

        $recipe->save();

        // 以下省略
    }
```

## ❶ アップロードされた画像の取得
以下のコードで、アップロードされた画像の情報を取得でき、それを`$recipe_image`に格納しています。

```php
$recipe_image = $request->file('cooking_img_file');
```

`file()`には、第一引数にinputタグのname属性の値を指定します。
（今回はinputタグに、`name="cooking_img_file"`と記述しています。）


## 画像保存処理
「画像の添付は必須ではない」という設定をしているため、if文で画像が添付されている時とされていない時で条件分岐しています。

```php
// 条件分岐

       // 画像がアップロードされている時
        if($recipe_image) {
            $path = Storage::disk('public')->putFile('recipes', $recipe_image);
            $recipeFileName = basename($path);
            $recipe->cooking_img_file = $recipeFileName;
       // 画像がアップロードされていない時
        } else {
            $path = null;
        }
```


## ❷ 画像を保存する場所の指定

```php
$path = Storage::disk('public')->putFile('recipes', $recipe_image);
```
１つずつ見ていきます。

```php
Storage::disk('public')
```

`Storage::disk('○')` → ○にはディスク名を指定。


ディスク名は、デフォルトで以下の３つが`config/filesystems.php`に用意されています。
今回はユーザーも画像を閲覧できるようにしたいので、diskには`public`を指定しています。

1. local：ローカルディスク（非公開）
2. public：ローカルディスク（公開）
3. s3：AWS S3

```php:config/filesystems.php
    'disks' => [

        'local' => [
            'driver' => 'local',
            'root' => storage_path('app'),
        ],

        'public' => [
            'driver' => 'local',
            'root' => storage_path('app/public'),
            'url' => env('APP_URL').'/storage',
            'visibility' => 'public',
        ],

        's3' => [
            'driver' => 's3',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'region' => env('AWS_DEFAULT_REGION'),
            'bucket' => env('AWS_BUCKET'),
            'url' => env('AWS_URL'),
            'endpoint' => env('AWS_ENDPOINT'),
        ],
```

```php
->putFile('recipes', $recipe_image);

// 第一引数 ： 保存先のフォルダ名. 上記では、`Storage/app/public/recipes` になる
// 第二引数 ： 保存したいファイル
```

https://readouble.com/laravel/6.x/ja/filesystem.html


## ❸ 画像のパス名

```php
$recipeFileName = basename($path);

// basename ： パスの最後にある名前の部分を返す
```

https://www.php.net/manual/ja/function.basename.php


現時点で`$path`には、

![スクリーンショット 2021-06-14 18.48.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1379469/669c61a2-2db4-ff23-4d13-0387b3dabcc8.png)
というように、`public/recipes/`が含まれているため、`basename`で画像のファイル名のみを取り出しDBに保存できるようにします。
この時点で`$recipeFileName`には、以下のようにファイル名のみを保存されるようにします。
![スクリーンショット 2021-06-14 18.54.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1379469/0d746a25-a077-32b7-4f08-840ef32ee1db.png)

## ❹ 取得したファイルの名前のみをDBに保存する
❸の処理で取り出した画像のファイル名を`$recipeFileName`に格納します。


```php
$recipe->cooking_img_file = $recipeFileName;
```
そして、画像のファイル名をDBのcooking_img_fileに保存しています。


画像を添付していない場合は、`$path`の値に`null`が入るようにしています。

```php
        } else {
            $path = null;
        }
```

これでコントローラー内の画像保存処理は終了です。

# おわりに
画像の保存処理は、初めは何をしているのか少しややこしかったですが一つ一つみていくことで少しずつ理解ができてきました。
この調子で頑張っていくぞ！

# 参考

https://qiita.com/Charry/items/11d55ab7b1451f68d056

https://qiita.com/kei_Q/items/62cb8747280266956100

https://qiita.com/shosho/items/93cbff79376c41c3a30b#storage

https://qiita.com/t1k2a/items/50081988363cf2fa1bca