【Laravel】プロフィール画像アップロード

# 実装したいこと

- usersテーブルにプロフィール画像を追加
- デフォルト画像を用意
- プロフィール画像変更
- 変更時、選択した画像を表示
- アップロード時、画像をリサイズ

# 前提

- Laravel7
- マイグレーション済み
- routes/web.phpにRoute::resource('user', 'UserController');を記述済み


# 実装

データベースに画像のファイル名を保存しておいて、そのファイル名をもとに画像の読み出しを行う、という戦略をとることとします。

## シンボリックリンクを張る
画像の保存場所はstorage/app/public/とします。webからのアクセスを許すために、public/storageからstorage/app/public/へシンボリックリンクを張る必要があります。
以下のコマンドを実行します。

```zsh
% php artisan storage:link
```

public/storage/以下にprofilesディレクトリを作り、その中にデフォルト画像を配置しておいてください。


## profile_imageカラムの追加

マイグレーションファイルを作成します。

```zsh
% php artisan make:migration add_column_to_users_table --table=users
```

カラムを追加する処理を書きます。

```php:database/migrations/20XX_00_00_000000_add_column_to_users_table.php
class AddColumnToUsersTable extends Migration
{
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('profile_image')->default('default.png');
        });
    }

    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('profile_image');
        });
    }
}
```

マイグレーション

```zsh
% php artisan migrate
```

User.phpの$fillableにカラムを追加

```php:app/User.php
    protected $fillable = [
        'name', 'email', 'password', 'profile_image'
    ];
```

## 画像の表示
画像の表示の一例を以下に示します

コントローラー

```php:app/Http/Controllers/ProfileController.php
    public function profile() {
        $user = Auth::user();
        return view('profile', ['user' => $user]);
    }
```

ビュー

```php:resources/views/profile.blade.php
<img src="{{ asset('storage/profiles/'.$user->profile_image) }}" alt="プロフィール画像">
```

## プロフィール画像編集(アップロード)

ここで、Intervention Imageを用います。
Intervention Imageを使用するには、以下のコマンドを実行するだけでいいです。

```zsh
% composer require intervention/image
```

Intervention Imageの使い方は以下のブログを参考にしました。
「[完全網羅！Intervention Image（PHP）で画像を編集する全実例](https://blog.capilano-fw.com/?p=1574)」
公式は[こちら](http://image.intervention.io/getting_started/installation#laravel)。

コントローラー
Intervention Imageでの処理は、saveProfileImage()内で行っています。
バリデーションはフォームリクエストUserRequestで行います。（省略)

```php:app/Http/Controllers/UserController.php
    public function edit($id) {
        $user = Auth::user();
        return view('user.edit', ['user' => $user]);
    }

    public function update($id, UserRequest $request) {
        $user = Auth::user();
        $form = $request->all();

        $profileImage = $request->file('profile_image');
        if ($profileImage != null) {
            $form['profile_image'] = $this->saveProfileImage($profileImage, $id); // return file name
        }
        
        unset($form['_token']);
        unset($form['_method']);
        $user->fill($form)->save();
        return redirect('/home');
    }

    private function saveProfileImage($image, $id) {
        // get instance
        $img = \Image::make($image);
        // resize
        $img->fit(100, 100, function($constraint){
            $constraint->upsize(); 
        });
        // save
        $file_name = 'profile_'.$id.'.'.$image->getClientOriginalExtension();
        $save_path = 'public/profiles/'.$file_name;
        Storage::put($save_path, (string) $img->encode());
        // return file name
        return $file_name;
    }
```

ビュー

- 現在のプロフィール画像を最初に表示
- プロフィール画像をクリックしたら、画像ファイルを選択できる
- 選択した画像ファイルが表示される

以上を満たすように実装します。
「選択画像が変化したらアップロードしようとしている画像を表示する」といった処理をJavaScriptで実装しています。

ファイルをアップロードするためには、enctype="multipart/form-data"をformタグに追加する必要があります。

```php:resources/views/user/edit.blade.php
<form method="post" action="{{ route('user.update', ['user' => $user->id]) }}" enctype="multipart/form-data">
  @csrf
  @method('PATCH')

  <label for="profile_image">プロフィール画像</label>

  <label for="profile_image" class="btn">
    <img src="{{ asset('storage/profiles/'.$user->profile_image) }}" id="img">
    <input id="profile_image" type="file"  name="profile_image" onchange="previewImage(this);">
  </label>
  
  <button type="submit" class="btn btn-primary">
    変更
  </button>
</form>

<script>
  function previewImage(obj)
  {
    var fileReader = new FileReader();
    fileReader.onload = (function() {
      document.getElementById('img').src = fileReader.result;
    });
    fileReader.readAsDataURL(obj.files[0]);
  }
</script>
```

SASS(CSS)

```sass
input#profile_image {
    display: none;
}
```

# おわり
画像のリサイズやJavaScriptの部分は一例に過ぎないので、他のサイトで別の方法をとてもいいかも
Intervention Imageは便利なので上で紹介したサイトで勉強してみては？