【Laravel × AWS S3】プロフィール機能を実装する（画像アップロード）

# はじめに
Web系自社開発企業への転職を目標にLaravel+VuejsでWebアプリケーションを作成しています！
今回は、【Laravel × AWS S3】でプロフィール機能を実装するまでの流れを解説していきます。


# 目次
<!-- タイトルとアンカー名を編集 -->
1. [はじめに](#Chapter1)
2. [モデルを作成](#Chapter2)
3. [ルーティングを追加](#Chapter3)
4. [コントローラーを作成](#Chapter4)
5. [ビューを作成](#Chapter5)
6. [エラー発生時に参考にしたサイト](#error)
7. [教材](#reference)

<!-- 各チャプター -->
<a id="#Chapter1"></a>
# はじめに

まずはじめに【AWS S3】でユーザ登録とバケットの作成を行ってください。
以下の記事を参考にしました。

https://qiita.com/nobu0717/items/4425c02157bc5e88d7b6

https://qiita.com/nobu0717/items/51dfcecda90d3c5958b8

- 環境変数の設定

作成した認証情報を使用して、下記のように環境変数を設定して下さい。

```
AWS_ACCESS_KEY_ID= csvの認証情報に記載されているAccess key ID
AWS_SECRET_ACCESS_KEY= csvの認証情報に記載されているSecret access key
AWS_DEFAULT_REGION=ap-northeast-1 （リージョンをアジアパシフィック東京で作成したため）
AWS_BUCKET= 作成したバケット名
```

- S3パッケージのインストール

以下のコマンドを実行してください。

```
composer require league/flysystem-aws-s3-v3
```

- ファイルシステムの編集

```php:filesystems.php
return [

    'default' => env('FILESYSTEM_DRIVER', 'local'),

    'cloud' => env('FILESYSTEM_CLOUD', 's3'),

    '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'),
        ],

    ],

];
```

<a id="#Chapter2"></a>
# モデルを作成

- Userモデルとマイグレーションファイルの作成

```
php artisan make:model User --migration
```

- マイグレーションを編集

```php:2014_10_12_000000_create_users_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name')->unique();
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('twitter_id')->nullable();
            $table->string('image')->nullable();
            $table->string('password')->nullable();
            $table->rememberToken();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('users');
    }
}
```

- マイグレーションファイルを実行してusersテーブルを作成

```
php artisan migrate
```

- リレーションの設定

```php:User.php
class User extends Authenticatable
{
    //略

    protected $fillable = [
        'name', 'email', 'password', 'twitter_id', 'image',
    ];

    //略
}
```

<a id="#Chapter3"></a>
# ルーティングの追加

```php:web.php
<?php

//認証系のルーティングを追加
Auth::routes();

//略

//ユーザーページ表示
Route::prefix('users')->name('users.')->group(function () {
    Route::get('/{name}', 'UserController@show')->name('show');
    //プロフィール編集画面
    Route::get('/{name}/edit', 'UserController@edit')->name('edit');
    //プロフィール編集処理
    Route::patch('/{name}/update', 'UserController@update')->name('update');
    Route::get('/{name}/likes', 'UserController@likes')->name('likes');
    Route::get('/{name}/followings', 'UserController@followings')->name('followings');
    Route::get('/{name}/followers', 'UserController@followers')->name('followers');
    Route::middleware('auth')->group(function () {
        Route::put('/{name}/follow', 'UserController@follow')->name('follow');
        Route::delete('/{name}/follow', 'UserController@unfollow')->name('unfollow');
    });
});

//略
```

<a id="#Chapter4"></a>
# コントローラーの作成

- 以下のコマンドを実行してコントローラーを作成する

```
php artisan make:controller UserController
```

- 作成したコントローラーにアクションを追加

```php:UserController.php
//プロフィール編集処理
public function update(Request $request, string $name)
{
    $user = User::where('name', $name)->first();
    $all_request = $request->all();

    if (isset($all_request['image'])) {
        $profile_image = $request->file('image');
        $upload_info = Storage::disk('s3')->putFile('image', $profile_image, 'public');
        $all_request['image'] = Storage::disk('s3')->url($upload_info);
    }

    $user->fill($all_request)->save();

    return redirect()->route('users.show', ["name" => $user->name]);
}
```

<a id="#Chapter5"></a>
# ビューを作成

```php:edit.blade.php

//略

<form action="{{ route('users.update', ['name' => $user->name]) }}" method="POST" enctype="multipart/form-data">
  @method('PATCH')
    @csrf
    {{-- 編集フォーム --}}
    <label for="image">
        <img src="{{ $user->image }}" id="img" class="img-fuild rounded-circle" width="80" height="80">
        <input type="file" id="image" name="image" onchange="previewImage(this);" class="d-none">
    </label>
    <div class="md-form col-lg-6 col-md-7 col-sm-8 col-xs-10 mx-auto">
        <label for="name">ユーザー名</label>
        <input type="text" class="form-control" id="name" name="name" required value="{{ $user->name }}">
        <small>3〜15文字で入力してください</small>
    </div>
    <div class="md-form col-lg-6 col-md-7 col-sm-8 col-xs-10 mx-auto">
        <label for="email">メールアドレス</label>
        <input type="text" class="form-control" id="email" name="email" required value="{{ $user->email }}">
    </div>
    <button type="submit" class="btn btn-block cyan darken-3 text-white col-lg-8 col-md-9 col-sm-10 col-xs-12 mx-auto mt-5 mb-5 waves-effect">
        更新する
    </button>
</form>

//略
```

```php:user.blade.php

//略

<a href="{{ route('users.show', ['name' => $user->name]) }}" class="text-dark">
    <img src="{{ $user->image }}" alt="Contact Person" class="img-fuild rounded-circle" width="60" height="60">
</a>

//略
```

```php:card.blade.php

//略

<a href="{{ route('users.show', ['name' => $article->user->name]) }}" class="text-dark">
    <img src="{{ $article->user->image }}" alt="Contact Person" class="img-fuild rounded-circle" width="60" height="60">
</a>

//略
```

<a id="#error"></a>
# エラー発生時に参考にしたサイト

- 【400 Bad Request】

https://teratail.com/questions/375208

- 【404 Not Found】

https://qiita.com/joe-main/items/bba9bcd6923b93399393

https://qiita.com/snct_hu/items/971d512c26dd8b3a3b3c

<a id="#reference"></a>
# 教材

https://www.techpit.jp/courses/18

https://www.techpit.jp/courses/11

https://www.techpit.jp/courses/78