🌟

Laravelで「お気に入り機能(いいね機能)」を実装する方法

2024/03/03に公開

はじめに

RealWorldというOSSの中で「Conduit」というMedium.comのクローンサイトを作るプロジェクトがあります。
Medium.comはNoteのように記事を投稿することができるSNSのことです。
そのRealWorldのバックエンドのAPIの仕様を満たすLaravel APIを作成していて、投稿記事のお気に入り機能(いいね機能)を作ったので備忘録としてまとめようと思います。
RealWorldドキュメント↓
https://realworld-docs.netlify.app/docs/intro

開発環境

  • PHP 8.1
  • Laravel9系

マイグレーション

usersテーブル(ユーザー管理)とarticles(記事管理)のテーブルができている前提で進めます。
1人のユーザーは複数の記事を「お気に入り」します。一方、一つの記事は複数のユーザーから「お気に入り」されます。「お気に入り」というリソースを通じて、ユーザーと記事は多対多の関係であると言えます。なので、usersとarticlesの中間テーブルを作成する必要があります。
下記のコマンドでusersとarticlesの中間テーブルを作成。

$ php artisan make:migration create_favorites_table

生成されたマイグレーションファイルを下記のように編集します。
usersテーブルのid、articlesテーブルのidを紐づけます。

2024_03_03_040212_create_favorites_table.php
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('favorites', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            $table->unsignedBigInteger('article_id');
            $table->timestamps();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('favorites');
    }

そしてデータベースに反映。

$ php artisan migrate

モデル

次に、下記コマンドでモデルを作成します。

$ php artisan make:model Favorite

Favoriteモデルファイル

「お気に入り機能」に関わるそれぞれのモデルを見ていきます。

Favoriteクラスから見たUserとArticleの関係をFavorite.phpに定義します。
各Favoriteは1人のUserや1つのArticleに「属している」のでbelongsToメソッドを使います。

Favorite.php
 public function user() {
    return $this->belongsTo(User::class);
}
public function articles() {
    return $this->belongsTo(Article::class);
}

Userモデルファイル

Userクラスから見たFavoriteとArticleの関係をUser.phpに定義します。
Userから見ると複数のFavoriteやArticleを「持っている」ことになるのでhasManyメソッドを使います。

User.php
public function articles() {
    return $this->hasMany(Article::class);
}
public function favorites() {
    return $this->hasMany(Favorite::class);
}

Articleモデルファイル

Articleクラスから見たFavoriteとUserの関係をArticle.phpに定義します。
各Articleは1人のUserに「属している」ことになるので、belongsToメソッドを使い、
また各Articleは複数のFavoriteを「持っている」ことになるのでhasManyメソッドを使います。

Article.php
public function user() {
    return $this->belongsTo(User::class);
}

public function favorites() {
    return $this->hasMany(Favorite::class);
}

ルーティング

下記の2行を追加。
今回は、ログイン認証ができている場合にのみメソッドを実行できるようにします。

api.php
Route::group(['middleware' => ['auth:sanctum']], function () {
    Route::post('/articles/{article:slug}/favorite', [ArticleController::class, 'favorite'])->name('favorite');
    Route::delete('/articles/{article:slug}/favorite', [ArticleController::class, 'unfavorite'])->name('unfavorite');
});

コントローラ

コントローラに「お気に入り」の追加・削除する処理を記述していきます。
削除についてはdelete()でfavoritesテーブルから該当のものを削除するだけなので省略します。

ArticleController.php
    public function favorite(Request $request, Article $article){
        // 認証済みユーザーを取得
        $user = Auth::user();

        if ($user) {
            // Userのid取得
            $user_id = Auth::id();

            // 既にいいねしているかチェック
            $existingFavorite = Favorite::where('article_id', $article->id)
                ->where('user_id', $user_id)
                ->first();

            // 既にいいねしている場合は何もせず、そうでない場合は新しいいいねを作成する
            if (!$existingFavorite) {
                $favorite = new Favorite();
                $favorite->article_id = $article->id;
                $favorite->user_id = $user_id;
                $favorite->save();
            }

            // 記事の状態を返す
            return response()->json([
                'article' => [
                    'slug' => $article->slug,
                    'title' => $article->title,
                    'description' => $article->description,
                    'body' => $article->body,
                    'tagList' => $article->tags->pluck('name'),
                    'createdAt' => $article->created_at,
                    'updatedAt' => $article->updated_at,
                    'favorited' => true, // いいねされた状態を示す
                    'favoritesCount' => $article->favorites()->count(), // いいねの合計数を取得
                ]
            ]);
        } else {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

    }

さいごに

以上が「お気に入り機能」の実装内容でした。
まだRealworldのConduitを作成している途中ですが、思考の整理のため実装方法をまとめてみました。
コントローラーについてはかなり冗長になっているので、処理をもう少しまとめようと思っています。

Discussion