Laravelで始めるTwitter風(Twitterクローン)のSNSツール開発チュートリアル
概要
スクールとかの課題だったりLaravelを初めてみたいけど何を作ろうって迷ってる人向けによくあるTwitter風のWEBサイトを作ってみます。
前回
- 第1回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第1回DB設計とMigration)
 - 第2回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第2回Seeder->ログイン/新規登録)
 - 第3回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第3回ユーザ関連とフォロー機能)
 - 第4回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第4回ツイートのCRUD機能)
 - 第5回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第5回ツイートのCRUD機能 編集と削除)
 - 第6回 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第6回コメントといいね機能)
 
第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
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のリソースコントローラを理解して使ってみよう!
では早速Resourceを利用してUsersControllerを作成していきます。
UsersController
php artisan make:controller UsersController --resource
これでapp/Http/Controllers/にUsersController.phpが生成されたと思います。
作成したControllerのアクションとルートを紐づけていきましょう!
Routing
LaravelではControllerのアクション(メソッド)に紐づける際は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');
});
このクロージャーの中にルートを設定することでログインした時にしかアクセス出来ないようにします。
Route::group(['middleware' => 'auth'], function() {
    //
});
Resourceを使ったことない人なら記述これだけで済むの??って思うかもしれませんが、ResourceControllerにすることでシステムが自動的にそれぞれのアクションに紐づけてくれます。
Route::resource('users', 'UsersController');
今回のユーザ機能では一覧/詳細/編集/更新のみを使用するので第3引数にonlyと記述して使うアクションのみを設定しましょう。
Route::resource('users', 'UsersController', ['only' => ['index', 'show', 'edit', 'update']]);
これでRoutingの設定は終わりです。
ユーザを取得
ユーザ一覧表示画面
一覧表示なのでUsersControllerのindexに書いていきます。
Controller
ユーザを取得するgetAllUsers()というメソッドにログインしているユーザIDを引数で渡しています。
Modelから返ってきた結果をViewに返します。
後で使うので色々
useしておきます。
<?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(依存性の注入)というのが内蔵されており、メソッドの引数にインジェクトしたいオブジェクトを書くだけで、そのインスタンスが使用できます。
これをメソッドインジェクションと呼びます( ◠‿◠ )
public function index(User $user)
{
    $all_users = $user->getAllUsers(auth()->user()->id);
}
つまり下記の様にわざわざインスタンスを発行する必要がなく、Userクラスに依存されないということです。
public function index()
{
    $user = new User;
    $all_users = $user->getAllUsers(auth()->user()->id);
}
もちろんメソッド単位ではなく__constructに書くコンストラクタインジェクションというのもあります。
話が逸れたのでModelにメソッドを書いていきます。。
Model
引数で受け取ったログインしているユーザを除くユーザを1ページにつき5名取得しています。
    public function getAllUsers(Int $user_id)
    {
        return $this->Where('id', '<>', $user_id)->paginate(5);
    }
View
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を叩けばこの様に表示できているはずです
フォロー関連の処理
では作成したユーザ一覧画面にフォローする機能とフォローを解除する機能ついでにフォローされているかの処理を書いていきます。
フォロー/フォロー解除/フォローされているか
Controller
フォローとフォロー解除はresourceのどのアクションにも該当しないので、独自に追加します。
    // フォロー
    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
    // フォローする
    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にコードを追加しただけなので、そのままコピペでもいけます。
@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
フォローされているかの判定
@if (auth()->user()->isFollowed($user->id))
フォローしているかの判定
@if (auth()->user()->isFollowing($user->id))
Routing
先ほど設定したresourceのルーティングの下にフォロー/フォロー解除のアクションも紐づけておきます。
// ログイン状態
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');
});
一覧表示(フォロー/フォロー解除/フォロー関係)
これでフォロー関連は終わりです。フォロー/フォロー解除したりして試してみてください。
またログアウトして別のユーザに切り替えてフォロー関係も確かめてみてください。
とりあえずここまでと言いたいがこのままだと後3回で終われる気がしないので、もう少し頑張ります。。😨😨
ユーザ詳細画面
この画面では以下の内容を実装していきます。
- プロフィール
 - プロフィールが自身だった時に編集ボタンを追加
 - プロフィールが自身以外のユーザだった時にフォロー/フォロー解除/フォローされているかの判定を追加
 - 総ツイート数/フォロー数/フォロワー数の表示
 - ユーザがツイートしたタイムラインの表示
 
。。。大変そう😱😱
しかし先ほど作った一覧画面で使った処理を使いまわしていくのでそこまで大変ではないと思います!
Controller
$login_userは自身の情報です。これを元にプロフィールが自身か別のユーザかを判定します。
$userと被るのであえて$login_userにしました(ネーミングセンス皆無)
その$login_userを元にフォロバ関連の判定をしています。
$timelinesはユーザのツイート情報、$~~countってついてるのがカウント関連です。
    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
    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
    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ファイルを作ってください。
@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を読み込むのとツイッターっぽくメニューバーに画像を追加しました。
<!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を開くとこの様になっていると思います!
別のユーザの詳細画面を叩くとこうなっているはずです!
users/2
ユーザ編集
もうひと頑張り😨😨
Controller
$requestで取得したデータをValidator::makeを使ってバリデーションをかけていきます( ◠‿◠ )
Rule::unique('users')->ignore($user->id)の部分はユニークに設定しているscreen_nameやemailを自身のIDの時だけ無効にするという設定です。
これを設定しておかないとバリデーションで弾かれてしまいます。
    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/に保存されます。
    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ファイルを作ってください。
@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のシンボリックリンクが作成されます。
php artisan storage:link
あとは画像を読み込んでいる(<img src="">)以下のコード部分を修正してください!!
- users/index.blade.php
 - users/show.blade.php
 - users/edit.blade.php
 
src="{{ $user->profile_image }}"
↓
src="{{ asset('storage/profile_image/' .$user->profile_image) }}"
- layouts/app.blade.php
 
src="{{ auth()->user()->profile_image }}"
↓
src="{{ asset('storage/profile_image/' .auth()->user()->profile_image) }}"
結果!!
ちなみに画像は好きなバスケ選のラッセル・ウェストブルック(聞いてない)
これにて第3回は終了です( ˘ω˘ )