Laravelでフォロー機能を実装してみる

テーマ

どうも!!naoto555です。
今回は、Laravelを用いて昨今のSNSでは当たり前になったフォロー機能フォロワー機能を実装する方法をご紹介したいと思います。Usersテーブルの詳細や登録しているUsersデータのダミーデータの作り方に関しましては、「Seederで日本語のダミーユーザーを作成する(こちらの記事)」をご参考にして頂けると幸いです。
(2024.2記載)

開発環境

統合開発環境 aws cloud9
PHP 8.1.16
Laravel 9.52
login機能 Laravel/jetstream(livewire)
Database SQlite(version 3.7.17)

解説

①Followモデルとテーブルの作成

まずは、誰が誰をフォローしているかを示すテーブルをモデルで作成していきます。

php artisan make:model Follow --all


database/migrations/2023_XX_XX_XXXXXX_create_follows_table.php

//一部抜粋
public function up()
{
    Schema::table('follows', function (Blueprint $table) {
        $table->id();
        $table->bigInteger('user_id')->unsigned();      //自分のID
        $table->bigInteger('follow_id')->unsigned();    //友達のID
        $table->timestamps();
        
        
        //外部キーの制約
        $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        $table->foreign('follow_id')->references('id')->on('users')->onDelete('cascade');

        //user_idとfollow_idの重複する組み合わせは許さない
        $table->unique(['user_id', 'follow_id']);
    });
}

$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');の意味としては、followsテーブルのuser_idカラムは、Usersテーブルを親テーブルとしていてUserテーブルのIDを参照してテーブルにレコードしなさい。という意味です。
また、”onDelete(‘cascade’)”を書くことで、親テーブル(Usersテーブル)のレコードが削除された場合に、それに関連する子テーブル(followsテーブル)のレコードも自動的に削除する設定になります。
また、$table->unique(['user_id', follow_id]);を書くことで重複した”user_id”と”follow_id”の組み合わせが登録されることを防ぐことができます。

このテーブルは下図のように、例えばuser(id=1)がuser(id=2)をフォローしたときに、それぞれのIDがfollowsテーブルに書き込まれるように設計していきます。

最後に、マイグレーションを実行し、追加したカラムをデータベースに反映させます。

php artisan migrate

 

②Usersモデルに関数メソッドを記載

続いて、Usersモデルに関数メソッドを書いていきます。

app/Modeles/User.php

use Illuminate\Support\Facades\Auth;    //追加

//---------------------------- 中略 ----------------------------

//user_idのユーザーが、フォローしているユーザー(ID:follow_id)を抽出
public function follows()
{
    return $this->belongsToMany(User::class, 'follows', 'user_id', 'follow_id');
}

//ログインユーザー(Auth::user())との関係性を返す
//0:どちらもフォローしていない 1:ログインユーザーが相手をフォロー 2:相手がログインユーザーをフォロー 3:お互いにフォロー
public function relation()
{
    $id = $this->id;

    //ログインユーザーが対象ユーザーをフォローしているか?をtrue/falseで返す
    $follow = (boolean) Auth::user()->follows()->where('follow_id', $id)->first();

    //対象Userが自分をフォローしているか?をtrue/falseで返す
    $follower = (boolean) $this->follows()->where('follow_id', Auth::user()->id)->first();

    if(!($follow) && !($follower)){ //0:どちらもフォローしていない
        $result = 0;
    }elseif($follow && !($follower)){ //1:ログインユーザーが相手をフォロー
        $result = 1;
    }elseif(!($follow) && $follower){ //2:相手がログインユーザーをフォロー
        $result = 2;
    }else{ //3:お互いにフォロー
        $result = 3;
    }

    return $result;
}

//ログインユーザーは、対象ユーザーをフォローしているか?
public function isFollow()
{
    $id = $this->id;
    $isFollow = (boolean) Auth::user()->follows()->where('follow_id',$id)->first();

    return $isFollow;
}

 

③コントローラの編集

続いて、コントローラの編集して、index機能,follow機能の追加を行います。Userコントローラ未作成の場合は、まず、以下のコマンドでUserコントローラを作成してください。

php artisan make:controller UserController --model=User

app/Http/Controllers/UserController.php

use Illuminate\Support\Facades\Auth;    //追加
use App\Models\Follow;                  //追加

//--------------------- 中略 ---------------------

public function index()
{
    $users = User::where('id','!=',Auth::user()->id)->get();
    return view('users.index', compact('users'));
}

