[Laravel] いいね機能実装してみた

自分への備考録です。
1からすればできるはずですが、利用する際は要動作確認お願いします。

1.準備: Laravel構築、認証機能構築
参考： [Laravel構築チートシート](https://qiita.com/ProgramingDai/items/6fe5f678bf5a05e27c05)

`php artisan make:auth`
`php artisan migrate`

timezoneを日本時間に

```php:app/config/app.php
// - 'timezone' => 'UTC',
'timezone' => 'Asia/Tokyo',
```

2.準備: CRUDとリレーション
[Laravelで投稿アプリの機能を作成 ~CRUD編~](https://qiita.com/ProgramingDai/items/cf6944f9cd0ac08f4e3e)
[Laravelで投稿アプリの機能を作成 ~リレーション編~](https://qiita.com/ProgramingDai/items/249acc8894079ee58268)


3.Likeモデルとlikesテーブル生成

`php artisan make:model Like -m`

```php
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も一気に削除される
        });
    }
```

`php artisan migrate`

4.postsテーブルにlikes_countカラムを追加

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

```php
public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->integer('likes_count')->default(0);
        });
    }
```

`php artisan migrate`

5.Counter Cache をインストール

```json:composer.json
"require": {
    // ...
    "kanazaca/counter-cache": "1.0.*"
}
```

```php:config/app.php
'providers' => array(
	// ...
	kanazaca\CounterCache\CounterCacheServiceProvider::class,
)
```
`composer install`

```
Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. Run update to update them.
```
上記Warningが出た場合は
`composer update --lock`
エラーがないか確認

6.Likeモデル編集

```php:app/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);
    }

}
```

7.Postモデル編集

```php:app/Post.php
    // 追加
    public function likes()
    {
      return $this->hasMany('App\Like');
    }

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

```

8.Userモデル編集

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

```

9.ルーティング

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

10.LikesControllerを生成
`php artisan make:controller LikesController`

```php:app/Http/Controllers/LikesController
<?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('PostController@show', $post->id);
    }

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

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

11.PostControllerを編集

```php:app/Http/Controllers/PostController
 public function show($id)
    {   
        $authUser = Auth::user(); // 認証ユーザー取得
        $item = Post::find($id);

        // 追加
        $like = $item->likes()->where('user_id', Auth::user()->id)->first();
        
$params = [
            'authUser' => $authUser,
            'item' => $item,

            // 追加
            'like' => $like,
        ];
        return view('post.show', $params);
    }
```

Formファサード追加
参考にした記事がFormファサードで書かれていたので
そのまま利用することにしました。
（通常のフォームに戻すのがめんどくさかっただけ）
`composer require "laravelcollective/html":"^5.7.0"`
5.7.0部分、各バージョンに合わす

```php:config/app.php
'providers' => [
    // ...
    Collective\Html\HtmlServiceProvider::class,
    // ...
  ],
'aliases' => [
    // ...
      'Form' => Collective\Html\FormFacade::class,
      'Html' => Collective\Html\HtmlFacade::class,
    // ...
  ],
```

12.viewを編集

```html:resources/views/post/show.blade.php
@extends('layouts.postapp')
@section('title', '詳細記事')

@section('content')
    @section('maincopy', '詳細記事')

    @if($item !== '')
        <div class="headcopy">Title</div><hr>
        <div class="text">{{ $item->title }}</div>
        <div class="headcopy">Message</div><hr>
        <div class="text">{{ $item->message }}</div>
        @if($authUser->id === $item->user_id)
        <form action="/post/{{$item->id}}" method="POST">
            {{ csrf_field() }}           
            <input type="hidden" name="user_id" value="{{ $authUser->id }}">
            <input type="text" class="form" name="title" placeholder="タイトル" value="{{ $item->title }}">
            <div>
                <textarea class="form" name="message" placeholder="メッセージ">{{ $item->message }}</textarea>
            </div>
            <input type="hidden" name="_method" value="PUT">
            <input type="submit" class="create" value="変　更">
        </form>
        @endif
    @endif

@if (Auth::check())
    @if ($like)
      <!-- いいね取り消しフォーム -->
      {{ Form::model($item, array('action' => array('LikesController@destroy', $item->id, $like->id))) }}
        <button type="submit">
          ♡ いいね {{ $item->likes_count }}
        </button>
      {!! Form::close() !!}
    @else
      <!-- いいねフォーム -->
      {{ Form::model($item, array('action' => array('LikesController@store', $item->id))) }}
        <button type="submit">
          + いいね {{ $item->likes_count }}
        </button>
      {!! Form::close() !!}
    @endif
@endif

    <a href="/post"><img src="{{ asset('img/edit.svg') }}" class="add" alt="topへ"></a>
@endsection
```
index.blade.phpに直接影響はないが
ログインユーザー以外でもshow.blade.phpに遷移させたいので
修正

```html:resources/views/post/index.blade.php
@extends('layouts.postapp')
@section('title', '投稿アプリ')

@section('content')
    @section('maincopy', '投稿してください')

    <!-- 投稿フォーム -->
    <form action="/post" method="post">
        {{ csrf_field() }}

        <input type="hidden" name="user_id" value="{{ $authUser->id }}">
        @if($errors->has('title'))
            <div class="error_msg">{{ $errors->first('title') }}</div>
        @endif
        <input type="text" class="form" name="title" placeholder="タイトル" value="{{ old('title') }}">

        @if($errors->has('message'))
            <div class="error_msg">{{ $errors->first('message') }}</div>
        @endif
        <div>
            <textarea class="form" name="message" placeholder="メッセージ">{{ old('message') }}</textarea>
        </div>
        <input type="submit" class="create" value="投  稿">
    </form>

    <!-- 記事描画部分 -->
    @if(count($items) > 0)
        @foreach($items as $item)
            <div class="alert alert-primary" role="alert">
                @if($authUser->id === $item->user_id)
                <!-- 主キーと外部キーが同じ場合 -> リンク付きテキストと削除ボタン表示 -->
                <a href="/post/{{ $item->id }}" class="alert-link">{{ $item->title }}</a>
                <div>{{ $item->message }}</div>
                <form action="/post/{{ $item->id }}" method="POST">
                {{ csrf_field() }}
                <input type="hidden" name="_method" value="DELETE">
                <input type="submit" class="delete" value="削除">
                </form>
                @else
                <a href="/post/{{ $item->id }}" class="alert-link">{{ $item->title }}</a>
                <div>{{ $item->message }}</div>
                @endif
                <!-- この部分はいろいろ出し方を変えられますのでおのおのでカスタマイズしてみてください -->
            </div>
        @endforeach
    @else
        <div>投稿記事がありません</div>
    @endif
@endsection
```

この記事ではshow.blade.php上でいいねをつけられる機能です。
今後、index.blade.phpでもいいねがつけられるようにするのが課題です。