【全6回】Laravel5.8でTwitterっぽいSNSツールを作る（第3回ユーザ関連とフォロー機能）

# Laravelで始めるTwitter風（Twitterクローン）のSNSツール開発チュートリアル

## 概要

スクールとかの課題だったりLaravelを初めてみたいけど何を作ろうって迷ってる人向けによくあるTwitter風のWEBサイトを作ってみます。

### 前回

- 第1回 [【全6回】Laravel5.8でTwitterっぽいSNSツールを作る（第1回DB設計とMigration）](https://qiita.com/namizatop/items/d79017aa474966244073)
- 第2回 [【全6回】Laravel5.8でTwitterっぽいSNSツールを作る（第2回Seeder->ログイン/新規登録）](https://qiita.com/namizatop/items/3c20c0d0565906cc6a1a)
- 第3回 [【全6回】Laravel5.8でTwitterっぽいSNSツールを作る（第3回ユーザ関連とフォロー機能）](https://qiita.com/namizatop/items/0c81b0a94a1084cda6de)
- 第4回 [【全6回】Laravel5.8でTwitterっぽいSNSツールを作る（第4回ツイートのCRUD機能）](https://qiita.com/namizatop/items/c9ed67f98fc3e5ce67c7)
- 第5回 [【全6回】Laravel5.8でTwitterっぽいSNSツールを作る（第5回ツイートのCRUD機能 編集と削除）](https://qiita.com/namizatop/items/2273bd0dcc817785b131)
- 第6回 [【全6回】Laravel5.8でTwitterっぽいSNSツールを作る（第6回コメントといいね機能）](https://qiita.com/namizatop/items/0f3f8783c93f3dfa277d)

第3回はユーザ関連とフォロー関連の機能を追加していきます。

## 前提

- PHPをある程度理解している
- Laravelが使える環境がある
- MVC構造をある程度理解している

## 環境

- Mac
- Homestead
- Laravel 5.8

## Controllerを作成

MVCで中枢の役割を担うControllerを作成していきます。

### ResourceController

ResourceとはCRUD操作（作成、一覧、編集、削除など）を予め決められた処理を簡潔にできる機能のことです。
LaravelではartisanコマンドでControllerファイルを生成するとき後ろに`--resource`と付けることでResourceに対応したControllerを自動的に生成してくれます。

言葉で言っても難しいと思うので`tweets`で表してみたいと思います。
※{tweet}の部分はIDが入ります。

| HTTP動詞          |URL                 |アクション            |役割                |
|:-----------------|:-------------------|:------------------|:------------------|
| GET              |/tweets             |index              |一覧表示            |
| GET              |/tweets/create      |create             |新規ツイート入力画面    |
| POST             |/tweets             |store              |新規ツイート投稿処理    |
| GET              |/tweets/{tweet}/show|show               |ツイート詳細画面        |
| GET              |/tweets/{tweet}/edit|edit               |ツイート編集画面        |
| PUT/PATCH        |/tweets/{tweet}     |update             |ツイート編集処理        |
| DELETE           |/tweets/{tweet}     |destory            |ツイート削除処理        |

**Conrollerで書くとこうなります。**

```php:TweetsConroller.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TweetsConroller extends Controller
{
    // 一覧表示
    public function index()
    {
        //
    }

    // 新規ツイート入力画面
    public function create()
    {
        //
    }

    // 新規ツイート投稿処理
    public function store(Request $request)
    {
        //
    }

    // ツイート詳細画面
    public function show($id)
    {
        //
    }

    // ツイート編集画面
    public function edit($id)
    {
        //
    }

    // ツイート編集処理
    public function update(Request $request, $id)
    {
        //
    }

    // ツイート削除処理
    public function destroy($id)
    {
        //
    }
}
```

**どこでどの処理を行なっているのか分かりやすくなりましたね( ◠‿◠ )**

> 参考にしました🙇‍♂️
[Laravelのリソースコントローラを理解して使ってみよう！](https://windii.jp/backend/laravel/resource-controller)

では早速Resourceを利用してUsersControllerを作成していきます。

### UsersController

```terminal
php artisan make:controller UsersController --resource
```

これで`app/Http/Controllers/`に`UsersController.php`が生成されたと思います。
作成したControllerのアクションとルートを紐づけていきましょう！

### Routing

LaravelではControllerのアクション(メソッド)に紐づける際は`routes`の`web.php`に記述します。

```php:routes/web.php
<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

// ログイン状態
Route::group(['middleware' => 'auth'], function() {

    // ユーザ関連
    Route::resource('users', 'UsersController');

});
```

このクロージャーの中にルートを設定することでログインした時にしかアクセス出来ないようにします。

```php
Route::group(['middleware' => 'auth'], function() {
    //
});
```

Resourceを使ったことない人なら記述これだけで済むの？？って思うかもしれませんが、ResourceControllerにすることでシステムが自動的にそれぞれのアクションに紐づけてくれます。

```php
Route::resource('users', 'UsersController');
```

今回のユーザ機能では`一覧`/`詳細`/`編集`/`更新`のみを使用するので第３引数に`only`と記述して使うアクションのみを設定しましょう。

```php
Route::resource('users', 'UsersController', ['only' => ['index', 'show', 'edit', 'update']]);
```

これでRoutingの設定は終わりです。

## ユーザを取得

### ユーザ一覧表示画面

一覧表示なので`UsersController`の`index`に書いていきます。

#### Controller

ユーザを取得するgetAllUsers()というメソッドにログインしているユーザIDを引数で渡しています。
Modelから返ってきた結果をViewに返します。

> 後で使うので色々`use`しておきます。

```php:app/Http/Controllers/UsersController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use App\Models\User;
use App\Models\Tweet;
use App\Models\Follower;

class UsersController extends Controller
{
    public function index(User $user)
    {
        $all_users = $user->getAllUsers(auth()->user()->id);
        
        return view('users.index', [
            'all_users'  => $all_users
        ]);
    }
}
```

##### `メソッドインジェクション`

`index()`の引数はどこから来たの？？って思うかもしれません。
LaravelにはDI（依存性の注入）というのが内蔵されており、メソッドの引数にインジェクトしたいオブジェクトを書くだけで、そのインスタンスが使用できます。

これをメソッドインジェクションと呼びます( ◠‿◠ )

```php
public function index(User $user)
{
    $all_users = $user->getAllUsers(auth()->user()->id);
}
```

つまり下記の様にわざわざインスタンスを発行する必要がなく、Userクラスに依存されないということです。

```php
public function index()
{
    $user = new User;
    $all_users = $user->getAllUsers(auth()->user()->id);
}
```

もちろんメソッド単位ではなく__constructに書くコンストラクタインジェクションというのもあります。

話が逸れたのでModelにメソッドを書いていきます。。

#### Model

引数で受け取ったログインしているユーザを除くユーザを1ページにつき5名取得しています。

```php:app/Models/User.php
    public function getAllUsers(Int $user_id)
    {
        return $this->Where('id', '<>', $user_id)->paginate(5);
    }
```

#### View

`resources/views/`に`users`というディレクトリを作成し、そのディレクトリ内に
`index.blade.php`ファイルを作成してください。

```php:resources/views/users/index.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                @foreach ($all_users as $user)
                    <div class="card">
                        <div class="card-haeder p-3 w-100 d-flex">
                            <img src="{{ $user->profile_image }}" class="rounded-circle" width="50" height="50">
                            <div class="ml-2 d-flex flex-column">
                                <p class="mb-0">{{ $user->name }}</p>
                                <a href="{{ url('users/' .$user->id) }}" class="text-secondary">{{ $user->screen_name }}</a>
                            </div>
                        </div>
                    </div>
                @endforeach
            </div>
        </div>
        <div class="my-4 d-flex justify-content-center">
            {{ $all_users->links() }}
        </div>
    </div>
@endsection
```

#### 一覧表示

自身の環境で`/users`を叩けばこの様に表示できているはずです

<img width="1440" alt="スクリーンショット 2019-08-20 22.41.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293438/8b42fef2-578d-54ff-6065-0c4a80a09acb.png">

## フォロー関連の処理

では作成したユーザ一覧画面にフォローする機能とフォローを解除する機能ついでにフォローされているかの処理を書いていきます。

### フォロー/フォロー解除/フォローされているか

#### Controller

フォローとフォロー解除はresourceのどのアクションにも該当しないので、独自に追加します。

```php:app/Http/Controllers/UsersController.php
    // フォロー
    public function follow(User $user)
    {
        $follower = auth()->user();
        // フォローしているか
        $is_following = $follower->isFollowing($user->id);
        if(!$is_following) {
            // フォローしていなければフォローする
            $follower->follow($user->id);
            return back();
        }
    }

    // フォロー解除
    public function unfollow(User $user)
    {
        $follower = auth()->user();
        // フォローしているか
        $is_following = $follower->isFollowing($user->id);
        if($is_following) {
            // フォローしていればフォローを解除する
            $follower->unfollow($user->id);
            return back();
        }
    }
```

#### Model

```php:app/Models/User.php
    // フォローする
    public function follow(Int $user_id) 
    {
        return $this->follows()->attach($user_id);
    }

    // フォロー解除する
    public function unfollow(Int $user_id)
    {
        return $this->follows()->detach($user_id);
    }

    // フォローしているか
    public function isFollowing(Int $user_id) 
    {
        return (boolean) $this->follows()->where('followed_id', $user_id)->first(['id']);
    }

    // フォローされているか
    public function isFollowed(Int $user_id) 
    {
        return (boolean) $this->followers()->where('following_id', $user_id)->first(['id']);
    }
```

#### View

先ほど記述した`index.blade.php`にコードを追加しただけなので、そのままコピペでもいけます。

```php:resources/views/users/index.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                @foreach ($all_users as $user)
                    <div class="card">
                        <div class="card-haeder p-3 w-100 d-flex">
                            <img src="{{ $user->profile_image }}" class="rounded-circle" width="50" height="50">
                            <div class="ml-2 d-flex flex-column">
                                <p class="mb-0">{{ $user->name }}</p>
                                <a href="{{ url('users/' .$user->id) }}" class="text-secondary">{{ $user->screen_name }}</a>
                            </div>
                            @if (auth()->user()->isFollowed($user->id))
                                <div class="px-2">
                                    <span class="px-1 bg-secondary text-light">フォローされています</span>
                                </div>
                            @endif
                            <div class="d-flex justify-content-end flex-grow-1">
                                @if (auth()->user()->isFollowing($user->id))
                                    <form action="{{ route('unfollow', ['id' => $user->id]) }}" method="POST">
                                        {{ csrf_field() }}
                                        {{ method_field('DELETE') }}

                                        <button type="submit" class="btn btn-danger">フォロー解除</button>
                                    </form>
                                @else
                                    <form action="{{ route('follow', ['id' => $user->id]) }}" method="POST">
                                        {{ csrf_field() }}

                                        <button type="submit" class="btn btn-primary">フォローする</button>
                                    </form>
                                @endif
                            </div>
                        </div>
                    </div>
                @endforeach
            </div>
        </div>
        <div class="my-4 d-flex justify-content-center">
            {{ $all_users->links() }}
        </div>
    </div>
@endsection
```

フォローされているかの判定

```php:resources/views/users/index.blade.php
@if (auth()->user()->isFollowed($user->id))
```

フォローしているかの判定

```php:resources/views/users/index.blade.php
@if (auth()->user()->isFollowing($user->id))
```

#### Routing

先ほど設定したresourceのルーティングの下にフォロー/フォロー解除のアクションも紐づけておきます。

```php:routes/web.php
// ログイン状態
Route::group(['middleware' => 'auth'], function() {

    // ユーザ関連
    Route::resource('users', 'UsersController', ['only' => ['index', 'show', 'edit', 'update']]);

    // フォロー/フォロー解除を追加
    Route::post('users/{user}/follow', 'UsersController@follow')->name('follow');
    Route::delete('users/{user}/unfollow', 'UsersController@unfollow')->name('unfollow');

});
```

#### 一覧表示（フォロー/フォロー解除/フォロー関係）

これでフォロー関連は終わりです。フォロー/フォロー解除したりして試してみてください。
またログアウトして別のユーザに切り替えてフォロー関係も確かめてみてください。

<img width="1440" alt="スクリーンショット 2019-08-20 23.08.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293438/83fea747-4acb-e9dd-db9b-9f202e4bb304.png">

とりあえずここまでと言いたいがこのままだと後3回で終われる気がしないので、もう少し頑張ります。。😨😨

### ユーザ詳細画面

この画面では以下の内容を実装していきます。

- プロフィール
- プロフィールが自身だった時に編集ボタンを追加
- プロフィールが自身以外のユーザだった時にフォロー/フォロー解除/フォローされているかの判定を追加
- 総ツイート数/フォロー数/フォロワー数の表示
- ユーザがツイートしたタイムラインの表示

。。。大変そう😱😱
しかし先ほど作った一覧画面で使った処理を使いまわしていくのでそこまで大変ではないと思います！

#### Controller

`$login_user`は自身の情報です。これを元にプロフィールが自身か別のユーザかを判定します。
`$user`と被るのであえて`$login_user`にしました（ネーミングセンス皆無）
その`$login_user`を元にフォロバ関連の判定をしています。

`$timelines`はユーザのツイート情報、`$~~count`ってついてるのがカウント関連です。

```php:app/Http/Controllers/UsersController.php
    public function show(User $user, Tweet $tweet, Follower $follower)
    {
        $login_user = auth()->user();
        $is_following = $login_user->isFollowing($user->id);
        $is_followed = $login_user->isFollowed($user->id);
        $timelines = $tweet->getUserTimeLine($user->id);
        $tweet_count = $tweet->getTweetCount($user->id);
        $follow_count = $follower->getFollowCount($user->id);
        $follower_count = $follower->getFollowerCount($user->id);

        return view('users.show', [
            'user'           => $user,
            'is_following'   => $is_following,
            'is_followed'    => $is_followed,
            'timelines'      => $timelines,
            'tweet_count'    => $tweet_count,
            'follow_count'   => $follow_count,
            'follower_count' => $follower_count
        ]);
    }
```

#### Model

##### `User`

> User.phpの処理は先ほどのメソッドを使うので省略します。

##### `Tweet`

```php:app/Models/Tweet.php
    public function getUserTimeLine(Int $user_id)
    {
        return $this->where('user_id', $user_id)->orderBy('created_at', 'DESC')->paginate(50);
    }

    public function getTweetCount(Int $user_id)
    {
        return $this->where('user_id', $user_id)->count();
    }
```

##### `Follower`

```php:app/Models/Follower.php
    public function getFollowCount($user_id)
    {
        return $this->where('following_id', $user_id)->count();
    }

    public function getFollowerCount($user_id)
    {
        return $this->where('followed_id', $user_id)->count();
    }
```

#### View

##### `show`

resources/views/users/の中にshow.blade.phpファイルを作ってください。

```php:resources/views/users/show.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8 mb-3">
            <div class="card">
                <div class="d-inline-flex">
                    <div class="p-3 d-flex flex-column">
                        <img src="{{ $user->profile_image }}" class="rounded-circle" width="100" height="100">
                        <div class="mt-3 d-flex flex-column">
                            <h4 class="mb-0 font-weight-bold">{{ $user->name }}</h4>
                            <span class="text-secondary">{{ $user->screen_name }}</span>
                        </div>
                    </div>
                    <div class="p-3 d-flex flex-column justify-content-between">
                        <div class="d-flex">
                            <div>
                                @if ($user->id === Auth::user()->id)
                                    <a href="{{ url('users/' .$user->id .'/edit') }}" class="btn btn-primary">プロフィールを編集する</a>
                                @else
                                    @if ($is_following)
                                        <form action="{{ route('unfollow', ['id' => $user->id]) }}" method="POST">
                                            {{ csrf_field() }}
                                            {{ method_field('DELETE') }}

                                            <button type="submit" class="btn btn-danger">フォロー解除</button>
                                        </form>
                                    @else
                                        <form action="{{ route('follow', ['id' => $user->id]) }}" method="POST">
                                            {{ csrf_field() }}

                                            <button type="submit" class="btn btn-primary">フォローする</button>
                                        </form>
                                    @endif

                                    @if ($is_followed)
                                        <span class="mt-2 px-1 bg-secondary text-light">フォローされています</span>
                                    @endif
                                @endif
                            </div>
                        </div>
                        <div class="d-flex justify-content-end">
                            <div class="p-2 d-flex flex-column align-items-center">
                                <p class="font-weight-bold">ツイート数</p>
                                <span>{{ $tweet_count }}</span>
                            </div>
                            <div class="p-2 d-flex flex-column align-items-center">
                                <p class="font-weight-bold">フォロー数</p>
                                <span>{{ $follow_count }}</span>
                            </div>
                            <div class="p-2 d-flex flex-column align-items-center">
                                <p class="font-weight-bold">フォロワー数</p>
                                <span>{{ $follower_count }}</span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        @if (isset($timelines))
            @foreach ($timelines as $timeline)
                <div class="col-md-8 mb-3">
                    <div class="card">
                        <div class="card-haeder p-3 w-100 d-flex">
                            <img src="{{ $user->profile_image }}" class="rounded-circle" width="50" height="50">
                            <div class="ml-2 d-flex flex-column flex-grow-1">
                                <p class="mb-0">{{ $timeline->user->name }}</p>
                                <a href="{{ url('users/' .$timeline->user->id) }}" class="text-secondary">{{ $timeline->user->screen_name }}</a>
                            </div>
                            <div class="d-flex justify-content-end flex-grow-1">
                                <p class="mb-0 text-secondary">{{ $timeline->created_at->format('Y-m-d H:i') }}</p>
                            </div>
                        </div>
                        <div class="card-body">
                            {{ $timeline->text }}
                        </div>
                        <div class="card-footer py-1 d-flex justify-content-end bg-white">
                            @if ($timeline->user->id === Auth::user()->id)
                                <div class="dropdown mr-3 d-flex align-items-center">
                                    <a href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                        <i class="fas fa-ellipsis-v fa-fw"></i>
                                    </a>
                                    <div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
                                        <form method="POST" action="{{ url('tweets/' .$timeline->id) }}" class="mb-0">
                                            @csrf
                                            @method('DELETE')

                                            <a href="{{ url('tweets/' .$timeline->id .'/edit') }}" class="dropdown-item">編集</a>
                                            <button type="submit" class="dropdown-item del-btn">削除</button>
                                        </form>
                                    </div>
                                </div>
                            @endif
                            <div class="mr-3 d-flex align-items-center">
                                <a href="#"><i class="far fa-comment fa-fw"></i></a>
                                <p class="mb-0 text-secondary">{{ count($timeline->comments) }}</p>
                            </div>
                            <div class="d-flex align-items-center">
                                <a href="#"><i class="far fa-comment fa-fw"></i></a>
                                <p class="mb-0 text-secondary">{{ count($timeline->favorites) }}</p>
                            </div>
                        </div>
                    </div>
                </div>
            @endforeach
        @endif
    </div>
    <div class="my-4 d-flex justify-content-center">
        {{ $timelines->links() }}
    </div>
</div>
@endsection
```

ついでに外枠のレイアウトの`layouts/app.blade.php`も修正していきます。

アイコンを使用したいのでFontAwesomeのSDNを読み込むのとツイッターっぽくメニューバーに画像を追加しました。

```php:layouts/app.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <!-- CSRF Token -->
        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>{{ config('app.name', 'Laravel') }}</title>

        <!-- Scripts -->
        <script src="{{ asset('js/app.js') }}"></script>

        <!-- Fonts -->
        <link rel="dns-prefetch" href="//fonts.gstatic.com">
        <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

        <!-- Styles -->
        <link href="{{ asset('css/app.css') }}" rel="stylesheet">
        <!-- Font Awesome -->
        <link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet">
    </head>
    <body>
        <div id="app">
            <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
                <div class="container">
                    <a class="navbar-brand" href="{{ url('/') }}">
                        {{ config('app.name', 'Laravel') }}
                    </a>
                    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                        <span class="navbar-toggler-icon"></span>
                    </button>

                    <div class="collapse navbar-collapse" id="navbarSupportedContent">
                        <!-- Left Side Of Navbar -->
                        <ul class="navbar-nav mr-auto">

                        </ul>

                        <!-- Right Side Of Navbar -->
                        <ul class="navbar-nav ml-auto align-items-center">
                            <!-- Authentication Links -->
                            @guest
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                                </li>
                                @if (Route::has('register'))
                                    <li class="nav-item">
                                        <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                    </li>
                                @endif
                            @else
                                <li class="nav-item">
                                    <img src="{{ auth()->user()->profile_image }}" class="rounded-circle" width="50" height="50">
                                </li>
                                <li class="nav-item dropdown">
                                    <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                        {{ auth()->user()->name }} <span class="caret"></span>
                                    </a>

                                    <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                                        <a href="{{ url('users/' .auth()->user()->id) }}" class="dropdown-item">プロフィール</a>
                                        <a href="{{ route('logout') }}" class="dropdown-item"
                                        onclick="event.preventDefault();
                                                        document.getElementById('logout-form').submit();">
                                            {{ __('Logout') }}
                                        </a>

                                        <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                            @csrf
                                        </form>
                                    </div>
                                </li>
                            @endguest
                        </ul>
                    </div>
                </div>
            </nav>

            <main class="py-4">
                @yield('content')
            </main>
        </div>
    </body>
</html>
```

これで一通り詳細画面は出来上がったので画面を開いてみます🤳

#### ユーザ詳細画面

ログインしているユーザのIDが1の場合`/users/1`を開くとこの様になっていると思います！

<img width="1440" alt="スクリーンショット 2019-08-24 11.40.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293438/c6260594-67e1-61d8-0abd-9a53296580f4.png">

別のユーザの詳細画面を叩くとこうなっているはずです！

`users/2`

<img width="1440" alt="スクリーンショット 2019-08-24 11.50.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293438/e3040250-b2a6-09c5-28e5-df46ec58ba43.png">

#### ユーザ編集

もうひと頑張り😨😨

##### `Controller`

$requestで取得したデータを`Validator::make`を使ってバリデーションをかけていきます( ◠‿◠ )

`Rule::unique('users')->ignore($user->id)`の部分はユニークに設定している`screen_name`や`email`を自身のIDの時だけ無効にするという設定です。
これを設定しておかないとバリデーションで弾かれてしまいます。

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

    public function update(Request $request, User $user)
    {
        $data = $request->all();
        $validator = Validator::make($data, [
            'screen_name'   => ['required', 'string', 'max:50', Rule::unique('users')->ignore($user->id)],
            'name'          => ['required', 'string', 'max:255'],
            'profile_image' => ['file', 'image', 'mimes:jpeg,png,jpg', 'max:2048'],
            'email'         => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($user->id)]
        ]);
        $validator->validate();
        $user->updateProfile($data);

        return redirect('users/'.$user->id);
    }
```

##### `Model`

$paramsの中に画像があれば処理を分けています。

`$file_name = $params['profile_image']->store('public/profile_image/');`
こうすることで画像ファイルが`/storage/app/public/profile_image/`に保存されます。

```app/Models/User.php
    public function updateProfile(Array $params)
    {
        if (isset($params['profile_image'])) {
            $file_name = $params['profile_image']->store('public/profile_image/');

            $this::where('id', $this->id)
                ->update([
                    'screen_name'   => $params['screen_name'],
                    'name'          => $params['name'],
                    'profile_image' => basename($file_name),
                    'email'         => $params['email'],
                ]);
        } else {
            $this::where('id', $this->id)
                ->update([
                    'screen_name'   => $params['screen_name'],
                    'name'          => $params['name'],
                    'email'         => $params['email'],
                ]); 
        }
        
        return;
    }
```

##### `View`

resources/views/users/の中にedit.blade.phpファイルを作ってください。

```php:resources/views/users/edit.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Update</div>

                <div class="card-body">
                    <form method="POST" action="{{ url('users/' .$user->id) }}" enctype="multipart/form-data">
                        @csrf
                        @method('PUT')

                        <div class="form-group row align-items-center">
                            <label for="profile_image" class="col-md-4 col-form-label text-md-right">{{ __('Profile Image') }}</label>

                            <div class="col-md-6 d-flex align-items-center">
                                <img src="{{ $user->profile_image }}" class="mr-2 rounded-circle" width="80" height="80" alt="profile_image">
                                <input type="file" name="profile_image" class="@error('profile_image') is-invalid @enderror" autocomplete="profile_image">

                                @error('profile_image')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="screen_name" class="col-md-4 col-form-label text-md-right">{{ __('Account Name') }}</label>

                            <div class="col-md-6">
                                <input id="screen_name" type="text" class="form-control @error('screen_name') is-invalid @enderror" name="screen_name" value="{{ $user->screen_name }}" required autocomplete="screen_name" autofocus>

                                @error('screen_name')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>

                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ $user->name }}" required autocomplete="name" autofocus>

                                @error('name')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ $user->email }}" required autocomplete="email">

                                @error('email')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">更新する</button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```

一旦これで更新は完了です！
アカウント名や画像を更新して確認してみて下さい！

しかし保存した画像を読み込むために`/storage/app/public/profile_image/`にシンボリックリンクを設定しないといけないのでシンボリックリンクを設定します。

**シンボリックリンク**とは簡単に言うと、ショートカットのようなものです。
あるファイルやフォルダにシンボリックリンクを貼ることで、別のパス（別の場所）にあるファイルやフォルダを参照する事が出来ます。

> シンボリックリンクを設定する必要は？？
>> Laravelのドキュメントルート（アプリケーションを公開するときに参照されるフォルダ）が`public`になっています。
その`public`フォルダにファイルや処理の全てが集約されています。
そのため保存した画像も`public`ディレクトリ内に存在しないとアクセスする事ができません。
そこでシンボリックリンク を利用して`storage`ディレクトリにアクセスできるようにします。

###### Laravelのシンボリックリンク 

Laravelでは`storage`のシンボリックリンクをartisanコマンドで設定できます( ˘ω˘ )

`vagrant ssh`でプロジェクトのフォルダに移動して以下のコマンドを叩いてください。
それだけで勝手に`public`フォルダに`storage`のシンボリックリンクが作成されます。

```terminal
php artisan storage:link
```

あとは画像を読み込んでいる(`<img src="">`)以下のコード部分を修正してください！！

- users/index.blade.php
- users/show.blade.php
- users/edit.blade.php

```php
src="{{ $user->profile_image }}"
```

↓

```php
src="{{ asset('storage/profile_image/' .$user->profile_image) }}"
```

- layouts/app.blade.php

```php
src="{{ auth()->user()->profile_image }}"
```

↓

```php
src="{{ asset('storage/profile_image/' .auth()->user()->profile_image) }}"
```

### 結果！！

<img width="1440" alt="スクリーンショット 2019-08-24 16.59.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293438/2e3b9c18-bf41-74f1-78a7-b66169a117fe.png">

> ちなみに画像は好きなバスケ選のラッセル・ウェストブルック（聞いてない）

これにて第3回は終了です( ˘ω˘ )

次回 -> [【全6回】Laravel5.8でTwitterっぽいSNSツールを作る（第4回ツイートのCRUD機能）](https://qiita.com/namizatop/items/c9ed67f98fc3e5ce67c7)