The 5 star rating system allows users to rate articles on a scale of 1 to 5.
It is a popular way for visitors to merchant websites to rate the quality of products and services. It is also commonly used to rate the quality of user-generated content, such as reviews and comments.
In this tutorial, I am creating a 5 star rating system in CodeIgniter 4 where the ratings will be stored in a database and the average rating will be displayed with the article. Users can rate an article and based on the rating the average rating gets updated.
Contents
1. Database configuration
- Open
.env
file which is available at the project root.
NOTE – If dot (.) not added at the start then rename the file to .env.
- Remove # from start of
database.default.hostname
,database.default.database
,database.default.username
,database.default.password
,database.default.DBDriver
,database.default.DBPrefix
, anddatabase.default.port
. - Update the configuration and save it.
database.default.hostname = 127.0.0.1 database.default.database = codeigniterdb database.default.username = root database.default.password = root database.default.DBDriver = MySQLi database.default.DBPrefix = database.default.port = 3306
2. Enable CSRF
- Again open
.env
file. - Remove # from the start of the
security.tokenName
,security.headerName
,security.cookieName
,security.expires
,andsecurity.regenerate
. - I update the
security.tokenName
value with'csrf_hash_name'
. With this name read CSRF hash. You can update it with any other value. - If you don’t want to regenerate CSRF hash after each request then set
security.regenerate = false
.
security.tokenName = 'csrf_hash_name' security.headerName = 'X-CSRF-TOKEN' security.cookieName = 'csrf_cookie_name' security.expires = 7200 security.regenerate = true
- Open
app/Config/Filters.php
file. - Uncomment
'csrf'
in'before'
if commented.
// Always applied before every request public $globals = [ 'before' => [ // 'honeypot', 'csrf', // 'invalidchars', ], 'after' => [ 'toolbar', // 'honeypot', // 'secureheaders', ], ];
3. Create Table
I am creating 2 tables using migration –
- posts – Store post records.
- post_ratings – Store user rating on a post.
posts Table –
php spark migrate:create create_posts_table
- Now, navigate to
app/Database/Migrations/
folder from the project root. - Find a PHP file that ends with
CreatePostsTable
open it. - Define the table structure in the
up()
method. - Using the
down()
method deleteposts
table that calls when undoing migration.
<?php namespace App\Database\Migrations; use CodeIgniter\Database\Migration; class CreatePostsTable extends Migration { public function up(){ $this->forge->addField([ 'id' => [ 'type' => 'INT', 'constraint' => 10, 'unsigned' => true, 'auto_increment' => true, ], 'title' => [ 'type' => 'VARCHAR', 'constraint' => '100', ], 'description' => [ 'type' => 'TEXT', 'null' => true, ], 'link' => [ 'type' => 'VARCHAR', 'constraint' => '255', ], ]); $this->forge->addKey('id', true); $this->forge->createTable('posts'); } public function down(){ $this->forge->dropTable('posts'); } }
post_ratings Table –
php spark migrate:create create_post_ratings_table
- Again, navigate to the
app/Database/Migrations/
folder from the project root. - Find a PHP file that ends with
CreatePostRatingsTable
open it. - Define the table structure in the
up()
method. Added foreign key onpost_id
field. I didn’t add a foreign key onuser_id
but you can add it will implementing. - Using the
down()
method deletepost_ratings
table that calls when undoing migration.
<?php namespace App\Database\Migrations; use CodeIgniter\Database\Migration; class CreatePostRatingsTable extends Migration { public function up(){ $this->forge->addField([ 'id' => [ 'type' => 'INT', 'constraint' => 10, 'unsigned' => true, 'auto_increment' => true, ], 'user_id' => [ 'type' => 'INT', 'constraint' => 10, 'unsigned' => true, ], 'post_id' => [ 'type' => 'INT', 'constraint' => 10, 'unsigned' => true, ], 'rating' => [ 'type' => 'VARCHAR', 'constraint' => 10 ] ]); $this->forge->addKey('id', true); $this->forge->addForeignKey('post_id', 'posts', 'id', 'CASCADE', 'CASCADE'); $this->forge->createTable('post_ratings'); } public function down(){ $this->forge->dropTable('post_ratings'); } }
- Run the migration –
php spark migrate
4. Model
Create 2 Models –
- Posts
- PostRatings
Posts Model –
- Create
Posts
Model –
php spark make:model Posts
- Open
app/Models/Posts.php
file. - In
$allowedFields
Array specify field names –['title','description','link']
that can be set during insert and update.
Completed Code
<?php namespace App\Models; use CodeIgniter\Model; class Posts extends Model { protected $DBGroup = 'default'; protected $table = 'posts'; protected $primaryKey = 'id'; protected $useAutoIncrement = true; protected $insertID = 0; protected $returnType = 'array'; protected $useSoftDeletes = false; protected $protectFields = true; protected $allowedFields = ['title','description','link']; // Dates protected $useTimestamps = false; protected $dateFormat = 'datetime'; protected $createdField = 'created_at'; protected $updatedField = 'updated_at'; protected $deletedField = 'deleted_at'; // Validation protected $validationRules = []; protected $validationMessages = []; protected $skipValidation = false; protected $cleanValidationRules = true; // Callbacks protected $allowCallbacks = true; protected $beforeInsert = []; protected $afterInsert = []; protected $beforeUpdate = []; protected $afterUpdate = []; protected $beforeFind = []; protected $afterFind = []; protected $beforeDelete = []; protected $afterDelete = []; }
PostRatings Model –
- Create
PostRatings
Model –
php spark make:model PostRatings
- Open
app/Models/PostRatings.php
file. - In
$allowedFields
Array specify field names –['user_id','post_id','rating']
that can be set during insert and update.
Completed Code
<?php namespace App\Models; use CodeIgniter\Model; class PostRatings extends Model { protected $DBGroup = 'default'; protected $table = 'post_ratings'; protected $primaryKey = 'id'; protected $useAutoIncrement = true; protected $insertID = 0; protected $returnType = 'array'; protected $useSoftDeletes = false; protected $protectFields = true; protected $allowedFields = ['user_id','post_id','rating']; // Dates protected $useTimestamps = false; protected $dateFormat = 'datetime'; protected $createdField = 'created_at'; protected $updatedField = 'updated_at'; protected $deletedField = 'deleted_at'; // Validation protected $validationRules = []; protected $validationMessages = []; protected $skipValidation = false; protected $cleanValidationRules = true; // Callbacks protected $allowCallbacks = true; protected $beforeInsert = []; protected $afterInsert = []; protected $beforeUpdate = []; protected $afterUpdate = []; protected $beforeFind = []; protected $afterFind = []; protected $beforeDelete = []; protected $afterDelete = []; }
5. Routes
- Open
app/Config/Routes.php
file. - Define 2 routes –
- / – Display records list with rating bar.
- post/updaterating – Update user post rating.
$routes->get('/', 'PostsController::index'); $routes->get('post/updaterating', 'PostsController::updateRating');
6. Controller
- Create
PostsController
Controller –
php spark make:controller PostsController
- Open
app/Controllers/PostsController.php
file. - Import
Posts
andPostRatings
Model. - I created
$userid
class variable to store user id for rating. You can update its value with logged-in SESSION user id.’
Create 2 methods –
- index() – Fetch all records from the
posts
table. Loop on the fetched records to get user rating on thepost_ratings
table and calculate average rating of the post.
Pass $userrating
and $avgrating
with post data.
Load index
view and pass $data
Array.
- updateRating() – Using this method handle AJAX requests.
Read passed post_id
and rating
and assign them to the variables. Check if user has any rating on the $post_id
or not in the post_ratings
table.
If exists then update the rating
field value with $rating
otherwise insert a new record in the post_ratings
table.
Calculate the average rating of the $post_id
and assign to $data['avgrating']
.
Return $data
Array in JSON format.
Completed Code
<?php namespace App\Controllers; use App\Controllers\BaseController; use App\Models\Posts; use App\Models\PostRatings; class PostsController extends BaseController { public $userid = 2;// Replace this value with your user SESSION variable public function index(){ $postsObj = new Posts(); $postRatingsObj = new PostRatings(); // Fetch all posts $postData = $postsObj->select('*')->findAll(); foreach($postData as $key=>$post){ ## User Rating $postratingData = $postRatingsObj->select('rating') ->where('post_id',$post['id']) ->where('user_id',$this->userid) ->find(); $userrating = 0; if(!empty($postratingData)){ $userrating = $postratingData[0]['rating']; } ## Post average rating $postratingData = $postRatingsObj->select('ROUND(AVG(rating),1) as averageRating') ->where('post_id',$post['id']) ->find(); $avgrating = $postratingData[0]['averageRating']; if($avgrating == ''){ $avgrating = 0; } $postData[$key]['userrating'] = $userrating; $postData[$key]['avgrating'] = $avgrating; } $data['posts'] = $postData; return view('index',$data); } public function updateRating(){ $request = service('request'); $getData = $request->getGet(); $post_id = $getData['post_id']; $rating = $getData['rating']; $postRatingsObj = new PostRatings(); // Check user rating $postratingData = $postRatingsObj->select('id') ->where('post_id',$post_id) ->where('user_id',$this->userid) ->find(); if(!empty($postratingData)){ ## Update user rating $postrating_id = $postratingData[0]['id']; $postRatingsObj->set('rating', $rating); $postRatingsObj->where('id', $postrating_id); $postRatingsObj->update(); }else{ ## Insert user rating $insdata = [ 'user_id' => $this->userid, 'post_id' => $post_id, 'rating' => $rating ]; $postRatingsObj->insert($insdata); } // Calculate average rating $postratingData = $postRatingsObj->select('ROUND(AVG(rating),1) as averageRating') ->where('post_id',$post_id) ->find(); $avgrating = $postratingData[0]['averageRating']; if($rating == ''){ $avgrating = 0; } $data['avgrating'] = $avgrating; return $this->response->setJSON($data); } }
7. View
- Create
index.php
file inapp/Views/
folder. - I am using bootstrap-star-rating jQuery plugin for the rating bar. You can download it from here.
- Extract the zip file in
public/
folder. - Include Bootstrap, Fontawesome, jQuery and bootstrap-star-rating library –
<!-- CSS --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/fontawesome.min.css"> <link rel="stylesheet" href="<?= base_url('bootstrap-star-rating/css/star-rating.css') ?>" media="all" type="text/css"/> <link rel="stylesheet" href="<?= base_url('bootstrap-star-rating/themes/krajee-svg/theme.css') ?>" media="all" type="text/css"/> <!-- Script --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script type="text/javascript" src="<?= base_url('bootstrap-star-rating/js/star-rating.min.js') ?>"></script>
- Loop on the
$posts
. Display post data with 5 star rating bar.
Adding Rating bar –
- For adding rating bar you need to add
<input >
element with classrating rating-loading
and setdata
attributes.
<input class="rating rating-loading" data-post_id="<?= $post['id'] ?>" data-min="0" data-max="5" data-step="0.5" data-show-clear="false" data-show-caption="false" data-size="md" value="<?= $post['userrating'] ?>" >
I have set the following data attributes –
- data-post_id – Store post id. Use this value in jQuery when the rating is been changed.
- data-min – Set minimum rating value.
- data-max – Set maximum rating value.
- data-step – Set it to 0.5 if you want to also allow half rating otherwise set it to 1 for full rating.
- data-show-clear – I set it to
false
to hide clear rating button. - data-show-caption – I set it to
false
to hide the rating label. - data-size – Rating bar size. I set it to
md
but you can change it toxl
,lg
,sm
, andxs
.
In value
attribute stored the user rating $post['userrating']
.
To display the average rating created <span >
. It value gets updated using jQuery when rating is been changed.
jQuery –
Define change
event on rating
class to detect rating change. Assign selected rating in rating
variable and also read post_id
from the data
attribute.
Send AJAX request to post/updaterating
. Pass {post_id: post_id,rating:rating}
as data. On successful callback update average rating in <span >
.
Completed Code
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>5 Star Rating system with jQuery AJAX in CodeIgniter 4</title> <style type="text/css"> .content{ border: 0px solid black; border-radius: 3px; padding: 5px; margin: 0 auto; width: 50%; } .post{ border-bottom: 1px solid black; padding: 10px; margin-top: 10px; margin-bottom: 10px; } .post:last-child{ border: 0; } .post h2{ font-weight: normal; font-size: 30px; } .post a.link{ text-decoration: none; color: black; } .post-text{ letter-spacing: 1px; font-size: 15px; font-family: serif; color: gray; text-align: justify; } .post-action{ margin-top: 15px; margin-bottom: 15px; } </style> <!-- CSS --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/fontawesome.min.css"> <link rel="stylesheet" href="<?= base_url('bootstrap-star-rating/css/star-rating.css') ?>" media="all" type="text/css"/> <link rel="stylesheet" href="<?= base_url('bootstrap-star-rating/themes/krajee-svg/theme.css') ?>" media="all" type="text/css"/> <!-- Script --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script type="text/javascript" src="<?= base_url('bootstrap-star-rating/js/star-rating.min.js') ?>"></script> </head> <body> <div class="content"> <?php foreach($posts as $post){ ?> <div class="post"> <h2> <a href='<?= $post['link'] ?>' class='link' target='_blank'><?= $post['title'] ?></a> </h2> <div class="post-text"> <?= $post['description'] ?> </div> <div class="post-action"> <!-- Rating Bar --> <input class="rating rating-loading" data-post_id="<?= $post['id'] ?>" data-min="0" data-max="5" data-step="0.5" data-show-clear="false" data-show-caption="false" data-size="md" value="<?= $post['userrating'] ?>" > <div style='clear: both;'></div> <!-- Display average rating --> Average Rating : <span id='avgrating_<?= $post['id'] ?>'><?= $post['avgrating'] ?></span> </div> </div> <?php } ?> </div> <!-- Script --> <script type="text/javascript"> $(document).ready(function(){ // Detect Rating change $('.rating').on( 'change', function () { var rating = $(this).val(); // Selected Rating var post_id = $(this).data('post_id'); // AJAX request $.ajax({ url:"<?=site_url('post/updaterating')?>", data: {post_id: post_id,rating:rating}, dataType: 'json', success: function(response){ // Update average rating $('#avgrating_'+post_id).text(response.avgrating) } }); } ); }); </script> </body> </html>
8. Demo
9. Conclusion
A rating system is a powerful way to enhance user engagement on your website or application. By following the steps outlined in this tutorial, you can easily create a rating system that is both user-friendly and efficient.
In the example, I have fixed the userid for rating. While implementing this on your project replace its value with the logged-in user id.
You can view more CodeIgniter 4 tutorials on this site.
If you found this tutorial helpful then don't forget to share.