Laravel掲示板に画像投稿機能を追加してみた！

paizaラーニングの[Laravel入門講座](https://paiza.jp/works/laravel/primer)で作成した掲示板に画像投稿機能を付けてみました。

#画像投稿の仕組み
１．新規投稿ページからファイル選択して画像を投稿
２．storeメソッドでpublicフォルダに画像を格納。ファイルパスをデータベースに格納
３．ファイルパスを読み取り、画像を表示

#1.新規投稿機能を追加
新規投稿ビューにファイルアップロードのフィールドを追加する

```php:new.blade.php
<h1>MYPICTURE</h1>
<p>{{$message}}</p>
{{Form::open(['route'=>'picture.store','files'=>true])}}
<div class="form-group">
    <!--投稿した画像のユーザーネームをDBに格納させる-->
    {{Form::label('user_name','Name:')}}
    {{Form::text('user_name',null)}}
</div>
<div class='form-group'>
    <!--投稿したコメントをDBに格納するビュー-->
    {{Form::label('content','Content:')}}
    {{Form::text('content',null)}}
</div>
<div class='form-group'>
    <!--ファイルを読み込む-->
    {{Form::file('thefile')}}
</div>
<div class="form-group">
    {{Form::submit('作成する',['class'=>'btn btn-primary'])}}
    <a href='{{ route("picture.list")}}'>一覧に戻る</a>
</div>
{{Form::close()}}

```

Form::openメソッドの「'files'=>true」はファイルのアップロードを行うことを指定します。
「{{Form::file('thefile')}}」で下図のようなファイルアップロードのフォームが出来上がります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/ee885f35-3021-cad6-837e-4bb8019ebcd3.png)

##シンボリックリンク
ビューにファイルアップロードの処理を記述したので次はコントローラ...の前に、シンボリックリンクを張りましょう。
シンボリックリンクとは、特定のファイルやディレクトリを指し示す別のファイルを作成し、それを通じて本体を参照できるようにする仕組みのことで、あるフォルダから別のフォルダに参照できるリンクを作成します。
Laravelにはこの機能が備わっており、ファイルアップロードはこの仕組みを利用します。
ディレクトリをプロジェクトの直下に移動させ、以下のコマンドをターミナルに打ち込みます。

```
$ php artisan storage:link
```
すると、publicの直下に以下のようなフォルダが出来上がります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/8706f84b-6ea7-a78a-dab8-7f4607b94d15.png)
これにより、publicからstorageフォルダにアクセスできるようになりました！
#2.storeメソッド
アップロードした画像をフォルダに、画像のファイル名をデータベースにそれぞれ格納するメソッドをstoreメソッドに追加します。

```php:PictureController.php(storeメソッド)
    public function store(Request $request)
    {
        $picture=new picture();
        //投稿した画像とコメントをDBに格納させる
        $picture->user_name=$request->user_name;
        $picture->content=$request->content;
        $filename=$request->file('thefile')->store('public');       //storageフォルダに投稿した画像を保存しファイルパスを格納
        $picture->image=str_replace('public/','',$filename);        //ファイル名から「public/」を取り除く
        $picture->save();
         //tinkerコマンドと同じ
        return redirect()->route('picture.show',['id'=>$picture->id]);
    }
```

アップロードした画像ファイルはstore('')関数でランダムで名前が付けられ、指定したディレクトリに保存されます。（この場合はpublic以下のフォルダに指定）
この時、デフォルトで「storage/app/」に保存され、ファイル名に「public/」と付いてしまう為、ファイル名からこれを取り除く為に一旦$filenameにファイル名を格納し、str_replace関数で「public/」を指定することで取り除いています。
あとは投稿者名やコメントと共にデータベースにファイル名を格納させます。

#3.ファイルパスを読み取り、画像を表示
showメソッドとビューを以下のように記述します。

##showメソッド

```php:PictureController.php(showメソッド)
    public function show(Request $request,$id,Picture $picture)
    {
        $message='This is your picture.'.$id;
        $picture=Picture::find($id);
        Storage::disk('local')->exists('public/storage/'.$picture->image);
         //$idに格納された番号と一致したデータを引っ張り出す。
        return view('show',['message'=>$message,'picture'=>$picture]);
    }
```
今回はローカル環境でこのアプリを作ったため、投稿した画像はローカルディスクに保存されます。
なので、「Storage::disk('local')」でローカルディスクを読み取り、「exists」に第一引数として「public/storage/」を、第二引数としてデータベースのimageカラムに格納されているファイル名をそれぞれ指定させます。

##ビュー
詳細ページのビューに以下の記述を追加します。

```
<p><img src="{{ asset('/storage/'.$picture->image)}}"></p>
```
これでstorageフォルダ直下に格納された画像ファイルを、ファイル名ごとに表示することができました！

#完成形
以上のことを踏まえて作成した画像投稿機能ですが、実際はこんな感じになります。

##新規投稿画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/67908efb-5ef3-2bdf-e5c5-6f5015c5f852.png)

##詳細ページ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/400df6b3-d1c0-db98-3cde-4315b5dafd54.png)

#課題
今回は「画像を投稿させ、それを表示させる。」ことを最優先にしたので、以下２つの課題があります。

##保存するフォルダについて
今回は「storage/app/public」直下に画像を保存させましたが、これだとアプリを公開した際に外から画像のフォルダが丸見えになってしまいます。
ローカル環境ではまだ良いかもしれませんが、公開するとなるとセキュリティ面で非常に危険な状態なので、投稿した画像のフォルダが外から見えないようにしなければなりません。

##ファイルのアップロードについて
「画像をアップロードする」って言ってるくせに、実際に組んだのは「ファイルのアップロード」の機能です。
これだと、画像に限らず、テキストだろうが何だろうが「ファイル」という形の物でしたら基本的に何でもアップロードできます。
これを「画像ファイルのみ」にするには、「バリデーション」というファイルの種類を指定する処理が必要になります。

以上２つ、課題として挙げた機能を今後追加していこうと思います。

#まとめ
今回はLaravel掲示板に画像投稿機能を追加してみました。
正直、思い付きで「掲示板に画像を投稿出来るようにした方がよくね？」と考えて実装してみましたが、実際にやってみるとかなり難しく、時間がかかってしまいました...。
途中、teratailで質問を投げましたが、回答やアドバイスをして下さった方々に、この場を借りて御礼を申し上げます。

#参考文献
[[Laravel] ユーザーのアイコン画像を投稿、表示させる機能の実装したのでメモ（画像の保存場所は？シンボリックリンクって？）](https://qiita.com/u-dai/items/8a904cc7fd2795c0e70d)
[[Laravel]保存した画像が404エラーで表示されない](https://qiita.com/Baisel/items/49b16ce3231c749f6c6f)
[Laravel シンボリックリンクで少しハマった話](https://qiita.com/YasuhaF/items/9a897e83afe1819d97af)
[IT用語辞典 シンボリックリンク](http://e-words.jp/w/%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AA%E3%83%83%E3%82%AF%E3%83%AA%E3%83%B3%E3%82%AF.html#:~:text=%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AA%E3%83%83%E3%82%AF%E3%83%AA%E3%83%B3%E3%82%AF%E3%81%A8%E3%81%AF%E3%80%81%E3%82%AA%E3%83%9A%E3%83%AC%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0,%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B%E4%BB%95%E7%B5%84%E3%81%BF%E3%80%82)
[Laravel Recipes ファイルアップロードのフィールドを作成する](http://recipes.laravel.jp/recipe/212)
[Laravelで画像をアップロードする方法](https://php-junkie.net/framework/laravel/upload_image/)