テーブル作成
フォロー・フォロワーの状態管理をするため、followsテーブルを作成します。
マイグレーションを作成します。
php artisan make:migration create_follows_table
マイグレーションファイルを以下のように編集します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('follows', function (Blueprint $table) {
$table->id();
$table->foreignId('following')->constrained('users')->onDelete('cascade');
$table->foreignId('followed')->constrained('users')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('follows');
}
};
followingとfollowedの2つのカラムを追加しただけですが、ここがポイントとなります。
foreignIdを使用した外部キーなので本来は命名法則に従ったカラム名にする必要があります。
usersテーブルに紐づくので、命名法則(テーブル名_id)に従えば"user_id"とすべきです。
しかし、今回フォロー(following)とフォロワー(followed)はどちらもuserテーブルの情報が
紐付きます。となると、命名法則に従えばフォロー&フォロワーのカラム名がどちらも"user_id"となり
重複することになります。
そこで、今回はfollowingとfollowedという命名法則を無視したユニークな名前にしています。
ここで、ユニークな名前のカラム名でリレーションをするためにconstrained()が活躍します。
大抵は引数は何も書かないことが多いですが今回は引数に"users"を指定しています。
constrained()の引数にリレーションしたいテーブル名を指定することで指定したテーブルと
リレーションが可能になります。
このうよにして今回はユニークな名前のカラム名でリレーションを実現しています。
マイグレーションファイルの編集が済んだらマイグレートしましょう。
php artisan migrate
モデルの設定
まずはモデルを作成します。
php artisan make:model Follow
モデルファイルをを作成できたら、以下のように編集する。
Followモデル特に編集はしませんが、後にコントローラーで使用しますので準備しておきます。
続いて、Userモデルを編集します。
ユーザー情報を返す際に、フォロー・フォロワーの情報をリレーションするようにします。
class User extends Authenticatable
//省略
//フォローしているユーザー
public function following()
{
return $this->belongsToMany(User::class, 'follows','following', 'followed');
}
//フォローされているユーザー
public function followed()
{
return $this->belongsToMany(User::class, 'follows','followed','following');
}
}
コントローラー編集
プロフィール画面でユーザーの情報を返すコントローラーと
フォローの追加・解除のDB操作を行うコントローラーを別々に作成します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;//登録ユーザーのDBを使用
class ProfileController extends Controller
{
//
public function get_user($user_id){
$user = User::with('following')->with('followed')->findOrFail($user_id);
return response()->json($user);
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Follow;//Followモデルをインポート
use Illuminate\Support\Facades\Auth; // Authファサードを読み込む
class FollowController extends Controller
{
//フォローしているかどうかの状態確認
public function check_following($id){
//自分がフォローしているかどうか検索
$check = Follow::where('following', Auth::id() )->where('followed', $id);
if($check->count() == 0):
//フォローしている場合
return response()->json(['status' => false]);
elseif($check->count() != 0):
//まだフォローしていない場合
return response()->json(['status' => true]);
endif;
}
//フォローする(中間テーブルをインサート)
public function following(Request $request){
//自分がフォローしているかどうか検索
$check = Follow::where('following', Auth::id())->where('followed', $request->user_id);
//検索結果が0(まだフォローしていない)場合のみフォローする
if($check->count() == 0):
$follow = new Follow;
$follow->following = Auth::id();
$follow->followed = $request->user_id;
$follow->save();
endif;
}
//フォローを外す
public function unfollowing(Request $request){
//削除対象のレコードを検索して削除
$unfollowing = Follow::where('following', Auth::id())->where('followed', $request->user_id)->delete();
}
}
routeの定義
routeを定義します。下記のように追記して下さい。
また、今回はAPIでの利用という前提ですのでapi.phpに書いてますが
フロントエンドはbladeテンプレート実装したい場合は同じ内容をweb.phpに追記すればOKです。
//省略
use App\Http\Controllers\ProfileController;//追加
use App\Http\Controllers\FollowController;//追加
//省略
//プロフィール閲覧で使用するユーザー情報の取得
Route::get('/profile/{id}',[ProfileController::class,'get_user']);
//フォロー状態の確認
Route::get('/follow/status/{id}',[FollowController::class,'check_following']);
//フォロー付与
Route::post('/follow/add',[FollowController::class,'following']);
//フォロー解除
Route::post('/follow/remove',[FollowController::class,'unfollowing']);
//省略
参考:フロントエンド
最後におまけでフロントエンドの実装例です。
自分の場合は、React(TypeScript)で実装しています。
パラメータ付きURlでユーザーのidを送り、それを受け取りプロフィールを表示します。
ユーザー情報にlaravel側でリレーションしているため、フォロー数とフォロワー数を取得できます。
フォロー/フォロー解除のボタンは別ファイルでコンポーネントを作り、Profile.tsxで使用しています。
import { FC } from "react";
import axios,{AxiosRequestConfig, AxiosResponse, AxiosError} from 'axios';
import { useParams } from 'react-router-dom';
import { useState, useEffect, useContext } from "react";
import { Following } from "./Following";
export const Profile:FC =()=>{
const { user_id } = useParams();
const [user,setUser] = useState<any>();
const [update,setUpdate] = useState<boolean>(false);
useEffect(()=>{
// パラメータ(暗号化されたid)付きでアクセスし、該当データをDBより取得
axios.get('/api/profile/' + user_id).then((response:any) => {
setUser(response.data);
console.log(response.data);
}).catch((error) => {
console.log('通信エラーが発生しました');
});
},[]);
return(
<>
<div className="w-full mt-1 mb-10 p-5 rounded-3xl bg-white text-slate-600">
{user && (
<div className="w-full rounded-3xl bg-white text-slate-600">
<div className="text-2xl">{user.name}</div>
<Following id={user.id} />
<div className="flex">
<div className="p-1">
<b>{user.following.length}</b> フォロー
</div>
<div className="p-1">
<b>{user.followed.length}</b> フォロワー
</div>
</div>
</div>
)}
</div>
</>
);
}
import { FC } from "react";
import axios,{AxiosRequestConfig, AxiosResponse, AxiosError} from 'axios';
import { useParams } from 'react-router-dom';
import { useState, useEffect, useContext } from "react";
export const Following:FC<{id:any}> =({id})=>{
const [status,setStatus] = useState<boolean>();
const [toggle,setToggle] = useState<boolean>(false);
//フォロー状態確認
useEffect(()=>{
// パラメータidでアクセスし、該当データをDBより取得
axios.get('/api/follow/status/'+ id).then((response) => {
console.log(response.data.status);
setStatus(response.data.status);
}).catch((error) => {
});
},[toggle]);
//フォロー付与
const Add = () =>{
// パラメータ(暗号化されたid)付きでアクセスし、該当データをDBより取得
axios.post('/api/follow/add',{user_id:id}).then((response) => {
alert('フォローしました');
setToggle(true);
}).catch((error) => {
alert('エラー');
});
}
//フォロー付与
const Remove = () =>{
// パラメータ(暗号化されたid)付きでアクセスし、該当データをDBより取得
axios.post('/api/follow/remove',{user_id:id}).then((response) => {
alert('フォロー解除');
setToggle(false);
}).catch((error) => {
alert('エラー');
});
}
return(
<>
{status ?
<button
className="bg-gray-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={Remove}>
フォロー解除
</button>
:
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={Add}>
フォロー
</button>
}
</>
);
}