public function follow(Request $request)
{
    $follow_id = $request->follow_id;
    
    //ログインユーザーが対象のユーザーをフォローしているか? 
    $isFollow = (boolean) Follow::where('user_id', Auth::user()->id)->where('follow_id', $follow_id)->first();

    if($isFollow){
        $unfollow = Follow::where('user_id', Auth::user()->id)->where('follow_id', $follow_id);
        $unfollow->delete();
    }else{
        $follow = new follow();
        $follow->user_id = Auth::user()->id;
        $follow->follow_id = $follow_id;
        $follow->save();
    }

    return back();
}

 

④ルーティングの編集


routes/web.php

use App\Http\Controllers\UserController;    //追加

//-------------------------- 中略 --------------------------

Route::get('/users/index',[UserController::class,'index'])->name('users.index');
Route::post('/users/follow',[UserController::class,'follow'])->name('users.follow');

 

⑤ビューの編集

続いて、ビューの編集していきます。ビューファイル未作成の場合は、まず、以下のコマンドでビューを作成してください。

#フォルダとファイルの作成
mkdir resources/views/users
touch resources/views/users/index.blade.php


resources/views/users/index.blade.php

<div style="margin:30px">
    <table border="1">
        <tr>
            <th style="padding:10px;">ID</th>
            <th style="padding:10px;">名前(name)</th>
            <th style="padding:10px;">メールアドレス(email)</th>
            <th style="padding:10px;">関係性</th>
            <th style="padding:10px;">フォローボタン</th>
        </tr>
        @foreach($users as $user)
        <tr style="padding:10px;">
            <td style="padding:10px;">{{ $user->id }}</td>
            <td style="padding:10px;">{{ $user->name }}</td>
            <td style="padding:10px;">{{ $user->email }}</td>
            <td style="padding:10px;">
                @switch($user->relation())
                    @case(0) <span>N/A</span> @break
                    @case(1) <span>あなたがフォロー</span> @break
                    @case(2) <span>あなたをフォロー</span> @break
                    @case(3) <span>相互フォロー</span> @break
                    @default <span>N/A</span>
                @endswitch
            </td>
            <td style="padding:10px; justify-content:center;">
                <form method="POST" action="{{ route('users.follow') }}">
                    @csrf
                    <input name="follow_id" type="hidden" value="{{ $user->id }}" />
                    @if($user->isFollow())
                        <button type="submit" style="background-color: #efc9d2;">
                            フォロー解除
                        </button>
                    @else
                        <button type="submit" style="background-color: #ccffcc;">
                            フォローする
                        </button>
                    @endif
                </form>
            </td>
        </tr>
        @endforeach
    </table>
</div>

 

⑥Seederを実行してユーザーデータを挿入してみる

以下のコマンドでユーザーデータをリセットし、以下のコマンドを打ちダミーユーザー10件を挿入する。
ユーザーのダミーデータの挿入する方法は、こちらの記事にまとめてあるのでご参照下さい。

#tinker起動
php artisan tinker

#Usersテーブルのデータをクリア
> User::truncate();

#tinker終了
> quit

#ダミーデータの挿入
php artisan db:seed --class=UserSeeder

 

⑦ログイン用のテストユーザーを登録

以下のコマンドを順に打ち、ログイン用のテストユーザーを登録

#tinkerの起動
php artisan tinker

#起動後、以コマンドを打っていく
> $user=new User();
> $user->name = "test_user";
> $user->email = "test@mail.example";
> $user->password = Hash::make('password');
> $user->save(); 

#tinkerの終了
> quit

これで、以下の内容でUsersテーブルに情報が追加されました。

name test_user
email test@mail.example
password password

users/indexのビューをみてみると下記のような表示となります。
表示はされていませんが、ID=11にログイン用のテストユーザーが登録されています。

⑧FollowテーブルにSeederデータを挿入

テストユーザー(ID=11)と各ユーザーが、以下の関係性になるようにSeederを作成します。

ID=1~3はテストユーザーと相互フォロー
ID=4~6はテストユーザーのみフォロー
ID=7~9は相手のみテストユーザーをフォロー
ID=10はお互いにフォローなし

FollowSeeder.phpを以下のように編集します。

database/seeders/FollowSeeder.php

use App\Models\User; //追加
use App\Models\Follow; //追加

class FollowSeeder extends Seeder
{

