Laravelでいいね機能を実装しよう

##1. はじめに
いいね機能とは、最近のSNSにはほとんどと言ってもいいほど実装されている機能で、「好き」,「わかる」,「支持できる」などの意思表示を表すコミュニケーションツールの一種です。

いいね機能があることによって、

```
・（ユーザー視点）多くの投稿からどの投稿が人気もしくは信憑性があるのか。
・(投稿者視点)これまで投稿した記事でどの記事がユーザーの求めている記事なのか。
```
などを判断する1つの材料となります。

今回はPHPのフレームワークLaravelを使用して、`いいね機能`を実装していきます。
※今回はLaravelの勉強を兼ねているため、いいね機能をサーバーサイドで処理を実装しています。本来のプロダクトであれば処理を軽くするためにもフロント側で処理を実装するかと思いますので、その点についてはご了承ください。

#####1-1. 開発環境
PHPとLaravelのバージョンは下記のグラフの通りとします。

| 言語・FW     　　　 |   バージョン      |    
|:-----------------|---------------:|
| PHP              |        7.2.26  |
| Laravel          |        6.17.1  |

##2. どのような仕様にするか
仕様としては1つの投稿(discussion)に複数のリプライ(Reply)が紐づくようになっていて、リプライに対していいね(Like)をつけることができます。
細かい仕様は以下のように定義します。

```
・初期値はいいねボタンがグレーで、いいねボタンを押すとボタンが緑色になる
・いいね数をいいねボタンの右横に表示する
・いいねボタンを2度押すといいねを取り消すことができる
・自分のリプライに対してもいいねをすることができる
・ログイン中のみに限り、リプライにいいねすることができる
```
<img width="1169" alt="スクリーンショット 2020-03-19 20.12.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283595/d9f47e66-4266-5d45-81d9-c0f3a994f73f.png">

##3. いいね機能の実装
それでは実際にいいね機能の実装に入っていきます。

#####3-1. ルーティング
まずはじめにいいね機能を実装する上でのルーティングを定義します。
リプライにいいねをする処理を`RepliesControllerのlike()`に、
リプライに付けたいいねを解除する処理を`RepliesControllerのunlike()`に記述するという定義を行います。

```web.php
Route::get('/reply/like/{id}', 'RepliesController@like')->name('reply.like');
Route::get('/reply/unlike/{id}', 'RepliesController@unlike')->name('reply.unlike');
```

#####3-2. 情報の関係性について考える
リプライ(Reply)といいね(Like)の関係性は以下のようになります。

```
・1つのリプライに対して複数のいいねが紐づく
・1つのいいねに対して紐づくリプライは1つのみ
```
RepliesテーブルとLikesテーブルのE-R図
<img width="412" alt="スクリーンショット 2020-03-19 21.03.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283595/b6db3819-bb0b-cb52-188d-b9476ca7cad0.png">

ユーザー(User)といいね(Like)の関係性は以下のようになります。

```
・1人のユーザーに対して複数のいいねが紐づく
・1つのいいねに対して紐づくユーザーは1人のみ
```
UsersテーブルとLikesテーブルのE-R図
<img width="382" alt="スクリーンショット 2020-03-19 21.09.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283595/b55d14dd-2e23-47e1-13a2-f71f265de411.png">

#####3-3. モデルの作成と修正
`artisan`コマンドでLikeモデルとマイグレーションファイルを生成します。

```
$ php artisan make:model Like -m
```

`3-2の情報の関係性`を基にしてマイグレーションファイルとモデルを修正します。

```create_likes_teble.php
class CreateLikesTable extends Migration
{
 /**
  * Run the migrations.
  *
  * @return void
  */
  public function up()
  {
    Schema::create('likes', function (Blueprint $table) {
      $table->bigIncrements('id');
      $table->integer('reply_id')->unsigned();
      $table->integer('user_id')->unsigned();
      $table->timestamps();
   });
  ...
 }
}
```

```Like.php
class Like extends Model
{
  // 配列内の要素を書き込み可能にする
  protected $fillable = ['reply_id','user_id'];

  public function reply()
  {
    return $this->belongsTo(Reply::class);
  }

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

```Reply.php
class Reply extends Model
{
  ...
  public function likes()
  {
    return $this->hasMany(Like::class, 'reply_id');
  }
}
```

#####3-4. いいね機能のビジネスロジックの実装
いいね機能のロジックを実装する上で以下の2点がポイントとなります。

```
・いいねボタンを押下したら、Likeテーブルに新たにレコードが生成されて、ユーザーIDとリプライIDが保存される。
・いいねしたボタンに対して、再度いいねボタンを押すと上記で生成したレコードが削除される。
```

上記のポイントを基に実装すると下記のようになります。

```RepliesController.php
class RepliesController extends Controller
{
  // only()の引数内のメソッドはログイン時のみ有効
  public function __construct()
  {
    $this->middleware(['auth', 'verified'])->only(['like', 'unlike']);
  }

  ...

 /**
  * 引数のIDに紐づくリプライにLIKEする
  *
  * @param $id リプライID
  * @return \Illuminate\Http\RedirectResponse
  */
  public function like($id)
  {
    Like::create([
      'reply_id' => $id,
      'user_id' => Auth::id(),
    ]);

    session()->flash('success', 'You Liked the Reply.');

    return redirect()->back();
  }

  /**
   * 引数のIDに紐づくリプライにUNLIKEする
   *
   * @param $id リプライID
   * @return \Illuminate\Http\RedirectResponse
   */
  public function unlike($id)
  {
    $like = Like::where('reply_id', $id)->where('user_id', Auth::id())->first();
    $like->delete();

    session()->flash('success', 'You Unliked the Reply.');

    return redirect()->back();
  }

...

}
```

##4. いいねボタンの実装
#####4-1. `いいね状態` と `非いいね状態` の表示制御
初期表示ではリプライにいいねがついておらず、いいねボタンはグレーでカウントは0、いいねをいている状態だと、いいねボタンが緑色になりカウントが1加算されます。

いいねがついているかどうかの判定を、 `is_liked_by_auth_user()` 内で判定して判定値をbooleanで返してあげます。

```show.blade.php
<div>
  @if($reply->is_liked_by_auth_user())
    <a href="{{ route('reply.unlike', ['id' => $reply->id]) }}" class="btn btn-success btn-sm">いいね<span class="badge">{{ $reply->likes->count() }}</span></a>
  @else
    <a href="{{ route('reply.like', ['id' => $reply->id]) }}" class="btn btn-secondary btn-sm">いいね<span class="badge">{{ $reply->likes->count() }}</span></a>
  @endif
</div>
```

```Replay.php
 /**
  * リプライにLIKEを付いているかの判定
  *
  * @return bool true:Likeがついてる false:Likeがついてない
  */
  public function is_liked_by_auth_user()
  {
    $id = Auth::id();

    $likers = array();
    foreach($this->likes as $like) {
      array_push($likers, $like->user_id);
    }

    if (in_array($id, $likers)) {
      return true;
    } else {
      return false;
    }
  }
```

#####4-2. いいね数の表示
いいね数の表示は、`Reply`と `Like`は既にアソシエーションされているので、`count()`でいいね数を取得することができます。

```shwo.blade.php
{{ $reply->likes->count() }}
```

##最後に
以上でLaravelでの`いいね機能`の実装のご紹介でした！！
誤字脱字や、もっとこうした方がいいという箇所がございましたらコメント欄から教えてください！！