よくわかるPHPの教科書/Twitter風ひとこと掲示板 に「いいね」機能を追加してみた

初めてのQiitaへの投稿です。
現在、プログラミングスクールQUELCODEでプログラミングを学んでいます。
※QUELCODEについて同期が書いたブログ
 <span style="font-size: 8px"> https://ktg6.com/quelcode-experience-information</span>
 <span style="font-size: 50%"> https://toshi65811.hatenablog.jp/entry/2019/01/11/132810</span>
 <span style="font-size: 50%"> https://yukimasablog.com/school-quelcode</span>



カリキュラムの中で、PHPについては「[よくわかるPHPの教科書【PHP7対応版】](https://book.mynavi.jp/ec/products/detail/id=89743)」を使いました。
そのテキストのChapter.6で「Twitter風ひとこと掲示板」を作る章があります。
テキストを3周し、3周目のときにはテキストの内容を作るだけでなく`いいね`機能を作ってみたので、まとめてみます。

同じテキストを使ってPHPを学んでいる初心者の方の参考になればと思います。(私はこのテキストで初めてPHPに触れました)
基本的には私が手を加えた部分だけ記載するため(テキストの内容はあまり書き写さない方がいいと思うので)、このテキストを使い、かつ、Chapter.6までやった方で無いと前提がよく分からないと思いますのでご了承下さい。

# 目的
テキストChapter.6の「Twitter風ひとこと掲示板」に本家Twitterを参考に`いいね`機能を追加し、PHP学習の理解を深める

# 追加する機能概要
①`いいね`を記録するテーブルを作る
②メッセージ一覧ページに、`いいね`のハートマーク / `いいね`されている件数 を表示する欄を作る
③メッセージ一覧ページでハートマークの横に`いいね`の件数を表示
④メッセージ一覧ページで`いいね`する機能を作る(色無しのハートマークを押すと、ハートマークが赤くなり`いいね`の件数が1件増える)※1メッセージに対して1ユーザーが`いいね`出来るのは1回だけ)
⑤メッセージ一覧ページで`いいね`を取り消す機能を作る(ハートマークが赤い状態で押すと`いいね`が取り消されてハートマークが色無しになり`いいね`の件数が1件減る)

本家Twitterを参考に、このように追加してみます。
※本家のように`いいね`したユーザー一覧のページも作ろうかと思ったのですが、スクールのカリキュラムを先に進めたくなったのでそこまではやりませんでした。

# 機能詳細
※丸数字は機能概要と連動してます。

###1. (①)テーブル「likes」を作り、`いいね`を記録出来るようにする</u>
<img width="582" alt="capture3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367907/afacf42b-9515-aa97-69ba-f0e4136cba01.png">
※プログラムの中で使用する項目は`liked_post_id`と`pressed_member_id`のみ

###2. (②)index.phpにて、メッセージ一覧の投稿日時と削除リンクの間に、ハートマークと`いいね`の件数表示出来るように③④⑤を作って行く
表示イメージ：　<img width="246" alt="capture2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367907/dd0543b2-55e7-a522-6715-c57d25d49f67.png">

###3. (③)index.phpにて、`いいね`された件数を判別するために必要な情報を作り出す
テキストでは元々以下のような情報を作り出しているので、これに必要な情報を加えていく

| メンバーのニックネーム | メンバーの写真のパス | postsテーブルの全項目 |
|:-:|:-:|:-:|
| members.name | members.picture | posts.* |
<details>
<summary>テキストのSQL</summary><div>

```SQL:テキストのSQL
SELECT m.name, m.picture, p.* FROM members m, posts p WHERE m.id=p.member_id ORDER BY p.created DESC LIMIT ?, 5
// ?には、何番目の投稿から取得するか(最大5件ずつなので)を意味する$startを入れる
```
</div>
</details>
######3-1. 「likes」テーブルからメッセージIDごとの`いいね`された件数を取得する<br>
|いいねされたメッセージID|いいねされた件数|
|:-:|:-:|
|liked_post_id|COUNT(liked_post_id) AS li_cnt|

<details>
<summary>SQL</summary><div>

