PHPとAjaxでいいねボタンを作ってみた

#はじめに
こんにちは、Kana。です！
今回は制作したポートフォリオ「[POAD](https://poad.kanasann.com)」(1日1投稿しかできない掲示板サービス)で実装されている`いいねボタン`について書いていきます。

 いいねボタンの実装、だいぶ苦戦しました。( ´Д`)
そもそも、Ajaxの知識も曖昧のまま実装しようとしていたためなかなか上手く実装できず。(当たり前)

`いいねボタン`について調べるも引っかかる記事はデータベースを利用しないものが多く私がやりたいものと違っていたり。

そんなこんなで苦労しながらもやっと実装できたので振り返りも兼ねて`いいねボタン`の私流の実装方法を紹介していきます。
内容について、ここはこうした方がいいよとかございましたらご指摘いただけると幸いです。

#実装環境
まず、実装方法について説明する前に実装した開発環境について紹介します。

開発OS：MacBook Pro
開発言語：PHP、JavaScript（Ajax）、HTML、CSS
データベース：MySQL
ライブラリ、ツール：jQuery、MAMP、Font Awesome

#Ajaxについて
`いいねボタン`を実装するにあたり採用したAjaxについてちょっとだけ説明。

Ajaxとは、**JavaScriptを使用して行われる非同期処理のことです。**
通常、Webページの内容が変更される際には画面遷移やリロードされます。しかし、Ajaxを使用すると非同期、つまりリロードせずにページの内容を変更させることができるのです。

Ajaxについて詳しく知りたい方はこちらを参考にしてみるといいかもです。
→[初心者目線でAjaxの説明](https://qiita.com/hisamura333/items/e3ea6ae549eb09b7efb9)

`いいねボタン`をクリックした際にいいねの数やいいねのスタイルを非同期で反映させたかったため今回Ajaxを使用することにしました。

#いいねボタンを実装する流れ
ざっとこんな流れでやりました！

1. いいねボタンが押された時にその投稿IDを取得。
2. Ajaxで取得した投稿IDをPHPファイルへ渡す。
3. 取得された投稿IDと押した本人のID（セッションを使う）を元にいいねされた投稿を格納しているテーブルに挿入。
4. 既にいいねテーブルにレコードがあればいいねを取り消す(レコード削除)。
5. いいねの総数を取得してAjaxの引数に渡す。
6. いいね数を表示・いいねを押した・取り消したでスタイルを変化させる。
7. 元々本人がいいねを押している投稿にはそのまま押された場合のスタイルを保持する。

##テーブル構造
テーブル名：good
<img width="1228" alt="goodテーブルの構造" src="https://qiita-image-store.s3.amazonaws.com/0/255876/8a53daeb-bccb-d308-cd24-7227525f64d2.png">

_※以下のソースコードはいいね機能の部分だけ抜粋して表示しています。関数の詳細やソースコードの詳細については[**GitHub**](https://github.com/kgkgon/PostOnceADay)に掲載しているのでそちらもご覧ください。_
##JavaScriptでの処理コード
```js:good.js
$(function(){
	var $good = $('.btn-good'), //いいねボタンセレクタ
		        goodPostId; //投稿ID
	$good.on('click',function(e){
		e.stopPropagation();
		var $this = $(this);
        //カスタム属性（postid）に格納された投稿ID取得
		goodPostId = $this.parents('.post').data('postid'); 
		$.ajax({
			type: 'POST',
			url: 'ajaxGood.php', //post送信を受けとるphpファイル
			data: { postId: goodPostId} //{キー:投稿ID}
		}).done(function(data){
			console.log('Ajax Success');

			// いいねの総数を表示
			$this.children('span').html(data);
			// いいね取り消しのスタイル
			$this.children('i').toggleClass('far'); //空洞ハート
			// いいね押した時のスタイル
			$this.children('i').toggleClass('fas'); //塗りつぶしハート
			$this.children('i').toggleClass('active');
			$this.toggleClass('active');
		}).fail(function(msg) {
			console.log('Ajax Error');
		});
	});
});
```
##AjaxでPOST送信された後の処理コード
```php:ajaxGood.php
<?php
//共通変数・関数ファイルを読込み
require('function.php');

// postがある場合
if(isset($_POST['postId'])){
	$p_id = $_POST['postId'];

	try{
		//DB接続
		$dbh = dbConnect();
		// goodテーブルから投稿IDとユーザーIDが一致したレコードを取得するSQL文
		$sql = 'SELECT * FROM good WHERE post_id = :p_id AND user_id = :u_id';
		$data = array(':p_id' => $p_id, 'u_id' => $_SESSION['user_id']);
		// クエリ実行
		$stmt = queryPost($dbh, $sql, $data);
		$resultCount = $stmt->rowCount();
		// レコードが1件でもある場合
		if(!empty($resultCount)){
			// レコードを削除する
			$sql = 'DELETE FROM good WHERE post_id = :p_id AND user_id = :u_id';
			$data = array(':p_id' => $p_id, ':u_id' => $_SESSION['user_id']);
			// クエリ実行
			$stmt = queryPost($dbh, $sql, $data);
			echo count(getGood($p_id));
		}else{
			// レコードを挿入する
			$sql = 'INSERT INTO good (post_id, user_id, created_date) VALUES (:p_id, :u_id, :date)';
			$data = array(':p_id' => $p_id, ':u_id' => $_SESSION['user_id'], ':date' => date('Y-m-d H:i:s'));
			// クエリ実行
			$stmt = queryPost($dbh, $sql, $data);
			echo count(getGood($p_id));
		}
	}catch(Exception $e){
		error_log('エラー発生：'.$e->getMessage());
	}
}
```
##画面表示部分のコード
```php:goodView.php
<?php
//共通変数・関数ファイルを読込み
require('function.php');

$p_id = ''; //投稿ID
$dbPostData = ''; //投稿内容
$dbPostGoodNum = ''; //いいねの数

// get送信がある場合
if(!empty($_GET['p_id'])){
	// 投稿IDのGETパラメータを取得
	$p_id = $_GET['p_id'];
	// DBから投稿データを取得
	$dbPostData = getPostData($p_id);
	// DBからいいねの数を取得
	$dbPostGoodNum = count(getGood($p_id));
?>

<!-- いいねボタン部分だけ切り取っています -->
<section class="post" data-postid="<?php echo sanitize($p_id); ?>">
	<div class="btn-good <?php if(isGood($_SESSION['user_id'], $dbPostData['id'])) echo 'active'; ?>">
		<!-- 自分がいいねした投稿にはハートのスタイルを常に保持する -->
		<i class="fa-heart fa-lg px-16
		<?php
			if(isGood($_SESSION['user_id'],$dbPostData['id'])){ //いいね押したらハートが塗りつぶされる
				echo ' active fas';
			}else{ //いいねを取り消したらハートのスタイルが取り消される
				echo ' far';
			}; ?>"></i><span><?php echo $dbPostGoodNum; ?></span>
	</div>
</section>
```
＜補足＞カスタムデータ属性について
タグの中に`data-名前`を指定することで独自データを格納することができます。

```php:カスタムデータ属性
<!-- カスタムデータ属性にpostidを設置しその中に投稿IDを格納している -->
<section class="post" data-postid="<?php echo sanitize($p_id); ?>">
```

```css:style.css
.btn-good{
	display: inline-block;
	padding: 0 8px;
	cursor: pointer;
}
.btn-good:hover{
	color: #f44336;
}
.active{
	color: #f44336;
}
.btn-good .active{
	color: #f44336;
}
```

##いいねボタンのスタイルについて
いいねボタンはFont Awesomeの「[heart](https://fontawesome.com/icons/heart?style=regular)」を使用しています。
いいねといいね取り消しのスタイルを変更するのに2種類のheart iconを切り替えて実装しています。(~~だいぶこじつけなコードになってます 笑~~)

最終的にこんな感じになります↓
<img width="720" alt="いいね押す前" src="https://qiita-image-store.s3.amazonaws.com/0/255876/5a1215ab-5930-f2e7-5409-c469fffd22ee.png">
<img width="722" alt="いいね押した後" src="https://qiita-image-store.s3.amazonaws.com/0/255876/47c9413a-ae2f-3a21-9647-d39b0d2fdbe8.png">


##おわりに
今回のいいねボタンの実装を通していいねボタンがどういった流れで実装されているのかが分かりました。
ブログラミングにおいては当たり前のことだと思いますが機能を実装する前やなかなかできなくて詰んでる時は一旦PCの前から離れて（~~離れなくてもいいけど~~）何を実装したいのか、どういう順序・流れでやればいいのかをもう一度考え直すことが重要だと実感しました。