Laravelで画像ファイルを保存したい

今参加しているプログラミングスクールでの課題で、LaravelでECsiteを作っているのですが、Laravelへの画像の保存に苦戦したので、備忘録ようにメモします

## 作りたいもの
- Ecsite（の一部）
- 商品の一覧を表示した際に、画像ファイルが保存されるようにしたい

## どこに画像を保存するか？
初めはDBにそのまま画像をぶち込もうと思っていたのですが、どうもそれはスマートではないらしい。。。</br>
[terateil:データベースに画像を保存するのはありでしょうか？](https://teratail.com/questions/81233)

すると、LaravelではStorage/app/public内に保存するのが基本のようです。そして、各々の画像へのパスをDBに保存することにしました。

## 保存するフォームを作る
今まで、お問い合わせフォームを作っていたので、それを使いまわして画像アップロード画面のひな形を作ります。bladeについては省略です。</br>
具体的には、input(入力画面)->confirm(確認画面)->complete(完了画面)の形にして、input->confirmの際に一度Storage/app/public内のtempディレクトリに仮保存し、confirm->completeの際にStorage/app/public内のproductImageディレクトリに移動します。

```php:web.php
Route::get('/image_input', 'ImageController@getImageInput');
Route::post('/image_confirm', 'ImageController@postImageConfirm');
Route::post('/image_complete', 'ImageController@postImageComplete');
```

```php:image_input.blade.php
@section('body')
    <form action="image_confirm" method="post" enctype="multipart/form-data" id="form">
        @csrf
        ファイル：
        <input type="file" name="imagefile" value=""/><br /><br />

        商品名：<br />
        <input type="text" name="product_name" size="50" value="{{ old('name') }}"/><br /><br />

        <input type="submit" name="confirm" id="button" value="確認" />
    </form>
@endsection
```

formタグ内で

```
enctype="multipart/form-data
```
を指定しないと、controllerでfileメソッドを使ってファイルを取り出そうとしてもnullになります。

```php:image_confirm.blade.php
@section('body')
    <form action="image_complete" method="post">
        @csrf
        <table border="1">
            <tr>
                <td>画像</td>
                <td><img src="{{ $data['read_temp_path'] }}" width="200" height="130"></td>
            </tr>
            <tr>
                <td>商品名</td>
                <td>{{ $data['product_name'] }}</td>
            </tr>
        </table>
        <input type="submit" name="action" value="送信" />
    </form>
@endsection
```

```
@section('body')
    <p>商品のアップロードが完了しました</p>
@endsection
```

また、DBは次のmigrationファイルを基にして作り、Modelも作成しておきます

```php:xxxx_xx_xx_xxxxxx_create_products_table.php
public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->bigIncrements('product_id');
            $table->string('path');
            $table->string('product_name');
            $table->timestamps();
        });
    }
```

## 問題はcontroller...
取り敢えず、フォームから画像と名前を受け取って、画像には一意のファイル名としてtempディレクトリに保存します。また、sessionに保存して、confirm画面から読み込めるようにします。

```php:ImageController.php
public function getImageInput(){
    return view('image_input');
}

public function postImageConfirm(ImageUploadRequest $request){
    $post_data = $request->except('imagefile');
    $imagefile = $request->file('imagefile');

    $temp_path = $imagefile->store('public/temp');

    $product_name = $post_data['product_name'];

    $data = array(
        'temp_path' => $temp_path,
        'product_name' => $product_name,
    );
    $request->session()->put('data', $data);

    return view('image_confirm', compact('data') );
```
storeメソッドは、引数内のディレクトリに一意のファイル名として保存し、そこへのパスを返します。

## 画像を読み込むディレクトリ
Laravelの場合、画像を読み込むディレクトリはpublic/storageディレクトリです。</br>
ややこしいのでもう一度書きます。</br>
__保存:storage/app/public__</br>
__読込:public/storage__</br>
名前を変えてほしいですね。なので、public/storageから、storage/app/public内を読み込めるよう（シンボリックリンクを張る）にします。

```
php artisan storage:link
```

これで、storage/app/public内を覗けるようになりました。

## ディレクトリが違えばパスも違う
では、いざconfirm画面を表示！　させても画像が表示されません...</br>
それもそのはず、読み込みに行っているパスが
__(storage/app/)public/temp/xxx.jpeg__
となっているからですね。でも先ほど書いた通り、読み込むのは
__(public/)storage/temp/xxx.jpeg__
でないといけないですね。</br>
よって、controllerを次のように変えます。

```php:ImageController.php
    $temp_path = $imagefile->store('public/temp');
    $read_temp_path = str_replace('public/', 'storage/', $temp_path); //追加

    $product_name = $post_data['product_name'];

    $data = array(
        'temp_path' => $temp_path,
        'read_temp_path' => $read_temp_path, //追加
        'product_name' => $product_name,
    );
    $request->session()->put('data', $data);
```

str_replaceメソッドで、public/をstorage/に置き換えました。</br>
いざ表示させると、無事できました！

## 後は移動させてDBへ保存
次に、confirm->completeにおいて、tempディレクトリからproductImageディレクトリに移動させ、DBに保存します

```
public function getImageComplete(Request $request) {
    $data = $request->session()->get('data');
    $temp_path = $data['temp_path'];
    $read_temp_path = $data['read_temp_path'];

    $filename = str_replace('public/temp/', '', $temp_path);
    //ファイル名は$temp_pathから"public/temp/"を除いたもの
    $storage_path = 'public/productimage/'.$filename;
    //画像を保存するパスは"public/productimage/xxx.jpeg"

    $request->session()->forget('data');

    Storage::move($temp_path, $storage_path);
    //Storageファサードのmoveメソッドで、第一引数->第二引数へファイルを移動

    $read_path = str_replace('public/', 'storage/', $storage_path);
    //商品一覧画面から画像を読み込むときのパスはstorage/productimage/xxx.jpeg"
    $product_name = $data['product_name'];

    $this->productcontroller->path = $read_path;
    $this->productcontroller->product_name = $product_name;
    $this->productcontroller->save();
    return view('image_complete');
}
```

無事、画像が保存されました！
後は読み込むところで

```
<img src="{{ $path }}" width="200" height="130">
```

とすればOKです！

# 参考にしたサイト
[Laravel学習帳:画像アップロード（基本）](https://laraweb.net/tutorial/2707/)</br>
[Qiita:Laravel5.6でファイルアップロードの実装と躓いたところまとめ](https://qiita.com/maejima_f/items/7691aa9385970ba7e3ed)</br>
[Qiita:[Laravel] ユーザーのアイコン画像を投稿、表示させる機能の実装したのでメモ（画像の保存場所は？シンボリックリンクって？）](https://qiita.com/u-dai/items/8a904cc7fd2795c0e70d)</br>