【PHP / Laravel】最も簡単ないいね機能を実装する

## 🎒 はじめに
超シンプルないいね機能をLaravelで実装してみました．

## 🎒 自己紹介
初めまして．趣味でweb開発を勉強している273*(ﾂﾅｻﾝﾄﾞ) / Kei.と申します．フルスタック開発やツール制作を行なっています．まだまだ初心者です．

### テーブル作成
- 中間テーブルを作成します．users，postテーブルは事前に用意しておきましょう．
- php artisan make:migration create_post_likes_table
- yyyy_mm_dd_hhmmss_create_likes_table.php
```php
public function up(): void
{
    Schema::create('likes', function (Blueprint $table) {
        // constrainedの第一引数は参照するテーブル名．
        // 何も指定しない場合は外部キー(foreignId)から自動で推測される．
        $table->foreignId('user_id')->constrained("users")->onDelete('cascade');
        $table->foreignId('post_id')->constrained("posts")->onDelete('cascade');
        // 主キーをuser_idとpost_idの組み合わせにする
        $table->primary(['user_id', 'post_id']);
        $table->timestamps();
    });
}
```

### モデルの作成，追加
- php artisan make:model Like
- Like.php
```php
// リレーション (Like belongs to a Post)
public function post()
{
	return $this->belongsTo(Post::class);
}

// リレーション (Like belongs to a User)
public function user()
{
	return $this->belongsTo(User::class);
}
```

- User.php / Post.php(追加)
```php
public function likes()
{
	// User / Post has many Likes
	return $this->hasMany(Like::class);
}
```

### ルーティング
- web.php
```php
Route::post('/posts/{postId}/like', [LikeController::class, 'likePost'])->middleware("auth");
```

### いいね登録・削除処理
- LikeController.php
```php
 public function likePost(Post $post)
{

    // ログインユーザーのidを取得
    $user_id = Auth::id();

    // ログインユーザーがその投稿をいいねしているレコードを取得
    $liked_post = $post->likes()->where('user_id', $user_id);

    // 既に「いいね」しているか確認
    if (!$liked_post->exists()) {

        //「いいね」していない場合は，likesテーブルにレコードを追加
        $like = new Like();
        $like->user_id = $user_id;
        $like->post_id = $post->id;
        $like->save();
    } else {
        // 既に「いいね」をしていた場合は，likesテーブルからレコードを削除     
        $liked_post->delete();
    }

    // いいねの数を取得
    $likes_count = $post->likes->count();

    
    $param = [
        'likes_count' => $likes_count, // いいねの数
    ];

    // フロントにいいねの数を返す
    return response()->json($param);
}
```


### いいねボタンの挙動を実装
resourcesディレクトリにlike.jsを作成
流れ：全てのいいねボタンを取得，いいねボタンを押されたら，バックエンドにいいね処理を行うように送信．処理が成功したら，非同期でいいね数とアイコンを変更．
- resources/like.js
```js
// 非同期でいいね機能を実装

// 全てのいいねボタン(ハートアイコン)の要素を取得
const likeBtns = document.querySelectorAll(".like-btn");

likeBtns.forEach((likeBtn) => {
    // どれかのいいねボタンがクリックされた時の処理
    likeBtn.addEventListener("click", async (e) => {
        // クリックされたいいねボタンのid(post id)を取得
        const postId = e.target.id; 

        // /posts/{postId}/likeにPOSTリクエストを送信
        await fetch(`/posts/${postId}/like`, {
            method: "POST", // リクエストメソッドはPOST
            headers: {
                //Content-Typeでサーバーに送るデータの種類を伝える。今回はapplication/json
                "Content-Type": "application/json",
                //csrfトークンを付与
                "X-CSRF-TOKEN": document
                    .querySelector('meta[name="csrf-token"]')
                    .getAttribute("content"),
            },
        })
            .then((res) => res.json()) // レスポンスをJSON形式に変換;
            .then((data) => {
                // いいねの数を更新
                e.target.nextElementSibling.innerHTML = data.likesCount;
                
                // いいねの状態によってハートアイコンの色を変更
                if (e.target.classList.contains("text-pink-500")) {
                    e.target.classList.remove("text-pink-500");
                    e.target.setAttribute("name", "heart-outline");
                } else {
                    e.target.classList.add("text-pink-500");
                    e.target.setAttribute("name", "heart");
                }
            })
             // 失敗した場合はアラートを表示
            .catch(() => alert("いいね処理が失敗しました"));
    });
});

```

- app.js
```js
import "./like"; // 追加
```

### いいねボタンの作成
- アイコン：[Ionicons: Premium Open Source Icon Pack for Ionic Framework](https://ionic.io/ionicons)
- 登録不要でscriptタグで読み込むだけで使用可能．
- index.blade.php / show.blade.php
```html
<!-- bladeファイルのheadタグ -->
<head>
    <meta charset="utf-8">
    <title>Blog</title>
    <!-- Fonts -->
    <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet">

	<!-- アイコンの読み込み -->
	<meta name="csrf-token" content="{{ csrf_token() }}">
    <script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
    <script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
</head>

<!--いいねボタンの作成 -->

<div class="flex items-center gap-3">
    <h2 class='title text-xl text-blue-600 font-bold'>
	    <a href="/posts/{{ $post->id }}">{{ $post->title }}</a>
    </h2>
    @auth
    <!--その投稿がいいねしているか判定 -->
    @if (Auth::user()->likes()->where('post_id', $post->id)->exists())
    <ion-icon name="heart" class="like-btn cursor-pointer text-pink-500" id={{$post->id}}></ion-icon>
    @else
    <ion-icon name="heart-outline" class="like-btn cursor-pointer" id={{$post->id}}></ion-icon>
    @endif
    <p class="count-num">{{$post->likes->count()}}</p>
    @endauth
</div>
```

# 🎒 最後に
Laravelは非常に扱いやすいフレームワークですね．今後も触っていきたいと思います．最後まで読んでいただきありがとうございました！