Nested Comments System in PHP, MySQL and AJAX

Introduction

This tutorial shows how to create nested comment system in PHP MySQL and AJAX. This nested comment system is also called hierarchical comment system. Here I will use MySQL database and jQuery library.

This threaded or nested comment system in PHP AJAX accepts reply up to maximum of five level depth. If you want to accept unlimited level or customize level of depth then you can modify the source code according to your needs.

You may also read Nested Comments using Codeigniter, AJAX

Prerequisites

PHP 7.0.15 – 7.4.3, Apache 2.4, jQuery 1.9 – 3.6.0, jQuery UI 1.10.3 – 1.12.1, jQuery Block UI 1.x – 2.70.0, CSS, MySQL 8.0.17 – 8.0.22

Note: You cannot use jQuery 1.9 with jQuery Block UI 2.70.0, use jQuery 1.9 with jQuery Block UI 1.x. each() function has been deprecated in PHP 7.2 or later, so I have created my own my_each() function. You don’t need jQuery migrate if you are using jQuery 1.9+. In this example, I have used maximum 3 level of depth for the threaded comments.

https://roytuts.com/database-connection-example-in-php-and-mysql/

Project Directory

Create a project root directory called php-nested-comments under your Apache server’s htdocs directory. I will put all the created files under the project root directory php-nested-comments.

MySQL table

Create MySQL table called comment under roytuts database. You can use phpMyAdmin or SQLyog or MySQL client to run the below SQL and it will automatically create a table for you.

