Welcome to a tutorial on how to build a simple like and dislike rating system with PHP and MySQL. Social media is big these days, and I am sure you have already seen “upvote/downvote” or “like/dislike” features everywhere. If you want to create your own independent like/dislike system, let us walk through an example – Read on!
TABLE OF CONTENTS
DOWNLOAD & NOTES
Here is the download link to the example code, so you don’t have to copy-paste everything.
EXAMPLE CODE DOWNLOAD
Just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
SORRY FOR THE ADS...
But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.
Buy Me A Coffee Code Boxx eBooks
PHP MYSQL LIKE DISLIKE SYSTEM
All right, let us now get into more details of building a like-dislike system with PHP and MYSQL.
TUTORIAL VIDEO
PART 1) REACTIONS DATABASE TABLE
CREATE TABLE `reactions` (
`id` bigint(20) NOT NULL,
`user_id` bigint(20) NOT NULL,
`reaction` tinyint(1) NOT NULL DEFAULT '1'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `reactions`
ADD PRIMARY KEY (`id`,`user_id`),
ADD KEY `reaction` (`reaction`);
Field | Description |
id |
Primary and foreign key. The post ID, product ID, video ID – Whatever you want to add a like/dislike button to. |
user_id |
Primary and foreign key. The user who liked/disliked. |
reaction |
For this example, we will use -1 for dislike and 1 for like. |
PART 2) PHP LIKES/DISLIKES LIBRARY
<?php
class Reactions {
// (A) CONSTRUCTOR - CONNECT TO DATABASE
private $pdo;
private $stmt;
public $error;
function __construct () {
$this->pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_NAMED
]);
}
// (B) DESTRUCTOR - CLOSE DATABASE CONNECTION
function __destruct () {
$this->pdo = null;
$this->stmt = null;
}
// (C) HELPER FUNCTION - RUN SQL
function query ($sql, $data=null) : void {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($data);
}
// (D) GET REACTIONS FOR ID
function get ($id, $uid=null) {
// (D1) GET TOTAL REACTIONS
$reacts = ["react" => [0, 0]]; // [likes, dislikes]
$this->query(
"SELECT `reaction`, COUNT(`reaction`) `total`
FROM `reactions` WHERE `id`=?
GROUP BY `reaction`", [$id]
);
while ($r = $this->stmt->fetch()) {
if ($r["reaction"]==1) { $reacts["react"][0] = $r["total"]; }
else { $reacts["react"][1] = $r["total"]; }
}
// (D2) GET REACTION BY USER (IF SPECIFIED)
if ($uid != null) {
$this->query(
"SELECT `reaction` FROM `reactions` WHERE `id`=? AND `user_id`=?",
[$id, $uid]
);
$reacts["user"] = $this->stmt->fetchColumn();
}
// (D3) DONE - RETURN RESULTS
return $reacts;
}
// (E) SAVE REACTION
function save ($id, $uid, $react) {
// (E1) FORMULATE SQL
if ($react == 0) {
$sql = "DELETE FROM `reactions` WHERE `id`=? AND `user_id`=?";
$data = [$id, $uid];
} else {
$sql = "REPLACE INTO `reactions` (`id`, `user_id`, `reaction`) VALUES (?,?,?)";
$data = [$id, $uid, $react];
}
// (E2) EXECUTE SQL
$this->query($sql, $data);
return true;
}
}
// (F) DATABASE SETTINGS - CHANGE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8mb4");
define("DB_USER", "root");
define("DB_PASSWORD", "");
// (G) NEW REACTIONS OBJECT
$_REACT = new Reactions();
The library looks massive at first, but keep calm and look carefully.
- (A, B, G) On creating
$_REACT = new Reactions()
, the constructor will automatically connect to the database. The destructor closes the connection. - (C)
query()
is a helper function to run an SQL query. - (D & E) There are only 2 “actual functions”.
get()
Gets the total number of reactions (likes and dislikes) for the given post/video/product ID. If the user ID is provided, it will also get that user’s reaction.save()
Self-explanatory, save a user’s reaction. Set$react = 0
to remove the user’s reaction.
- (F) Remember to change the database settings to your own.
PART 3) AJAX HANDLER
if (isset($_POST["req"])) {
// (A) LOAD LIKE DISLIKE LIBRARY
require "2-lidi-lib.php";
// (B) FIXED DUMMY USER ID
$uid = 1;
// (C) HANDLE REQUESTS
switch ($_POST["req"]) {
// (C1) GET REACTIONS
case "get":
echo json_encode($_REACT->get($_POST["pid"], $uid));
break;
// (C2) SAVE REACTION
case "save":
if ($_REACT->save($_POST["pid"], $uid, $_POST["react"])) {
echo json_encode($_REACT->get($_POST["pid"], $uid));
} else { echo $_REACT->error ; }
break;
}}
Next, we have an AJAX handler to “map” user requests to the library functions.
- That is, send a request
$_POST["req"]
and the required parameters over, and this will process it accordingly. - A quick example of those who are lost – We send
$_POST["req"]="save"
,$_POST["pid"]=900
, and$_POST["react"]=1
to this script when the user likes the product.
P.S. Take note that the user ID is fixed in this example. You should be tying this into your own system, and opening the like/dislike feature to registered users only.
STEP 4) DUMMY PRODUCT PAGE
4A) THE HTML
<!-- (A) LOAD LIKE/DISLIKE "WIDGET" -->
<link rel="stylesheet" href="lidi.css">
<script src="lidi.js"></script>
<script src="4-demo.js"></script>
<!-- (B) DUMMY PRODUCT -->
<div id="demowrap">
<h1>PRODUCT</h1>
<img src="box.png">
<div id="demo"></div>
</div>
The only important part here is using <div id="demo">
to generate the like/dislike button. Also, lidi.js
and lidi.css
is a like/dislike widget that I created. Check the links below if you are interested to learn more.
4B) THE JAVASCRIPT
var demo = {
// (A) SUPPORT FUNCTION - AJAX FETCH
fetch : (data, load) => {
// (A1) FORM DATA
var form = new FormData();
for (let [k,v] of Object.entries(data)) { form.append(k, v); }
// (A2) AJAX FETCH
fetch("2-lidi-lib.php", { method: "post", body : form })
.then(res => res.text())
.then(res => load(res))
.catch(err => console.error(err));
},
// (B) INIT
lidi : null, // like dislike instance
init : () => demo.fetch(
// (B1) GET LIKES COUNT FROM SERVER
{
req : "get",
pid : 900 // fixed dummy product id
},
// (B2) DRAW HTML LIKE/DISLIKE BUTTON
data => {
demo.lidi = lidi({
hWrap : document.getElementById("demo"),
status : data.user ? data.user : 0,
count : data.react,
change : demo.save
});
}
),
// (C) SAVE LIKE/DISLIKE REACTION
save : status => demo.fetch(
// (C1) SEND NEW STATUS TO SERVER
{
req : "save",
react : status,
pid : 900 // fixed dummy product id
},
// (C2) UPDATE HTML COUNT
res => demo.lidi.recount(res.react)
)
};
window.onload = demo.init;
Not going to explain this line-by-line, so a quick walkthrough:
demo.fetch()
A helper function to do an AJAX call to2-lidi-lib.php
.demo.init()
Runs on page load. It gets the like/dislike count from the server and sets up the HTML widget.demo.save()
This is fired when the user changes the like/dislike reaction. Once again, we will send the new reaction to update the server.
EXTRAS
That’s all for the code, and here are a few small extras that may be useful to you.
A SMALL NOTE REGARDING THE COUNT
- As you can see, we are doing a
COUNT(*)
to get the total number of likes and dislikes. - We get the likes/dislikes count when the user first loads the page.
- Also when the user changes the like/dislike status.
This works for small projects. But there’s a good reason why most social media remove exact and accurate reaction counts – Counting millions of likes is very heavy on system resources. So if you ever get to that stage, do consider other ways to deal with the likes/dislikes count. Maybe cache the SQL, or create a “total count table”.
MUST THERE BE A USER SYSTEM?
Technically, we can generate a random ID and set it in a cookie – Use this as a “temporary unique user ID”. But the security concern remains, anyone who knows well enough can create a simple bot to repeat “like and delete the cookie”.
There will be a lot of spam if you open the like/dislike system to the public. So yes, I will recommend keeping like/dislike as a “users-only feature”.
HOW ABOUT MULTIPLE PRODUCTS?
<!-- (A) HTML PRODUCTS -->
<div class="pdt" data-pid="1"></div>
<div class="pdt" data-pid="2"></div>
<script>
// (B) ATTACH LIKE/DISLIKE BUTTONS
for (let p of document.querySelectorAll(".pdt")) {
lidi({
hWrap : p,
change : stat => demo.fetch({
req : "save",
react : stat,
pid : p.dataset.pid
})
});
}
</script>
But of course, this is only a simple example. There are endless ways to do it – Just make sure that the proper product ID is associated with the like/dislike button and passed to the server “on reaction change”.
LINKS & REFERENCES
- Simple User Login System With PHP MySQL – Code Boxx
- Simple User Registration With Email Verification In PHP MYSQL – Code Boxx
- Simple Like Dislike Button With Pure HTML JS – Code Boxx
- If you do not want to build a user system, you really should be looking at the Facebook Like Button for web pages.
THE END
Thank you for reading, and we have come to the end of this guide. I hope that it has helped you to improve your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!
Thanks for your help
hello what if the i want to use the system in a loop
like in my post page i am using a while loop to display all publish post
As above – HOW ABOUT MULTIPLE PRODUCTS?
“If you do not want to build a user system, you really should be looking at the Facebook Like Button for web pages.” what’s special with facebook? how to control spam? thank you.
1) You don’t need to create your own user and likes system, but users need to have an FB account.
2) All data is held on FB.
3) FB will deal with the spam and updates, you just implement the API and relax.
Thanks for the great code. In my scenario I will add to different products. But likes and dislikes are the same everywhere. How to set it for each product?
Tutorial updated. See “HOW ABOUT MULTIPLE PRODUCTS?” above.
Many thanks! This is easy to implement just what I was looking for. Because of a brain damage it is not possible for me anymore to understand all the code but this is very clear and easy to implement. I am wondering if it is possible to use the <alt tag for the images so people who have non or no good visibility also know what buttons there are when their device speaks out the text. And if possible how do I implement this?
Use both
aria-label
andtitle
.Out of topic for this guide, but read up on HTML ARIA.