Laravelでいいね機能を作ってみた

## はじめに

前提として、投稿機能とユーザ機能が完成したアプリケーションがあるとしてそこに「いいね機能」を追加するという流れで進めていきます。

また、今回作るいいね機能は、一つの投稿に対して一人のユーザーがいいねできるのは一回までで、既にいいねされている状態でいいねを押すといいねを取り消すことができるように実装しました。

## Likeモデルとlikesテーブルを作成しよう

ターミナルでLikeモデルを作成します。

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

最後に```-m```をつけることでマイグレーションファイルを同時に作成することができます。

マイグレーションファイルは以下のように記述しました。

```〇〇.php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

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

            $table->foreign('user_id')
                  ->references('id')
                  ->on('users')
                  ->onDelete('cascade'); // userが削除されたとき、それに関連するlikeも一気に削除される

            $table->foreign('post_id')
                  ->references('id')
                  ->on('posts')
                  ->onDelete('cascade'); // postが削除されたとき、それに関連するlikeも一気に削除される
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('likes');
    }
}

```

ポイントとしては、投稿が削除されたとき、それに関連するlikeも一気に削除される実装を書いてあげます。

そして、マイグレーションファイルを実行するといいですね。

```
$ php artisan migrate
```

## postsテーブルにlikes_countカラムを追加しよう

まず、カラムを追加するマイグレーションファイルを作成します。

```
php artisan make:migration add_columns_to_posts_table --table=posts
```

作成したマイグレーションファイルは以下のように記述しました。

```◯◯.php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddColumnsToPostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->integer('likes_count')->default(0);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('posts', function (Blueprint $table) {
            //
        });
    }
}

```

最後に、このマイグレーションファイルを実行するといいですね。

```
$ php artisan migrate
```

## Counter Cache をインストールしよう

[kanazaca/counter-cache](https://github.com/kanazaca/counter-cache)

上記のサイトを参考にCounter Cache をインストールします。
Counter Cache を使うことで、いいねボタンを押すことでいいねの数をカウントする実装ができます。

インストールの仕方は上記のサイトを参考にしてください。

## Likeモデルを編集しよう

インストールが終わって```config/app.php```の編集も終えたら、[kanazaca/counter-cache](https://github.com/kanazaca/counter-cache)を参考にLikeモデルを編集します。

```Like.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use kanazaca\CounterCache\CounterCache;

class Like extends Model
{
    use CounterCache;

    public $counterCacheOptions = [
        'Post' => [
            'field' => 'likes_count',
            'foreignKey' => 'post_id'
        ]
    ];

    protected $fillable = ['user_id', 'post_id'];

    public function Post()
    {
      return $this->belongsTo('App\Post');
    }

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

}

```

## Postモデルを編集しよう

Likeモデルとのアソシエーションの記述を書いてあげましょう。

```Post.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Auth;
use App\Like;

class Post extends Model
{
    protected $fillable = ['title', 'body', 'summary', 'user_id'];

    public function comments() {
      return $this->hasMany('App\Comment');
    }

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

    public function likes()
    {
      return $this->hasMany('App\Like');
    }

    public function like_by()
    {
      return Like::where('user_id', Auth::user()->id)->first();
    }
}

```

ここで、like_byメソッドを定義していますが、ここはコントローラーの記述の際に詳しく説明したいと思います。

## Userモデルを編集しよう

Postモデルと同じようにLikeモデルとのアソシエーションの記述を追加してあげるといいですね。

```User.php
    // 以下の記述を追加
    public function likes()
    {
      return $this->hasMany(Like::class);
    }
```

## ルーティングを編集しよう

```routes.php
  Route::post('/posts/{post}/likes', 'LikesController@store');
  Route::post('/posts/{post}/likes/{like}', 'LikesController@destroy');
```

## LikesControllerを作成しよう

まず、以下のコマンドでLikesコントローラーを作成します。

```
php artisan make:controller LikesController
```

次にその作成したファイルの中身を編集しよう。

```LikesController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Like;
use Auth;
use App\Post;

class LikesController extends Controller
{
    public function store(Request $request, $postId)
    {
        Like::create(
          array(
            'user_id' => Auth::user()->id,
            'post_id' => $postId
          )
        );

        $post = Post::findOrFail($postId);

        return redirect()
             ->action('PostsController@show', $post->id);
    }

    public function destroy($postId, $likeId) {
      $post = Post::findOrFail($postId);
      $post->like_by()->findOrFail($likeId)->delete();

      return redirect()
             ->action('PostsController@show', $post->id);
    }
}

```

ここで、like_byメソッドがでてきたました。
ここでのlike_byメソッドは、likesテーブルのなかのuser_idが現在ログインしているidのものを取得しています。

## PostsControllerを編集しよう

変数$likeはその投稿のlikeテーブルにある現在ログインしているuser_idを取得してます。
少し複雑でここは難しいかもしれません。
書き方はこちらの[Laravel 5.1 Eloquent：リレーション](https://readouble.com/laravel/5.1/ja/eloquent-relationships.html#one-to-many)がわかりやすかったです。

```PostsController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Post;
use Auth;

class PostsController extends Controller
{
    public function __construct()
    {
      $this->middleware('auth', array('except' => 'index'));
    }

    public function show($id) {
      $post = Post::findOrFail($id); // findOrFail 見つからなかった時の例外処理

      $like = $post->likes()->where('user_id', Auth::user()->id)->first();

      return view('posts.show')->with(array('post' => $post, 'like' => $like));
    }
}
```

## viewを編集しよう

投稿に現在ログインしているユーザーがいいねが押している時はdestroyアクションを、そうでないときはcreateアクションを実行するように条件分岐する必要があります。
ここで、先ほどコントローラーで記述した変数$likeを利用します。

```show.blade.php
  @if (Auth::check())
    @if ($like)
      {{ Form::model($post, array('action' => array('LikesController@destroy', $post->id, $like->id))) }}
        <button type="submit">
          <img src="/images/icon_heart_red.svg">
          Like {{ $post->likes_count }}
        </button>
      {!! Form::close() !!}
    @else
      {{ Form::model($post, array('action' => array('LikesController@store', $post->id))) }}
        <button type="submit">
          <img src="/images/icon_heart.svg">
          Like {{ $post->likes_count }}
        </button>
      {!! Form::close() !!}
    @endif
  @endif
```

またここではFORMファサードを利用してます。
FORMファサードを使うためにはlaravelcollective/htmlライブラリを読みこむ必要があります。

自分はlaravelのバージョンは5.2なので、
下記のコマンドでインストールをしました。

```
$ composer require laravelcollective/html 5.2
```

詳しいインストール方法はこちらの[Laravel Collective](https://laravelcollective.com/docs/5.2/html)を参考にしてください。

## 最後に

これで、いいね機能の実装のおおまかな説明は終わりです。
今回はいいねボタンを押したとき ```redirect()``` を使って画面遷移させてましたが、
次回、ajaxを使ったページ遷移をせずにいいねを増やしたり消したりする方法を追加したいと思います！