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 we 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

Knowledge on PHP, PHP 5.5, Apache 2.2

Creating project directory

Create a project root directory called nested_comments under your Apache server’s htdocs directory. We will put all the created files under the project root directory nested_comments.

Creating MySQL table

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

CREATE TABLE `comment` (
	`comment_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
	`comment_text` text NOT NULL,
	`parent_id` int(10) unsigned NOT NULL,
	`ip_address` varchar(20) NOT NULL,
	`email_address` varchar(100) NOT NULL,
	`web_address` varchar(255) DEFAULT NULL,
	`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
	`created_by` varchar(45) NOT NULL,
	PRIMARY KEY (`comment_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

Before proceeding next paragraph, please look at the https://www.roytuts.com/database-connection-example-in-php-and-mysql/

Retrieving Comments

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

<?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 we segregate parent child relationship and accordingly we display the comments in hierarchy. At the leaf we display also reply form in order to allow user reply to the comment.

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

<?php

	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 ) )) {
			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 = '';
				}
				$name = strlen($option['value']['created_by']) ? $option['value']['created_by'] : 'anonymous_user';
				//$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="commenter">%3$s</span> ' .
						'<span class="comment_date">%5$s</span></div>' .
						'%1$s%1$s<div style="margin-top:4px;">%4$s</div>' .
						$reply_link . '</li>', $tab, // %1$s = tabulation
						$option['value']['comment_id'], //%2$s id
						$name . ' says', // %3$s = commenter
						$option['value']['comment_text'], // %4$s = comment
						', ' . $option['value']['created_date'] // %5$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 {
				$name = strlen($option['value']['created_by']) ? $option['value']['created_by'] : 'anonymous user';
				$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="commenter">%3$s</span> ' .
						'<span class="comment_date">%5$s</span></div>' .
						'%1$s%1$s<div style="margin-top:4px;">%4$s</div>' .
						$reply_link . '</li>', str_repeat("\t", ( count($parent_stack) + 1 ) * 2 - 1), // %1$s = tabulation
						$option['value']['comment_id'], //%2$s id
						$name . ' says', // %3$s = commenter
						$option['value']['comment_text'], // %4$s = comment
						', ' . $option['value']['created_date'] // %5$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="jquery-ui-1.10.3-custom.min.js"></script>
        <script src="jquery-migrate-1.2.1.js"></script>
        <script src="jquery.blockUI.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>
                            Name<input type="text" name="comment_name" id="comment_name" size="54"/>
                        </div>
                        <div>
                            Email<input type="text" name="comment_email" id="comment_email" size="54"/>
                        </div>
                        <div>
                            Website<input type="text" name="comment_web" id="comment_web" size="54"/>
                        </div>
                        <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) {
        event.preventDefault();
        var id = $(this).attr("id");
        if ($("#li_comment_" + id).find('ul').size() > 0) {
            $("#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_name").val() == "")
        {
            alert("Please enter your name");
            return false;
        }
        if ($("#comment_email").val() == "")
        {
            alert("Please enter your email");
            return false;
        }
        var regex_email = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
        if (regex_email.test($("#comment_email").val()) == false) {
            alert('Invalid Email Address');
            return false;
        }
        var regex_web = /^((ftp|https?):\/\/)?(www\.)?[a-z0-9\-\.]{3,}\.[a-z]{2,3}$/;
        if ($("#comment_web").val() != "" && regex_web.test($("#comment_web").val()) == false) {
            alert('Invalid Website Address');
            return false;
        }
        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) {
                        $("#li_comment_" + reply_id + " ul:first").prepend(comment);
                    }
                    else {
                        $("#li_comment_" + reply_id).append('<ul class="comment">' + comment + '</ul>');
                    }
                }
                $("#comment_name").attr("value", "");
                $("#comment_email").attr("value", "");
                $("#comment_web").attr("value", "");
                $("#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'];
		$email = $_POST['comment_email'];
		$name = $_POST['comment_name'];
		$web = $_POST['comment_web'];
		$comment_text = $_POST['comment_text'];
		$depth_level = $_POST['depth_level'];
		$sql = "INSERT INTO comment(comment_text, parent_id, ip_address, email_address, web_address, created_by) VALUES('$comment_text', $parent_id, '" . $_SERVER['REMOTE_ADDR'] . "', '$email', '$web', '$name')";
		$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;
				$name = strlen($row['created_by']) ? $row['created_by'] : 'anonymous user';
				echo "<li id=\"li_comment_{$row['comment_id']}\" data-depth-level=\"{$depth}\">" .
				"<div><span class=\"commenter\">{$name} says</span> <span class=\"comment_date\">,  {$row['created_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;
}

Downloading Static Resources

The jQuery libraries are available at jQuery site.

You can also download here in order to complete the nested comment system in PHP MYSQL and AJAX.

<script src="jquery-1.9.1.min.js"></script> 
<script src="jquery-ui-1.10.3-custom.min.js"></script> 
<script src="jquery-migrate-1.2.1.js"></script> 
<script src="jquery.blockUI.js"></script>

Testing the Application

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

You can add comment and reply to the comment.

nested comment system in php ajax

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

Thanks for your reading.

Tags:

I am a professional Web developer, Enterprise Application developer, Software Engineer and Blogger. Connect me on JEE Tutorials | TwitterFacebook Google PlusLinkedin | Reddit

31 thoughts on “Nested comment system in PHP, MySQL and AJAX

  1. Hello i’m a novice in php ,please what should be the name of the data base you are using?thanks for your answers

  2. I think this code is not tested one. many places few thing is missing.
    e.g:
    Index .php:

    action file name is missing. it should be:

    In helper.php
    $reply_link = ‘%1$s%1$sreply%1$s’;

    can any one tell me from where $s is coming.
    why clicking on reply in not working???

  3. In helper.php
    $reply_link = ‘%1$s%1$sreply%1$s’;

    here what is $s , from where it is coming.
    why clicking reply is not working?
    please send solutions.

    1. you have not read the tutorial thoroughly, when I tried, I saw your page does not include jquery stuffs. I had already said in previous reply that you should read it carefully. Please make sure you have done everything before you post your comment. Have you downloaded the jquery attached files from the tutorial ?

      1. Great work Soumitra. it is working fine. I have done mistake and copied jquery in js folder. now I have moved everything in parent folder and working fine.

        I want to remove threaded reply so comment few line in healer.php, but still showing threaded:
        // if ($keep_track_depth <= 3) {
        // $reply_link = '%1$s%1$sreply%1$s’;
        // } else {
        $reply_link = ”;
        // }
        any specific changes is required?

  4. Hi,
    Thanks for this great tutorial!
    I would like to know how to add the href link of the user’s website on his nickname and to add a captcha system in French if possible?
    (Sorry for my English I am French)
    Thank you 🙂

  5. Hello there, Thank you so much for this tutorial. it worked wonders for me.
    the only question I have, or should I say the help I need, is how to change the date format that the script outputs?
    Currently the time stamp display (yyyy-mm-dd) I would like it to appear (dd-mm-yyyy)

      1. Thank you Soumitra….for your prompt reply……very kind of you.
        may I kindly point to you that I am just an amateur starting on codding.
        The question once again will be where would I put that line of code……I mean in which .php file and after which line of code?

        Sorry for inconvenience caused to you. and Merry Xmas.

        1. you see the lines $option[‘value’][‘comment_text’], // %4$s = comment
          ‘, ‘ . $option[‘value’][‘created_date’] // %5$s = comment created_date

          so replace $option[‘value’][‘created_date’] by date(‘d-m-Y’, strtotime($option[‘value’][‘created_date’]));

          1. HO ho ho…….Santa and Soumitra hava made my day!
            Thank you immensely Soumitra, the date displays as i wanted them to, and the whole thing works like a charm now,
            God bless.

  6. Hi ,

    I am facing problem while inserting values in database. I could submit the values from form without error , but values are not inserting into table.

    How to solve this ?

    What could possible went wrong ?

    FYI I am new to PHP

  7. Hi.. actually no comments is displayed below the comment box.. and the comment i have posted is not stored in the database.. is there anything that i have done wrong .. ? I have created all files mentioned above.. including the js files.. but im not getting it.. please anyone give a reply .. i will be grateful to you..

  8. This is a brilliant threaded comment system. I really liked you tutorial.
    I am new in this field. I tested it – and working absolutely fine.

    I have just one -WISH.

    How can I make the comments “show” in left side without giving a gap of around 50 px.
    It could be ( without keeping the left GAP ) B replys to A.
    How can I make the Commented Box Smaller with Reply below his/her comment ( removing it from right side )
    In this IMAGE url, I tried to explain my Wish : http://www.woon.16mb.com/MyWish-SnapShot.jpg

    Can you please help me to do it ?

  9. Warning: mysqli_connect(): (HY000/1049): Unknown database ‘cdcol’ in D:\XAMP\htdocs\new\database.php on line 3
    MySQL connect failed. Unknown database ‘cdcol’

    can u please tell me thi type of error how i resolve it
    cuz this type of data base is not use in any file ?

  10. Deprecated: The each() function is deprecated. This message will be suppressed on further calls in helper.php file with php 7.2 and higher.

Leave a Reply

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