```SQL:index.php
SELECT liked_post_id, COUNT(liked_post_id) AS li_cnt FROM likes GROUP BY liked_post_id) AS li
```
</div>
</details>
######3-2. テキストのSQLのFROM posts 部分の「posts」テーブルについて、メッセージIDごとに`いいね`された件数を付加した状態に置き換える
その情報を作成するために、「posts」テーブルと3-1の情報をposts.idとliked_post_idで結合する

|postsテーブルの全項目|いいねされた件数|
|:-:|:-:|
| posts.* | li_cnt |

<details>
<summary>postsを置き換える情報を作るためのSQL</summary><div>

```sql:index.php
SELECT posts.*, li_cnt FROM posts LEFT JOIN 
                            (SELECT liked_post_id, COUNT(liked_post_id) AS li_cnt FROM likes GROUP BY liked_post_id) AS li ON posts.id=li.liked_post_id)
```
</div>
</details>
テキストのSQLのFROMの`posts`部分に上のSQLをはめ込んで完成させる

<details>
<summary>SQL</summary><div>

```sql:index.php
SELECT m.name, m.picture, p.* FROM members m,
(SELECT posts.*, li_cnt FROM posts LEFT JOIN 
                                   (SELECT liked_post_id, COUNT(liked_post_id) AS li_cnt FROM likes GROUP BY liked_post_id) AS li ON posts.id=li.liked_post_id) p
WHERE m.id=p.member_id ORDER BY p.created DESC LIMIT ?, 5
```
</div>
</details>
SQLをprepareステートメントで実行する

<details>
<summary>index.phpに追加した処理</summary><div>

```PHP:index.php
$posts = $db->prepare('SELECT m.name, m.picture, p.* FROM members m,
                                                         (SELECT posts.*, li_cnt FROM posts LEFT JOIN 
                                                                 (SELECT liked_post_id, COUNT(liked_post_id) AS li_cnt FROM likes GROUP BY liked_post_id) AS li ON posts.id=li.liked_post_id) p
                       WHERE m.id=p.member_id ORDER BY p.created DESC LIMIT ?, 5;');
$posts->bindParam(1, $start, PDO::PARAM_INT);
$posts->execute();
```
</div>
</details>
###4. (④⑤) index.phpにて「likes」テーブルより自身が`いいね`したメッセージIDの一覧情報を作り出す

        いいねしたメッセージID|
        :-:|
        liked_post_id|
<details>
<summary>SQL</summary><div>

```php:index.php
SELECT liked_post_id FROM likes WHERE pressed_member_id=?
// ?には自身のIDとして$_SESSION['id']が入る
```
</div>
</details>
SQLをprepareステートメントで実行し、後で使用するため配列に入れておく

<details>
<summary>index.phpに追加した処理</summary><div>

```PHP:index.php
$pressedMessages = $db->prepare('SELECT liked_post_id FROM likes WHERE pressed_member_id=?');
$pressedMessages->bindParam(1, $_SESSION['id'], PDO::PARAM_INT);
$pressedMessages->execute();
$pressedMsg = array();
foreach ($pressedMessages as $pMsg) {
  $pressedMsg[] = $pMsg;
}
```
</div>
</details>

###5. (③④⑤) index.phpにて(最大で)5件出力するメッセージ一覧の繰り返し処理(foreach)の中に、取得した情報を組み込む
######5-1. 4で取得したメッセージIDの中に現在のメッセージID(最大で5件繰り返すforeachの中の`$post['id']`)が存在していれば目印をつける(`$likeAri`に値を入れる)
<details>
<summary>index.phpに追加した処理</summary><div>

```php:index.php
$likeAri = 0; // $likeAriは毎回初期化
for($i=0; $i<count($pressedMsg); $i++) {
    if ($pressedMsg[$i]['liked_post_id'] == $post['id']) {
        $likeAri = $post['id'];
        break;
    }
}
// 変数名のネーミングセンスが微妙過ぎる
```
</div>
</details>
######    5-2. メッセージ一覧ページに、`いいね`のハートマーク / `いいね`されている件数 を表示する
5-1で設定した`$likeAri`より、`いいね`していたら赤いハートを、`いいね`していなかったら色無しのハートを、ハートの隣には取得した件数を表示する

<details>
<summary>index.phpに追加した処理</summary><div>

