예전에 PHP로 만들었던 간단한 CRUD 게시판에 ‘좋아요 기능’을 추가해보도록 하겠습니다. ‘좋아요 기능’은 빈 하트()를 누르면 하트가 채워지면서() 숫자 카운트가 올라가는 기능입니다.

여기서는 로그인 기능이 구현되지 않았으므로 아이피 주소(IP Address)를 기준으로 중복 좋아요를 방지하도록 하겠습니다. 만약 로그인이 구현되어 있다면 로그인한 유저를 기준으로 중복 좋아요를 방지할 수도 있습니다.

이 예제는 phpmyadmin, JQuery, Bootstrap 을 사용합니다.

참고로 게시판의 DB 테이블 구조는 다음과 같습니다.

 

1) 좋아요 기능 관련 데이터베이스 테이블 (좋아요 테이블) 만들기

아래 구조를 가진 테이블을 생성합니다.

  • id – 고유 키값입니다.
  • service_code – 각 게시물마다 사용할 수 있는 고유의 코드를 저장합니다. 예를 들어 위의 게시판에서 게시판ID를 기준으로 코드를 만든다면 phpex-1, phpex-2 이런 식으로 코드를 부여합니다. 또는 코드 대신 게시물 아이디를 저장할 수도 있습니다.
  • liked__ip – 중복 여부를 검사하기 위해 IP를 저장합니다.
  • is_like – 이 부분은 나중에 싫어요 기능을 만드는 것에 대비해 만들어 놓은 컬럼입니다. 1이면 ‘좋아요’, 0이면 ‘싫어요’ 입니다.
  • date – 기능이 실행된 때를 기록하는 컬럼입니다.

 

2) 게시판 테이블에 좋아요 카운트를 기록하는 컬럼 생성

좋아요 숫자를 별도로 기록하는 컬럼을 게시판 테이블에 생성합니다. 빨간 박스는 새로 생성한 컬럼입니다.

SELECT count(*) FROM like_table WHERE service_code = 'XXX'

사실 좋아요 카운트 컬럼을 별도로 기록하지 않고 위의 좋아요 테이블에서 count(*) 문을 사용하여 갯수를 셀 수도 있습니다만, 굳이 별도의 컬럼을 추가한 이유는 다음과 같습니다.

  • count를 사용하는 경우, 게시판 초기 로딩 시 카운트 수를 불러오기가 매우 어려워집니다. 일일히 게시글 수 만큼 count 작업을 수행해야 하며 단일 JOIN으로 처리하기도 어렵습니다.
  • 만일 처리가 가능하더라도 반복된 SELECT 호출로 인해 성능 저하를 일으킬 수 있습니다.

이러한 이유로 번거로워 보이지만 별도의 카운트를 저장하는 컬럼을 생성한 뒤, 좋아요 버튼이 클릭되면 좋아요 테이블에 새로운 아이피를 기록함과 동시에 게시판 테이블에도 좋아요 카운트 1을 늘리고, 반대로 좋아요를 취소한 경우 좋아요 테이블의 기록을 삭제하고 게시판 테이블에도 좋아요 카운트 1을 감소시키는 형태로 진행하도록 하겠습니다.

 

3) 좋아요 버튼 생성

하트 텍스트나 이모지 기호를 사용하든, 이미지를 사용하든, Font Awesome 등 별도의 이모티콘 라이브러리를 사용하든 자유입니다만, 여기서는 최대한 빠르게 작업하기 위해 텍스트를 사용하도록 하겠습니다.

하트 기호가 들어갈 부분에 하이라이트된 코드를 작성합니다.

<?php while($row = mysqli_fetch_array($res)) { ?>
<tr>
    <?php 
        $seq = $row['seq'];
        echo "<th scope='row'>"; 
        // ...
        echo '<td class="like-container"><button type="button" class="btn-like" data-article-id="'.$seq.'">'
            .'<span class="heart-shape">♡</span> <span class="like-count">'.$row['like_count'].'</span></button></td>'
    ?>
  
</tr>
<?php }?>

 

CSS 스타일을 지정합니다.

.btn-like .heart-shape {
    display: inline;
    color: red;
}

.btn-like {
    border: none;
    background-color: inherit;
}

 