CREATE TABLE `comment` (
	`comment_id` int unsigned COLLATE utf8mb4_unicode_ci NOT NULL AUTO_INCREMENT,
	`comment_text` text COLLATE utf8mb4_unicode_ci NOT NULL,
	`parent_id` int unsigned COLLATE utf8mb4_unicode_ci NOT NULL,
	`comment_date` timestamp COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT CURRENT_TIMESTAMP,
	PRIMARY KEY (`comment_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Retrieving Comments

Retrieve all the comments from database table using the following MySQL query. I have also included two other php files. The config.php file is used for database configuration, which you can find in the above link in the Prerequisites section and helper.php file source code is given below later.

Create a file called comments.php with below source code:

<?php
	require("config.php");
	require("helper.php");

	$sql = 'SELECT * FROM comment';
	$results = dbQuery($sql);
	$items = array();
	while ($row = dbFetchAssoc($results)) {
		$items[] = $row;
	}
	$comments = format_comments($items);
?>

Formatting Comments – helper.php

Below is the helper function which will help us to format the comments for display purpose.

In the below source code I segregate parent child relationship and accordingly I display the comments in hierarchy. At the leaf I display also reply form in order to allow user reply to the comment.

I wrap the nested comments in <ul> and <li> tags.

<?php

	function my_each(&$arr) {
		$key = key($arr);
		$result = ($key === null) ? false : [$key, current($arr), 'key' => $key, 'value' => current($arr)];
		next($arr);
		return $result;
	}

	function format_comments($comments) {
		$html = array();
		$root_id = 0;
		foreach ($comments as $comment)
			$children[$comment['parent_id']][] = $comment;

		// loop will be false if the root has no children (i.e., an empty comment!)
		$loop = !empty($children[$root_id]);

		// initializing $parent as the root
		$parent = $root_id;
		$parent_stack = array();

		// HTML wrapper for the menu (open)
		$html[] = '<ul class="comment">';

		//while ($loop && ( ( $option = each($children[$parent]) ) || ( $parent > $root_id ) )) { //below PHP 7.2
		while ($loop && ( ( $option = my_each($children[$parent]) ) || ( $parent > $root_id ) )) { //PHP 7.2+
			if ($option === false) {
				$parent = array_pop($parent_stack);

				// HTML for comment item containing childrens (close)
				$html[] = str_repeat("\t", ( count($parent_stack) + 1 ) * 2) . '</ul>';
				$html[] = str_repeat("\t", ( count($parent_stack) + 1 ) * 2 - 1) . '</li>';
			} elseif (!empty($children[$option['value']['comment_id']])) {
				$tab = str_repeat("\t", ( count($parent_stack) + 1 ) * 2 - 1);
				$keep_track_depth = count($parent_stack);
				if ($keep_track_depth <= 3) {
					$reply_link = '%1$s%1$s<a href="#" class="reply_button" id="%2$s">reply</a><br/>%1$s';
				} else {
					$reply_link = '';
				}
				//$reply_link = '%1$s%1$s<a href="#" class="reply_button" id="%2$s">reply</a><br/>';
				// HTML for comment item containing childrens (open)
				$html[] = sprintf(
						'%1$s<li id="li_comment_%2$s" data-depth-level="' . $keep_track_depth . '">' .
						'%1$s%1$s<div><span class="comment_date">%4$s</span></div>' .
						'%1$s%1$s<div style="margin-top:4px;">%3$s</div>' .
						$reply_link . '</li>', $tab, // %1$s = tabulation
						$option['value']['comment_id'], //%2$s id
						$option['value']['comment_text'], // %3$s = comment
						$option['value']['comment_date'] // %4$s = comment created_date
				);
				//$check_status = "";
				$html[] = $tab . "\t" . '<ul class="comment">';

				array_push($parent_stack, $option['value']['parent_id']);
				$parent = $option['value']['comment_id'];
			} else {
				$keep_track_depth = count($parent_stack);
				if ($keep_track_depth <= 3) {
					$reply_link = '%1$s%1$s<a href="#" class="reply_button" id="%2$s">reply</a><br/>%1$s';
				} else {
					$reply_link = '';
				}

				//$reply_link = '%1$s%1$s<a href="#" class="reply_button" id="%2$s">reply</a><br/>%1$s</li>';
				// HTML for comment item with no children (aka "leaf")
				$html[] = sprintf(
						'%1$s<li id="li_comment_%2$s" data-depth-level="' . $keep_track_depth . '">' .
						'%1$s%1$s<div><span class="comment_date">%4$s</span></div>' .
						'%1$s%1$s<div style="margin-top:4px;">%3$s</div>' .
						$reply_link . '</li>', str_repeat("\t", ( count($parent_stack) + 1 ) * 2 - 1), // %1$s = tabulation
						$option['value']['comment_id'], //%2$s id
						$option['value']['comment_text'], // %3$s = comment
						$option['value']['comment_date'] // %4$s = comment created_date
				);
			}
		}

		// HTML wrapper for the comment (close)
		$html[] = '</ul>';
		return implode("\r\n", $html);
	}
?>

Displaying Nested Comments – index.php

Code for displaying the nested comments in index.php page. The index.php file contains static resources, such as, JavaScript and CSS files. So make sure you include these static resources while you are copying the files. You will also find a download link at the bottom of the tutorial to download these static resource files.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Nested or hierarchical comment system in PHP, AJAX, Jquery</title>
        <link rel="stylesheet" href="comments.css">
        <!--<script src="jquery-1.9.1.min.js"></script>-->
		<script src="https://code.jquery.com/jquery-3.6.0.min.js" crossorigin="anonymous"></script>
        <!--<script src="jquery-ui-1.10.3-custom.min.js"></script>-->
		<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" crossorigin="anonymous"></script>
        <!--<script src="jquery-migrate-1.2.1.js"></script>-->
        <!--<script src="jquery.blockUI.js"></script>-->
		<script src="jquery.blockUI-2.70.0.js"></script>
        <script src="comments_blog.js"></script>
    </head>
    <body>
        <?php
        require("comments.php");
        ?>
        <div style="width: 600px;">
            <div id="comment_wrapper">
                <div id="comment_form_wrapper">
                    <div id="comment_resp"></div>
                    <h4>Please Leave a Reply<a href="javascript:void(0);" id="cancel-comment-reply-link">Cancel Reply</a></h4>
                    <form id="comment_form" name="comment_form" action="" method="post">
                        <div>
                            Comment<textarea name="comment_text" id="comment_text" rows="6"></textarea>
                        </div>
                        <div>
                            <input type="hidden" name="reply_id" id="reply_id" value=""/>
                            <input type="hidden" name="depth_level" id="depth_level" value=""/>
                            <input type="submit" name="comment_submit" id="comment_submit" value="Post Comment" class="button"/>
                        </div>
                    </form>
                </div>
                <?php
                echo $comments;
                ?>
            </div>
        </div>
    </body>
</html>

Validating and Adding comments – jQuery and AJAX

Jquery and AJAX code while comment is posted by a user.

This jquery code validates inputs given by users on comment form and accordingly appends nested comment to the parent and display without page refresh.

Create a JavaScript (js) file called comments_blog.js under project root directory with below code:

$(function() {
    $("#cancel-comment-reply-link").hide();
    //$(".reply_button").live('click', function(event) { //jQuery upto 1.9
	$(".reply_button").on('click', function(event) { //jQuery 1.9+
        event.preventDefault();
        var id = $(this).attr("id");
        //if ($("#li_comment_" + id).find('ul').size() > 0) { //jQuery upto 1.9
		if ($("#li_comment_" + id).find('ul').length > 0) { //jQuery 1.9+
            $("#li_comment_" + id + " ul:first").prepend($("#comment_form_wrapper"));
        } else {
            $("#li_comment_" + id).append($("#comment_form_wrapper"));
        }
        var depth_level = $('#li_comment_' + id).data('depth-level');
        $("#reply_id").attr("value", id);
        $("#depth_level").attr("value", depth_level);
        $("#cancel-comment-reply-link").show();
    });

    $("#cancel-comment-reply-link").bind("click", function(event) {
        event.preventDefault();
        $("#reply_id").attr("value", "");
        $("#comment_wrapper").prepend($("#comment_form_wrapper"));
        $(this).hide();
    });

    $("#comment_form").bind("submit", function(event) {
        event.preventDefault();
        if ($("#comment_text").val() == "") {
            alert("Please enter your comment");
            return false;
        }
        $.ajax({
            type: "POST",
            //async: false,
            url: "add_comment.php",
            data: $('#comment_form').serialize(),
            dataType: "html",
            cache: false,
            beforeSend: function() {
                $('#comment_wrapper').block({
                    message: 'Please wait....',
                    css: {
                        border: 'none',
                        padding: '15px',
                        backgroundColor: '#ccc',
                        '-webkit-border-radius': '10px',
                        '-moz-border-radius': '10px'
                    },
                    overlayCSS: {
                        backgroundColor: '#ffe'
                    }
                });
            },
            success: function(comment) {
                var reply_id = $("#reply_id").val();
                if (reply_id == "") {
                    $("#comment_wrapper ul:first").prepend(comment);
                }
                else {
					//if ($("#li_comment_" + reply_id).find('ul').size() > 0) { //jQuery upto 1.9
                    if ($("#li_comment_" + reply_id).find('ul').length > 0) { //jQuery 1.9+
                        $("#li_comment_" + reply_id + " ul:first").prepend(comment);
                    }
                    else {
                        $("#li_comment_" + reply_id).append('<ul class="comment">' + comment + '</ul>');
                    }
                }
                $("#comment_text").attr("value", "");
                $("#reply_id").attr("value", "");
                $("#cancel-comment-reply-link").hide();
                $("#comment_wrapper").prepend($("#comment_form_wrapper"));
                $('#comment_wrapper').unblock();
            },
            error: function(jqXHR, textStatus, errorThrown) {
                //console.log(textStatus, errorThrown);
                alert(textStatus + " " + errorThrown);
            }
        });
    });
});

Adding Comments

Create a file called add_comment.php under the project root directory with the below code. This is server side code that saves comments in the database.

<?php

	require("config.php");
	require("helper.php");

	if (isset($_POST)) {
		$parent_id = ($_POST['reply_id'] == NULL || $_POST['reply_id'] == '') ? 0 : $_POST['reply_id'];
		$comment_text = $_POST['comment_text'];
		$depth_level = ($_POST['depth_level'] == NULL || $_POST['depth_level'] == '') ? 0 : $_POST['depth_level'];;
		$sql = "INSERT INTO comment(comment_text, parent_id) VALUES('$comment_text', $parent_id)";
		$query = dbQuery($sql);
		$inserted_id = dbInsertId();
		$sql = "SELECT * FROM comment WHERE comment_id=" . $inserted_id;
		$results = dbQuery($sql);
		if ($results) {
			while ($row = dbFetchAssoc($results)) {
				if ($depth_level < 3) {
					$reply_link = "<a href=\"#\" class=\"reply_button\" id=\"{$row['comment_id']}\">reply</a><br/>";
				} else {
					$reply_link = '';
				}
				$depth = $depth_level + 1;
				echo "<li id=\"li_comment_{$row['comment_id']}\" data-depth-level=\"{$depth}\">" .
				"<span class=\"comment_date\">{$row['comment_date']}</span></div>" .
				"<div style=\"margin-top:4px;\">{$row['comment_text']}</div>" .
				$reply_link . "</li>";
			}
			echo '<div class="success">Comment successfully posted</div>';
		} else {
			echo '<div class="error">Error in adding comment</div>';
		}
	} else {
		echo '<div class="error">Please enter required fields</div>';
	}
?>

Adding Styles

Add some styles to the nested comment system. Create comments.css file under the project root directory.

#comment_wrapper {
	width:100%;
	font-family:serif,sans-serif,cursive;
}

#comment_form_wrapper {
	margin: 12px 12px 12px 12px;
	padding: 12px 0px 12px 12px; /* Note 0px padding right */
	background-color: #ebefee;
	border: thin dotted #39C;
}

#comment_name{
	padding: 4px 2px 4px 5px;
	margin: 3px 0 3px 13px;
}

#comment_email{
	padding: 4px 2px 4px 5px;
	margin: 3px 0 3px 15px;
}

#comment_web{
	padding: 4px 2px 4px 5px;
	margin: 3px;
}

#comment_form textarea {
	width: 93.4%;
	background: white;
	/*border: 4px solid #EEE;*/
	border: 1px solid #eee;
	/* -moz-border-radius: 5px;
	border-radius: 5px;*/
	padding: 10px;
	margin-left: 5px;
	font-family:serif,sans-serif,cursive;
	font-size:14px;
}

#comment_resp_err{
	color: red;
	font-size: 13px;
}

ul.comment {
width: 100%;
	/* margin: 12px 12px 12px 0px;
	padding: 3px 3px 3px 3px;*/
}

ul.comment li {
	margin: 12px 12px 12px 12px;
	padding: 12px 0px 12px 12px; /* Note 0px padding right */
	list-style: none; /* no glyphs before a list item */
	background-color: #ebefee;
	border: thin dotted #39C;
}

ul.comment li span.commenter {
	font-weight:bold;
	color:#369;
}

ul.comment li span.comment_date {
	color:#666;
}

#comment_wrapper .button,
#comment_wrapper .reply_button {
	background: none repeat scroll 0 0 #5394A8;
	color: #FFFFFF;
	float: right;
	font-size: 10px;
	font-weight: bold;
	margin: -10px 5px ;
	padding: 3px 10px;
	text-transform: uppercase;
	text-decoration: none;
	cursor: pointer;
	border: 1px solid #369;
}

#comment_wrapper #comment_submit {
	float:none;
	margin: 0px 5px ;
}

#comment_wrapper .button:hover,
#comment_wrapper .reply_button:hover {
	background: none repeat scroll 0 0 #069;
	text-decoration: underline;
}

#cancel-comment-reply-link {
	color: #666;
	margin-left: 10px;
	margin-right:10px;
	text-decoration: none;
	font-size: 10px;
	font-weight: normal;
	float:right;
	text-transform: uppercase;
}

#cancel-comment-reply-link:hover{
	text-decoration: underline;
}

Testing the Application

When you start the Apache server, MySQL server and access the URL http://localhost/nested_comments/ in browser then you will see the following output.

You can add comment and reply to the comment.

nested comment system in php mysql ajax

Hope you got an idea on nested comment system in php mysql ajax.

Source Code

Download

2 thoughts on “Nested Comments System in PHP, MySQL and AJAX

Leave a Reply

Your email address will not be published. Required fields are marked *