    public function run()
    {
        $data = [
            //ID1~3は、相互フォロー
            [1, 11],[2, 11],[3, 11],[11, 1],[11, 2],[11, 3],

            //ID4~6は、ログインユーザーがフォロー
            [11, 4],[11, 5],[11, 6],

            //ID7~9は、ログインユーザーをフォロー
            [7, 11],[8, 11],[9, 11]
        ];

        foreach($data as $record){
            Follow::create([
                'user_id' => $record[0],
                'follow_id' => $record[1],
            ]);
        }
    }
}

 

以下のコマンドで、シーダーを挿入します。

php artisan db:seed --class=FollowSeeder

 

完成図

作成したテストユーザーでログインしてビューをみてみます。
以下のコマンドをターミナルで打ち、ログインしてusers/indexページを開いていきます。

php artisan serve --port=8080

 

Indexページに、ログインユーザーとの関連性とフォローボタンが設置されていることが確認できます。

↑ボタンのほうもきちんと機能しました。

まとめ&補足

今回は、中間テーブルを作成せずに専用のテーブルを用意しました。
このように作成すると、例えば自分がフォローしている人、自分のフォロワー、相互フォロワーをリスト表示したい場合にとても簡単なので専用テーブルを用意しました。

リストを表示方法も以下に示してみます。
まずはリスト表示するページのルーティングを設定します。UserコントローラーのListファンクションとでも命名します。

routes/web.php

use App\Http\Controllers\UserController;    //追加

//------------------- 中略 -------------------

Route::get('/users/list',[UserController::class,'list'])->name('users.list');  //追加

 

続いて、コントローラの編集してlist機能を追加していきます。
Followモデルを作成したことで、”use App\Models\Follow;“を宣言することで、Userコントローラ内で直接Followテーブルの操作ができるのでとても便利です。

app/Http/Controllers/UserController.php

use App\Models\Follow; //追加

//------------------------ 中略 ------------------------

public function list(){
    //ログインユーザーがフォローしているユーザーのIDを$follow_idにArrayで格納
    $follow_id = Follow::where('user_id', Auth::user()->id)->pluck('follow_id')->toArray();

    //ログインユーザーをフォローしているユーザーのIDを$follower_idにArrayで格納
    $follower_id = Follow::where('follow_id', Auth::user()->id)->pluck('user_id')->toArray();

    //相互フォローユーザーのIDを$follow_eachに格納
    $follow_each_id = array_intersect($follow_id, $follower_id);

    //相互フォローユーザー
    $follow_eachs = User::whereIn('id', $follow_each_id)->get();

    //ログインユーザーのフォローしているユーザー(相互フォローユーザーを除く)
    $follows = User::whereIn('id',$follow_id)->whereNotIn('id',$follow_each_id)->get();

    //ログインユーザーをフォローしているユーザー(相互フォローユーザーを除く)
    $followers = User::whereIn('id',$follower_id)->whereNotIn('id',$follow_each_id)->get();


    return view('users.list', compact('follows','followers','follow_eachs'));
}

ビューを作成し、それを編集してコントローラから受け取った$follows, $followers, $follow_eachsを表示していきます。

#Linuxコマンドで、list.blade.phpを作成
touch resources/views/users/list.blade.php

resources/views/list.blade.php

<div style="margin:30px;">
    <h3>相互フォロー</h3>
    <table border="1">
        <tr>
            <th style="padding:10px;">ID</th>
            <th style="padding:10px;">名前</th>
        </tr>

        @foreach($follow_eachs as $follow_each)
        <tr>
            <td style="padding:10px;">
                {{ $follow_each->id }}
            </td>
            <td style="padding:10px;">
                {{ $follow_each->name }}
            </td>
        </tr>
        @endforeach
    </table>

    <h3>あなたがフォローしているユーザー</h3>
    <table border="1">
        <tr>
            <th style="padding:10px;">ID</th>
            <th style="padding:10px;">名前</th>
        </tr>

         @foreach($follows as $follow)
         <tr>
             <td style="padding:10px;">
                 {{ $follow->id }}
             </td>
             <td style="padding:10px;">
                 {{ $follow->name }}
             </td>
         </tr>
         @endforeach
    </table>

    <h3>あなたをフォローしているユーザー</h3>
    <table border="1">
        <tr>
            <th style="padding:10px;">ID</th>
            <th style="padding:10px;">名前</th>
        </tr>

        @foreach($followers as $follower)
        <tr>
            <td style="padding:10px;">
                {{ $follower->id }}
            </td>
            <td style="padding:10px;">
                {{ $follower->name }}
            </td>
        </tr>
        @endforeach
    </table>
</div>

 

/users/listをブラウザで表示すると、以下のように表示されます。