아래와 같이 좋아요 버튼 부분이 생긴 것을 확인할 수 있습니다.

 

4) 좋아요 및 좋아요 취소 부분을 담당하는 JQuery 코드 작성

$(".btn-like").on("click", function(e) {
    var button = $(e.currentTarget || e.target)
    var likeCount = button.find(".like-count")
    var heartShape = button.find(".heart-shape")

    $.post("./like_proc.php", {
        articleId: button.data("articleId")
    }, function(res) {
        var addCount = (res == "like" ? 1 : res == "unlike" ? -1 : 0)
        likeCount.text(+likeCount.text() + addCount)
        heartShape.text(res == "like" ? "♥" : res == "unlike" ? "♡" : "♡")
    })
    
})

php 단에서는 한 개의 POST 파라미터를 받는다고 미리 계획하고 제이쿼리 코드를 작성합니다. 응답 메시지로 "like", "unlike", "failed"  셋 중 하나를 받는다고 가정하고 코드를 작성합니다.

  • like인 경우 현재 카운터를 1 증가시키고, 하트 모양을 채워져 있는 것으로 변경
  • unlike인 경우 현재 카운터를 1 감소시키고, 하트 모양을 비워져 있는 것으로 변경

작업은 AJAX로 처리하며, $.post를 사용하였습니다. articleId는 php단에서 받을 파라미터입니다.

 

5) 좋아요 업데이트를 처리하는 PHP 작성

필요한 기능은 아래와 같습니다.

  • 좋아요 업데이트를 하는 기능
  • 좋아요 취소를 하는 기능

POST 로 처리하도록 하겠습니다.

<?php

include 'initializeDB.php'; // $mysqli 변수 포함

$ip = $ip = $_SERVER['REMOTE_ADDR']; // 사용자의 IP주소 가져오기
$article_id = $_POST['articleId']; // 게시글 아이디

if(!empty($article_id)) {
    $sql1 = "SELECT * from dere_like WHERE service_code = 'phpex-$article_id' AND liked_ip = '$ip'";
    $res1 = mysqli_num_rows($mysqli->query($sql1)); // sql 의 행 갯수를 가져옴 

    if($res1 == 0) {
        // 좋아요 기록이 없는 경우 -> 좋아요 등록
        $sql2 = "INSERT into dere_like VALUES(0, 'phpex-$article_id', '$ip', 1, sysdate())";
        $res2 = $mysqli->query($sql2);

        // 게시판 테이블 업데이트
        $sql3 = "UPDATE messages SET like_count = like_count + 1 WHERE seq = $article_id";
        $res3 = $mysqli->query($sql3);
        echo $res2 && $res3 ? "like" : "failed";
    } else {
        // 이미 좋아요를 누른 경우 -> 좋아요 취소
        $sql2 = "DELETE from dere_like WHERE service_code = 'phpex-$article_id' AND liked_ip = '$ip'";
        $res2 = $mysqli->query($sql2);
        
        // 게시판 테이블 업데이트
        $sql3 = "UPDATE messages SET like_count = like_count - 1 WHERE seq = $article_id";
        $res3 = $mysqli->query($sql3);
        echo $res2 && $res3 ? "unlike" : "failed";
    }
}

?>

없어보이는 코드이지만 프로세스를 요약하자면

  1. 좋아요 여부를 토글하고
  2. 게시판 테이블의 like_count를 업데이트

입니다. 트랜잭션이 가능한 경우 트랜잭션 처리하여 둘 중 하나만 업데이트 되는 경우를 방지하도록 합니다.

여기까지 하면 좋아요 버튼을 눌렀을 때 좋아요가 추가되거나 감소됩니다. 그리고 실제 데이터베이스에도 반영됩니다.

 

5) 게시판을 로딩했을 때 내가 좋아요 버튼을 눌렀는지 여부 알아내기

위에서 데이터베이스는 정상적으로 처리가 되지만 새로고침하면 하트 모양 글자는 좋아요 여부와 관계없이 빈 하트가 나타나게 됩니다.

내가 좋아요를 눌렀던 글이라면 처음부터 채워진 하트 모양이 나오도록 하겠습니다.

