Comments and replies – PHP & MySQL

Demo

In this article, we will teach you how you can implement a comments and replies feature in your website. If you are running a blog, or have a portfolio or a ticket booking website. The list goes on, you want to have an option to gather the opinions, reviews and suggestions from your users. For example, I upload computer programming related educational content. So I want to know if my users are following my articles ? Do they understand them well ? Which articles are difficult to understand, which are good, which can be improved etc.

We are going to create a table which can hold all the comments of any post, a post can be anything, blog post/product/movie/celebrity etc. There will be a single table which can save the comments as well as each comments replies. User can add a comment to a post and can also reply to a comment. When you add a reply to a comment, then an email is also sent to the person to whom you have replied.

Create comments table

Run the following query in your phpMyAdmin in your database to create a table named “posts” and “comments“:

CREATE TABLE IF NOT EXISTS posts(
    id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
    title TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS comments (
    id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
    name TEXT NOT NULL,
    email TEXT NOT NULL,
    comment TEXT NOT NULL,
    post_id INTEGER NOT NULL,
    created_at DATETIME NOT NULL,
    reply_of INTEGER NOT NULL,
    CONSTRAINT fk_comments_post_id FOREIGN KEY (post_id) REFERENCES posts(id)
)

This will create a table named “comments” in your database if not exists. It will have unique ID, name, email and comment. post_id will be the ID of post where comment is added because user can comment on each post. created_at will be the date and time when the comment is added. reply_of will be the value of comment whom user has replied. When you add a comment directly on the post then it’s value will be 0, if you add a reply on any comment then it’s value will be the ID of comment whom you are replying. That is why it is not added as a foreign key because it’s value can be null. Finally, we are adding a foreign key constraint to our post_id field because it will be the ID of post whom comment is added.

Add comment form

Our comment form will have name, email, comment, a hidden post ID field and a submit button. You can design this as per your website color scheme. The hidden input field will be the post unique ID, make sure to enter your dynamic post ID in $post_id variable.

<?php
    // make sure you have a post ID 1 in your "posts table"
    $post_id = 1;
?>

<form action="index.php" method="post">

    <input type="hidden" name="post_id" value="<?php echo $post_id; ?>" required>

    <p>
        <label>Your name</label>
        <input type="text" name="name" required>
    </p>

    <p>
        <label>Your email address</label>
        <input type="email" name="email" required>
    </p>

    <p>
        <label>Comment</label>
        <textarea name="comment" required></textarea>
    </p>

    <p>
        <input type="submit" value="Add Comment" name="post_comment">
    </p>
</form>

Form method is POST, action will be the name of file where data will be sent to process. We are sending to index.php but you can change the name of file as per your need. Then we are creating all fields, make sure to give name attribute to all the fields including the submit button. It will help the server to check if the request is made from specific form and to get the values from these input fields.

Save comment in database

Following code will check if the form is submitted, validate all fields from SQL injection and save in database.

<?php

// index.php

$conn = mysqli_connect("localhost:8889", "root", "root", "yourdbname");

if (isset($_POST["post_comment"]))
{
    $name = mysqli_real_escape_string($conn, $_POST["name"]);
    $email = mysqli_real_escape_string($conn, $_POST["email"]);
    $comment = mysqli_real_escape_string($conn, $_POST["comment"]);
    $post_id = mysqli_real_escape_string($conn, $_POST["post_id"]);
    $reply_of = 0;

    mysqli_query($conn, "INSERT INTO comments(name, email, comment, post_id, created_at, reply_of) VALUES ('" . $name . "', '" . $email . "', '" . $comment . "', '" . $post_id . "', NOW(), '" . $reply_of . "')");
    echo "<p>Comment has been posted.</p>";
}

?>

First we are connected with database, you can change the user, password and database name as per your project. Then we are checking if the request is made from add comment’s form. Then we are validating all fields to prevent from SQL injection using PHP built-in mysqli_real_escape_string function. We are setting the reply_of value to 0 because as mentioned earlier, this field will have 0 value if the comment is added directly on the post. It will have value greater than zero only for replies.

Then we are running the query to insert the data in comments table. To set the value in created_at field we are using MySQL built-in NOW() function. This will return the current date and time in proper format Y-m-d H:i:s. Finally a success message is displayed that the comment has been posted.

Show all comments

Now we need to fetch all comments from database in following format:

[
    {
        "id": 1,
        "name": "ali ahmad",
        "email": "aliahmad@gmail.com",
        "comment": "nice post",
        "post_id": 3,
        "created_at": "2020-09-16 20:09:44",
        "reply_of": 0,
        "replies": [
            {
                "id": 2,
                "name": "ali ahmad",
                "email": "aliahmad@gmail.com",
                "comment": "thanks",
                "created_at": "2020-09-16 20:09:44",
                "post_id": 3,
                "reply_of": 1
            }
        ]
    }
]

Pay close attention to this format. We have an array of comments, each object has a unique ID, name, email, comment, post_id, created_at, reply_of and replies. Now if you explore the “replies” array you will see that it has the same object as comment’s except for the replies array. The reply_of value in replies array is same as the ID of comment. You will understand it better once we finish the replies feature.

Show all comments

The following code will generate the data structure as above, you can put that code where you want to display all comments of a post:

<?php

// connect with database
$conn = mysqli_connect("localhost:8889", "root", "root", "tutorials");

// get all comments of post
$result = mysqli_query($conn, "SELECT * FROM comments WHERE post_id = " . $post_id);

// save all records from database in an array
$comments = array();
while ($row = mysqli_fetch_object($result))
{
    array_push($comments, $row);
}

// loop through each comment
foreach ($comments as $comment_key => $comment)
{
    // initialize replies array for each comment
    $replies = array();

    // check if it is a comment to post, not a reply to comment
    if ($comment->reply_of == 0)
    {
        // loop through all comments again
        foreach ($comments as $reply_key => $reply)
        {
            // check if comment is a reply
            if ($reply->reply_of == $comment->id)
            {
                // add in replies array
                array_push($replies, $reply);

                // remove from comments array
                unset($comments[$reply_key]);
            }
        }
    }

    // assign replies to comments object
    $comment->replies = $replies;
}

?>

Do not forgot to replace the post_id in the SQL query. Rest of the code is self-explanatory. If you write the following command after the top foreach loop then it will show the same data structure as above:

print_r($comments);

But right now the replies array will be empty because right now we added comment to a post but we havn’t added any reply to a comment. Time to display all these comments, again you can design as per your desire but for the sake of simplicity we are creating basic layout.

<ul class="comments">
    <?php foreach ($comments as $comment): ?>
        <li>
            <p>
                <?php echo $comment->name; ?>
            </p>

            <p>
                <?php echo $comment->comment; ?>
            </p>

            <p>
                <?php echo date("F d, Y h:i a", strtotime($comment->created_at)); ?>
            </p>

            <div data-id="<?php echo $comment->id; ?>" onclick="showReplyForm(this);">Reply</div>

            <form action="index.php" method="post" id="form-<?php echo $comment->id; ?>" style="display: none;">
                
                <input type="hidden" name="reply_of" value="<?php echo $comment->id; ?>" required>
                <input type="hidden" name="post_id" value="<?php echo $post_id; ?>" required>

                <p>
                    <label>Your name</label>
                    <input type="text" name="name" required>
                </p>

                <p>
                    <label>Your email address</label>
                    <input type="email" name="email" required>
                </p>

                <p>
                    <label>Comment</label>
                    <textarea name="comment" required></textarea>
                </p>

                <p>
                    <input type="submit" value="Reply" name="do_reply">
                </p>
            </form>
        </li>
    <?php endforeach; ?>
</ul>

This will display all comments in an un-ordered list in such a way that you can see the name and comment of person, the date and time when the comment was posted, a button to reply. You see that we also created a form tag but immediately hides it using CSS style attribute. This form will only be visible when you click on the “Reply” button. You can see the reply button has a data-id attribute which has the value of comment ID, it is the same as the ID attribute of form. This will help us show the form only for that comment.

Hidden input fields

The form has 2 hidden input fields, reply_of means the ID of comment whom I am replying and second will be the post ID. Other fields are same as previous comment form (name, email, comment). Make sure your reply button inside the form has name attribute different to the comment’s form from previous section. For example, previously we had given name=”post_comment” but in-case of reply, we are setting name=”do_reply”. This will help us run separate code for comments and replies because in-case of reply, we have to send an email to the commenter so that he can know that someone has replied to his comment.

Add Reply to a comment

In the previous section, you can see that we added an onclick event listener to the reply button. Now we need to create it’s function in Javascript:

<script>

function showReplyForm(self) {
    var commentId = self.getAttribute("data-id");
    if (document.getElementById("form-" + commentId).style.display == "") {
        document.getElementById("form-" + commentId).style.display = "none";
    } else {
        document.getElementById("form-" + commentId).style.display = "";
    }
}

</script>

First it will get the comment ID using data-id attribute of reply button. Then it will check if the form is visible, if visible then it will hide the form and if hidden then it will make it visible. This will work like a toggle.

Now the form is displayed, time to process it. When the reply form is submitted, we will save it’s record in database as we are doing with normal comment but we will set it’s reply_of value to the ID of comment. Moreover, we will send an email to the person whom you are replying.

<?php

if (isset($_POST["do_reply"]))
{
    $name = mysqli_real_escape_string($conn, $_POST["name"]);
    $email = mysqli_real_escape_string($conn, $_POST["email"]);
    $comment = mysqli_real_escape_string($conn, $_POST["comment"]);
    $post_id = mysqli_real_escape_string($conn, $_POST["post_id"]);
    $reply_of = mysqli_real_escape_string($conn, $_POST["reply_of"]);

    $result = mysqli_query($conn, "SELECT * FROM comments WHERE id = " . $reply_of);
    if (mysqli_num_rows($result) > 0)
    {
        $row = mysqli_fetch_object($result);

        // sending email
        $headers = 'From: YourWebsite <no-reply@yourwebsite.com>' . "\r\n";
        $headers .= 'MIME-Version: 1.0' . "\r\n";
        $headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
        
        $subject = "Reply on your comment";

        $body = "<h1>Reply from:</h1>";
        $body .= "<p>Name: " . $name . "</p>";
        $body .= "<p>Email: " . $email . "</p>";
        $body .= "<p>Reply: " . $comment . "</p>";

        mail($row->email, $subject, $body, $headers);
    }

    mysqli_query($conn, "INSERT INTO comments(name, email, comment, post_id, created_at, reply_of) VALUES ('" . $name . "', '" . $email . "', '" . $comment . "', '" . $post_id . "', NOW(), '" . $reply_of . "')");
    echo "<p>Reply has been posted.</p>";
}

?>

First we are checking if the request received is for adding reply. Then we are protecting all input fields from SQL injection including the hidden fields. Then we are getting the comment’s row whom reply is being adding. By fetching the comment’s row from database, we can get the email address of the commenter and thus can easily send an email using PHP built-in mail() function. If you are working on localhost then mail() function might not work, in this case you are use PHPMailer. A library that allows you to send emails even from localhost, learn how to use this.

Finally we are running the INSERT query to save the record in database. And a success message is displayed as well. Now you will see that your $comments array has a nested replies array as well. Time to display the replies for each comment.

Show replies of each comment

Inside the $comments array foreach loop, after the form tag is ended, paste the following code to show all replies:

<ul class="comments reply">
    <?php foreach ($comment->replies as $reply): ?>
        <li>
            <p>
                <?php echo $reply->name; ?>
            </p>

            <p>
                <?php echo $reply->comment; ?>
            </p>

            <p>
                <?php echo date("F d, Y h:i a", strtotime($reply->created_at)); ?>
            </p>

            <div onclick="showReplyForReplyForm(this);" data-name="<?php echo $reply->name; ?>" data-id="<?php echo $comment->id; ?>"> Reply</div>
        </li>
    <?php endforeach; ?>
</ul>

This will show all replies for each comment in same format as we are displaying top-level comments. It will again have a reply button but this will not be a “reply of a reply“, because it might go on to an infinite level and will not look good in design either. That is why we will again show a form and his reply will be appended in the list.

Show reply form

The reply button has data-id and data-name attributes that represents the ID of comment and name of person whom you are replying. This is useful if you want to show the name of person with @ sign in the reply textarea field.

You will notice that the function called in onclick event is different than the previous, this is because we are going to show @ sign in reply textarea field. Create the following function in your javascript:

<script>

function showReplyForReplyForm(self) {
    var commentId = self.getAttribute("data-id");
    var name = self.getAttribute("data-name");

    if (document.getElementById("form-" + commentId).style.display == "") {
        document.getElementById("form-" + commentId).style.display = "none";
    } else {
        document.getElementById("form-" + commentId).style.display = "";
    }

    document.querySelector("#form-" + commentId + " textarea[name=comment]").value = "@" + name;
    document.getElementById("form-" + commentId).scrollIntoView();
}

</script>

First we are getting comment ID and name of person using data attributes. Then we are displaying the form if hidden and hiding the form if already visible. Then we are prepending @ sign in textarea field and writing commenter’s name in it. The last line scrolls the page to the form, this helps because the form is created at the top of each comment and if you try to reply to a 1000th person then it will automatically scroll to the form tag.

That’s how you can create a comments and replies section in your PHP website.

The template used in the demo is a premium template named “porto”. So it’s source code is not included in the code below. But all the functional code is in there.

[wpdm_package id=’1243′]

19 Replies to “Comments and replies – PHP & MySQL”

  1. Error 523 [url=https://support.cloudflare.com/hc/en-us/articles/115003011431-Troubleshooting-Cloudflare-5XX-errors#523error]origin is unreachable[/url]

  2. This is a good project, tried it and everything works great, but when I wanted to connect it to my blog project, it shows me an error
    Notice: Undefined index: id in C:\xampp\htdocs\blog\single.php on line 14
    when I want to record a comment. Instead of ( $post_id = 1; ) I changed it to ( $post_id=intval($_GET[‘id’]); ) and there it finds an error in line 14 and shows me the number ( echo $post_id ) but it won’t insert .
    What should I change instead of this ( $post_id = 1; ) how to make a variable ? Thank you very much.

  3. May I simply say what a relief to find someone that really knows what they’re discussing over
    the internet. You definitely understand how to bring a problem to light
    and make it important. A lot more people must check this out and understand this side of
    your story. I was surprised you are not more popular since you surely possess the gift.

  4. Hi there just wanted to give you a quick heads up and let
    you know a few of the pictures aren’t loading correctly.
    I’m not sure why but I think its a linking issue. I’ve tried it
    in two different web browsers and both show the same results.

  5. Generally I don’t learn post on blogs, however I wish to say that this write-up very forced me to
    take a look at and do so! Your writing taste has been amazed me.
    Thank you, very nice post.

Comments are closed.