```php:index.php
<?php if ($likeAri > 0) { ?>
    <a class="like" style="color:#F33;" href="like_delete.php?id=<?php echo h($post['id']); ?>">&#9829;</a><span class="likeCount"><?php echo h($post['li_cnt']); ?></span>
<?php } else { ?>
    <a class="like" href="like_insert.php?id=<?php echo h($post['id']); ?>">&#9825;</a><span class="likeCount"><?php echo h($post['li_cnt']); ?></span>
<?php } ?>
// ハートマークはただの記号のハートで、画像とかは使ってません
```
</div>
</details>

###6. (④⑤) ハートマークを押した場合の動きを作る
######    6-1. (④)色無しのハートマークを押したら新規作成したlike_insert.phpにて「likes」テーブルに`いいね`した記録をINSERTし、index.phpに戻る
<details>
<summary>like_insert.php</summary><div>

```php:like_insert.php
session_start();
require('dbconnect.php');

if (isset($_SESSION['id'])) {

  // いいねが登録済みかチェックするため情報を取得する
  $likes = $db->prepare('SELECT COUNT(liked_post_id) AS like_cnt FROM (SELECT liked_post_id FROM likes WHERE liked_post_id=? AND pressed_member_id=? GROUP BY liked_post_id) A');
  $likes->execute(array(
    $_REQUEST['id'], // liked_post_id いいねしようとしているメッセージのid
    $_SESSION['id']  // pressed_member_id いいねしようとしているメンバーのid
  ));
  $like = $likes->fetch();

  // いいねしようとしている投稿に対して、すでにいいねをしていないかチェックする
  if ($like['like_cnt'] == 0) {
    // いいねを登録する
    $like_ins = $db->prepare('INSERT INTO likes SET liked_post_id=?, pressed_member_id=?, created=NOW()');
    $like_ins->execute(array(
      $_REQUEST['id'], // liked_post_id いいねしようとしているメッセージのid
      $_SESSION['id']  // pressed_member_id いいねしようとしているメンバーのid
    ));
  }
}

header('Location: index.php');
exit();
```
</div>
</details>
######    6-2. (⑤) ハートマークが赤い状態で押したら新規作成したlike_delete.phpにて「likes」テーブルから`いいね`した記録をDELETEし、index.phpに戻る
<details>
<summary>like_delete.php</summary><div>

```php:like_delete.php
session_start();
require('dbconnect.php');

if (isset($_SESSION['id'])) {
  // いいねが登録済みかチェックするため情報を取得する
  $likes = $db->prepare('SELECT COUNT(liked_post_id) AS like_cnt FROM (SELECT liked_post_id FROM likes WHERE liked_post_id=? AND pressed_member_id=? GROUP BY liked_post_id) A');
  $likes->execute(array(
    $_REQUEST['id'], // liked_post_id いいねを取消しようとしているメッセージのid
    $_SESSION['id']  // pressed_member_id いいねを取消しようとしているメンバーのid
  ));
  $like = $likes->fetch();

  // いいねを取消しようとしている投稿が1件のみか確認する
  if ($like['like_cnt'] == 1) {
    // いいねを削除する
    $like_ins = $db->prepare('DELETE FROM likes WHERE liked_post_id=? AND pressed_member_id=?');
    $like_ins->execute(array(
      $_REQUEST['id'], // liked_post_id いいねしようとしているメッセージのid
      $_SESSION['id']  // pressed_member_id いいねしようとしているメンバーのid
    ));
  }
}

header('Location: index.php');
exit();
```
</div>
</details>

# 完成
いいねする前：ハートマークが色なし(横の数字は自分以外にいいねが3件ある例)
<img width="241" alt="capture1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367907/bfd37d4b-8c9c-713c-89d7-2b7e952b3112.png">
↓
いいねするとハートが赤くなって、数字が増える
<img width="246" alt="capture2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367907/ea4f2ed9-ff7a-4321-c1c0-f5e675d281cc.png">
↓
もう一度ハートマークを押すと、いいねが取り消されて色なし3件に戻る
※1メッセージに対して1人1回しかいいね出来ないようにしてある
<img width="241" alt="capture1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367907/bfd37d4b-8c9c-713c-89d7-2b7e952b3112.png">

# 振り返り
PHPの学習というよりは、SQLがメインだったかなという気がします。
試行錯誤している内にCOBOLっぽい感じになってきてしまった気がする(現職がCOBOLer)ので、もっとスマートに書けるようになりたいです。