먼저 내가 좋아요를 눌렀는지 여부를 알려주는 php를 작성하도록 하겠습니다.

<?php

include 'initializeDB.php'; //$mysqli 변수 포함

$ip = $ip = $_SERVER['REMOTE_ADDR']; // 사용자의 IP주소 가져오기
$article_id = $_POST['articleId']; // 게시글 아이디
$service_code = $_GET['getLikedByCode']; 

if(!empty($article_id)) {
    // ....
} else if(!empty($service_code)) {
    $sql1 = "SELECT * from dere_like WHERE service_code = 'phpex-$service_code' AND liked_ip = '$ip'";
    $res1 = mysqli_num_rows($mysqli->query($sql1)); // sql 의 행 갯수를 가져옴 
    
    echo $res1 != 0 ? "liked" : "unliked";
}

?>

좋아요 기록이 있다면 liked를, 없다면 unliked를 반환합니다.

다음으로 Heart 버튼을 display: none; 처리하도록 하겠습니다. 이것을 하는 이유는, 만약 로딩이 늦어졌을 때 결과가 뒤늦게 반영되는 것을 방지하고, AJAX 결과를 받아왔을 때 동시에 표시되도록 하여 혼란을 방지하도록 하기 위함입니다.

.btn-like {
    display: none;
    border: none;
    background-color: inherit;
}

 

다음으로 JQuery 부분입니다.

$(".btn-like").each(function(idx, el) {
    var button = $(el)
    var heartShape = button.find(".heart-shape")
    $.get("./like_proc.php", {
        getLikedByCode: button.data("articleId")
    }, function(res) {
        heartShape.text(res == "liked" ? "♥" : "♡")
		button.fadeIn(200)
    })  
})

each() 함수를 이용해 버튼을 순회하면서 $.get으로 해당 게시글에 내가 하트를 눌렀는지 여부를 가져오고, 그 여부에 따라 하트 모양을 결정합니다. 그 다음 fadeIn()을 적용해 좋아요 버튼이 서서히 표시가 되도록 합니다.

 

이렇게 하면 페이지를 처음 로딩했을 경우에도 좋아요 여부가 반영됩니다.

 

전체 코드 보기

 


문의 | 코멘트 또는 yoonbumtae@gmail.com  donaricano-btn


14개의 댓글

변성우 · 2021년 1월 27일 1:24 오후

허접한 실력의 asp로 제 취미 활동중인데 제게 꼭 필요한 기능을 예시로 보여주셨네요.
비록 제가 모르는 php이지만 억지로 asp로 conversion은 한듯하고요…

질문드릴 내용은 게시판 자체에서 script를 호출하는 기능이 작동하지 않는데 혹시 예시에 누락된 것은 아닌가요? 예를들면 btn-like 클릭하면 작동하도록 onclick=function() 등등이 추가되어 있어야 하는것은 아닌지요? 제가 php를 몰라서 php 에서는 그런 기능이 내제되거나 이미 포함된 것인지 모르겠습니다.

혹 보시게 된다면 회신 주시면 대단히 감사 하겠습니다.

    yoonbumtae (BGSMM) · 2021년 1월 27일 3:58 오후

    안녕하세요.
    btn-like 이벤트에 관련 부분을 찾아봤는데
    4) 좋아요 및 좋아요 취소 부분을 담당하는 JQuery 코드 작성
    이 항목에 관련 내용이 있습니다.
    편의상 자바스크립트 부분을 백엔드 부분보다 먼저 설명한 것 같습니다.
    감사합니다.

변성우 · 2021년 1월 27일 1:33 오후

추가로 좋아요 테이블의 script 부분에 있는 articleId는 별도로 지정하는 과정 없이도 저절로 script 부분에 읽어와서 게시판 테이블로 값을 넘져주게 되는 것인지요?
제 짧은 생각으로는 articleId라는 id값을 btn-like 부분에 지정해주어야 script로 값이 지정될 듯한데요.

