Laravelで画像ファイルをアップロードする

Laravelでユーザーのアバター画像をアップロードする想定で実装してみました。

#テーブルにカラムを追加する
usersテーブルに画像のパスを保存するカラムを追加し、migrateを実行します。

```php:add_image_path_to_users_table.php

class AddImagePathToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('image_path')->after('email_verified_at');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('image_path');
        });
    }
}

```

#画像をアップロードするフォームを作成する。
ここで注意点があります。
① Laravelでフォームを作成する際、CSRF対策がなされていないと、エラーになってしまうので、素のHTMLタグでフォームを作成する際は`{{ csrf_field }}`を追記しましょう。

`{{ csrf_field }}`で出力される内容は以下のようになります。type属性はhidden。フォームデータを送信する時に、ハッシュ値がリクエストされます。

```html
<input type="hidden" name="_token" value="tyKtcrH2KscjxVAsxb3KHXG5ppZ8te6PA6SJY8df">
```

※CSRFとはクロスサイトリクエストフォージェリの略で、Webアプリケーションの脆弱性をついた攻撃手法です。信頼できるユーザーになりすまし、ユーザーの不利益になるような処理を行うことができます。Laravelではこの対策として、各セッションごとにトークンを自動的に生成しています。

>
webミドルウェアグループに含まれている、VerifyCsrfToken ミドルウェアが、リクエスト中のトークンとセッションに保存されているトークンが一致するか、確認しています。
https://readouble.com/laravel/5.7/ja/csrf.html

②formタグに `enctype='multipart/form-data'` を忘れず、記述しましょう。これは、複合データ型であることを示し、１回のHTTP通信で、複数の種類のデータ形式を扱う事でできるようになります。(今回の場合は、jpeg、png等)


```php:edit.blade.php
@section('content')
    <form route="/users/{$user->id}/edit" method="post" enctype='multipart/form-data'>
        {{ csrf_field() }}

        @isset ($filename)
            <div>
                <img src="{{ asset('storage/avatar/' . $user->image_path) }}">
            </div>
        @endisset

        <div>
            <input type="hidden" name="id" value="{{$user->id}}">
        </div>
        <div>
            <input type="text" name="name" value="{{$user->name}}">
        </div>
        <div>
            <input type="text" name="email" value="{{$user->email}}">
        </div>
        <div>
            <input type="file" name="image">
        </div>
        <input type="submit" value="更新する">
    </form>
@endsection
```

#Routeを追加する

```PHP:web.php
Route::get('/users/{user}/edit', 'UserController@edit')->name('users.edit');
Route::post('/users/{user}/edit', 'UserController@update');
```

#Controllerを定義
fileメソッドにはinputタグのname属性を、storeメソッドには、保存したいパス(ディレクトリ)を指定します。
(任意のファイル名を指定したい場合はstoreAsメソッドを使用してく

ここではユーザーのavatarをアップロードする事を想定して、`public/avatar`を指定します。ファイルが保存される場所は、`storage/app`ディレクトリになるので、この場合、`storage/app/public/avatar`ディレクトリに画像が保存されます。

次に、このままでは、カラムにパスの情報ごと保存されてしまうので、`basename()`を使用して、画像名のみ保存するようにしています。


viewで表示する際には`asset()`を使用して ```<img src="{{ asset('storage/avatar/' . $user->image_path) }}">``` と指定します。

```php:UserController.php

public function edit(Request $request, User $user)
{
    return view('users.edit', compact('user'));
}

public function update(Request $request, User $user)
{
    $user->name = $request->name;
    $user->email = $request->email;
    $path = $request->file('image')->store('public/avatar');
    $user->image_path = basename($path);
    $user->save();

    return view('users.edit', compact('user'))->with('filename', basename($path));
}
```

#シンボリックリンクを張る
最後にシンボリックリンクを張りましょう。

アップロードしただけでは、ブラウザからアップロードした画像を表示する事は出来ません。

ブラウザからサーバー上のファイルにアクセスするには、publicディレクトリの下に保存する必要があります。しかし、アップロードしたファイルが保存される場所は、`storage/app`ディレクトリになるので、`public/storage`から`storage/app/public`へリンクを持たせる必要があります。Laravelではそのコマンドが用意されています。以下、コマンドを実行しましょう。

```
$ php artisan storage:link
```

リンクが貼られていれば、`public/storage`にリンクされている事を示すiconが表示されます。
<img width="242" alt="スクリーンショット 2019-08-12 13.12.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33632/4a3f864c-03a6-af6c-76bf-eca20848af4f.png">


以上、自分用のメモの意味合いが強いですが、これで画像のアップロード、表示が実現できます。
もっと良い方法があれば、コメント頂けると嬉しいです。