이것이 articleId 값이 게시판 처리 like_proc.php로 넘어가지 못하는 이유는 아닌지요?

    yoonbumtae (BGSMM) · 2021년 1월 27일 4:02 오후

    안녕하세요.
    님 말이 맞습니다. 코드에서 그 부분이 생략된 채 들어가 있네요.
    php 단에서 페이지를 렌더링할 때 button 태그에 data-article-id="26" 처럼 articleId를 지정해야 합니다. 그렇게 지정하면 제이쿼리에서 button.data("articleId") 해서 읽는 것입니다.
    이 부분은 원본 코드를 찾아보고 수정하도록 하겠습니다.
    감사합니다.

      변성우 · 2021년 1월 28일 5:27 오후

      감사합니다.
      수정해서 구현했습니다. 5번 항목의 자신의 투표 결과는 별도의 페이지 수정 없이 그냥 최초 로딩 시 디비 데이터 읽어와서 뿌려주어도 되는듯하여 그렇게 간략화했습니다. 이것도 문제 없을듯은 합니다만…

      추가로 혹 하트 모양을 예제의 text가 아닌 fontawesome을 사용하고자 하는데 heartShape.text(res == “like” ? 이후에서 계속 텍스트로만 읽어와서 도무지 구현이 쉽지 않네요. img로도 안되고 생각보다 간다할듯 한데 쉽지가 않네요. 혹 가능한 방법에 대한 팁이라도 주시면 무지무지 감사 하겠습니다.

        yoonbumtae (BGSMM) · 2021년 1월 28일 10:11 오후

        fontawesome을 사용한다면 다음 방법을 추천합니다.

        예를 들어 하트 부분이 아래 코드와 같다면
        <button class="btn-like"><i class="far fa-heart"></i></button>

        클래스가 far라면 속이 빈 하트, fas이면 속이 채워진 하트입니다.
        좋아요 기능을 구현할 때, 삼항 연산자 대신 if문을 사용해서 클래스명을 토클링합니다.

        if (res == "unliked") {
                        container.find("i").removeClass("fas").addClass("far")
                    } else if (res == "liked") {
                        container.find("i").removeClass("far").addClass("fas")
                    }

        이런식으로 하면 되지 않을까 싶습니다.

        감사합니다.

          변성우 · 2021년 1월 29일 8:42 오전

          정말 감사합니다. 님 덕분에 또 큰 산을 넘은 느낌입니다.
          어제 하루종일 구글링 통해서 이잡듯이 뒤져서 heartShape.text==> heartShape.html 요렇게 변경하고 하트 text 대신 fontawesome의 <i class="…. 적용해주니 되네요. 정말 감사합니다.

우미 · 2021년 3월 25일 10:14 오전

잘 설명된 글 재밌게 읽었습니다. 한가지 궁금한 거 있어 댓글 적습니다.
count 필드를 만드신 이유가 좋아요 테이블 전체를 select해서 count하는것이 비효율적일까봐인 것인데, 내가 즣아요했는가?에 대한 정보는 이런 면에서 어떻게 구현해야할 지 고민입니다.
게시물 id와 사용자 id 혹은 ip로 인덱싱을 하면 좀 나아질까? 싶은 생각인데 이에 대해선 어떻게 생각하시나요?
아마 지금 구현으로 봐서는 count 수를 세지 않기위해 article에 like count 필드를 만들었지만 내가 조회했는가를 체크할 때 결국 테이블 전체를 한 번 조회해야하지않나 싶기도 합니다.

    yoonbumtae (BGSMM) · 2021년 3월 25일 11:22 오전

    안녕하세요.
    말씀하신 게 맞는 것 같습니다.
    일단 제가 count 필드를 별도로 만든 이유는
    게시판에 제목 링크만 있는 경우와 로그인하지 않은 사람한테는 좋아요 횟수 조회 기능만 제공할 경우를
    고려하여 이렇게 만드는게 좋을것 같다 싶어 차용한 것입니다.
    그런데 제가 만든 예제나 인스타그램처럼 처음부터 좋아요 조회뿐만 아니라 자신이 좋아요 했는지 여부도 체크해야되는 경우라면
    필연적으로 어떤식으로든 좋아요 테이블 조회가 필요하곘네요.
    거기까지는 생각을 못했는데 인덱싱을 하면 성능향상이 있을 것 같습니다.
    그리고 애초에 위 예제는 자신이 좋아여 눌렀는지 여부를 확인하기 위해 일일히 글 하나마다 리퀘스트를 보내는것도 말이 안되는 것이였기 때문에
    group by까지 고려한 인덱싱을 사용해서 테이블 조인을 하면 더 나을것 같기도 합니다.
    http://jason-heo.github.io/mysql/2014/03/05/index-opt-for-groupby.html
    성능 최적화에 대해 더 공부하겠습니다.
    감사합니다.

      우미 · 2021년 3월 25일 11:24 오전

      감사합니다~ 저도 많이 배우고 갑니다 ㅎㅎ

버스터콜 · 2021년 4월 1일 4:54 오후

안녕하세요 ajax를 배우고 있는 학생입니다
제가 만든 게시판과 db에 작성하신 코드를 활용해 보았습니다, 그런데 좋아요 버튼을 눌렀을 때 반응이 좀처럼 나오질 않아 잠시 접어두고 올려두신 전체 코드로 테이블명, 코드명까지 전부 일치시켜서 말 그대로 100퍼센트 똑같이 하여서 만들어 보았습니다.
대부분의 기능이 정상적으로 작동하지만, 만드신 사이트처럼 하트를 눌렀을 때 반응이 다른데요
1. 바로 변화가 이루어지지 않습니다 f5를 눌러 새로고침을 해야 올라간 좋아요 값이 보입니다
2. db에는 값이 등록됩니다 하지만 두 번째 눌러서 값이 원위치로 되어야 하는데 누른 버튼 횟수만큼 값이 계속해서 올라갑니다
3. 제이쿼리의 버전이 문제인가 싶어서 code.jquery.com/jquery-3.3.1.min.js를
https://code.jquery.com/jquery-3.6.0.min.js로 바꾸어 보았더니 좋아요 버튼 자체가 사라졌습니다

위와 같은 문제를 해결해야 제 게시판에 적용 할 수 있는 실마리라도 잡을 수 있지 않을까 생각합니다
바쁘시겠지만 짐작가는 것이라도 알려주시면 감사하곘습니다 파일명, 코드 모두 100펴센트 같습니다

    yoonbumtae (BGSMM) · 2021년 4월 1일 6:42 오후

    안녕하세요.
    먼저 예제에서 사용된 제이쿼리 버전은 3.3.1 버전입니다.
    그리고 코드는 제 환경에서 확인해본 결과는 문제가 없었습니다.
    새로고침했을때 정상적으로 작동하는거 보면 서버와 통신하는 부분은 이상이 없고
    자바스크립트에서 하트 모양을 바꾸는 부분 (4. 좋아요 및 좋아요 취소 부분을 담당하는 JQuery 코드 작성)
    에 문제가 있는 것 같습니다.
    위에서 서버로부터 성공했다는 메시지가 왔으면 하트 모양을 채운 하트로 바꾸면서 1을 추가하고,
    서버로부터 ‘좋아요 취소’했다는 메시지가 왔으면 하트를 빈 하트로 변경하고 1을 감소해야 합니다.

    1. 서버로부터 응답이 왔을 때 콜백 함수가 정상적으로 동작하지 않을 수 있으므로 콘솔 로그로 확인해보거나
    2. HTML과 제이쿼리의 요소 클래스 이름이 서로 다를 수 있으므로 확인
    3. 삼항연산자를 if문으로 변환
    4. 서버에서 보내는 메시지와 자바스크립트에서의 메시지가 일치하지 않음
    5. 가능성은 낮으나 하트 기호를 브라우저, 운영체제에서 제대로 인식하지 못하는 경우 -> 이미지나 fontawesome 이모티콘 등으로 대체

    정도로 생각이 납니다.
    console.log()로 확인해보는것을 추천드립니다.
    감사합니다.

      버스터콜 · 2021년 4월 2일 10:22 오전

      좋아요 처리 php 파일의 테이블 명에 오타가 있어서 생긴 오류였습니다 이제 이 기능을 다른 게시판에 적용해보는 연습도 해야겠네요 감사합니다

버스터콜 · 2021년 4월 1일 8:06 오후

감사합니다 말씀하신대로 적용해보겠습니다

답글 남기기

이메일 주소를 발행하지 않을 것입니다. 필수 항목은 *(으)로 표시합니다