PHP class for threaded comments

Filed under: PHP | 46 Comments

I had to set up a simple threaded comments system in a PHP project yesterday and didn’t find any standout options. The presentation requirements were pretty simple and my only real code requirement was to use a single SQL query (using a self-referencing parent_id foreign key field) to fetch the comments. I didn’t want to mess with a full-on tree either (that would be overkill as I’d never need to walk up the tree, just needed to show all the comments for a single object).

What I found was either laughably pathetic or various WordPress plug-ins that were even more hard to parse than the main WordPress codebase.

I ended up writing a simple class that accomplishes it without getting too fancy. The main meat of the class is a recursively called method (Threaded_comment::print_parent()) that prints a comment and then its children. The class as I have shared below will need modification depending on how you use it, for my use I modified it to be a CodeIgniter library and output comments using a View file. Simple enough. It’s set-up as an example, not a full-on third party library. I am sure I’ll have to hack on it more, but hopefully it helps someone.

Update: Note that this code is not running the comments you see below–that’s powered by WordPress’ native comment feature (which also does threading). The code below is if you want to setup a threaded comment system in your own PHP application. You could certainly attempt to run it on a WordPress install, but there would be no point because the feature is already seamlessly built-in.

class Threaded_comments
{
    
    public $parents  = array();
    public $children = array();

    /**
     * @param array $comments 
     */
    function __construct($comments)
    {
        foreach ($comments as $comment)
        {
            if ($comment['parent_id'] === NULL)
            {
                $this->parents[$comment['id']][] = $comment;
            }
            else
            {
                $this->children[$comment['parent_id']][] = $comment;
            }
        }        
    }
   
    /**
     * @param array $comment
     * @param int $depth 
     */
    private function format_comment($comment, $depth)
    {   
        for ($depth; $depth > 0; $depth--)
        {
            echo "\t";
        }
        
        echo $comment['text'];
        echo "\n";
    }
    
    /**
     * @param array $comment
     * @param int $depth 
     */ 
    private function print_parent($comment, $depth = 0)
    {   
        foreach ($comment as $c)
        {
            $this->format_comment($c, $depth);

            if (isset($this->children[$c['id']]))
            {
                $this->print_parent($this->children[$c['id']], $depth + 1);
            }
        }
    }

    public function print_comments()
    {
        foreach ($this->parents as $c)
        {
            $this->print_parent($c);
        }
    }
  
}

Here’s the example usage with the data provided as an array. Remember that if your data is in another format you’ll have to modify the class.


$comments = array(  array('id'=>1, 'parent_id'=>NULL,   'text'=>'Parent'),
                    array('id'=>2, 'parent_id'=>1,      'text'=>'Child'),
                    array('id'=>3, 'parent_id'=>2,      'text'=>'Child Third level'),
                    array('id'=>4, 'parent_id'=>NULL,   'text'=>'Second Parent'),
                    array('id'=>5, 'parent_id'=>4,   'text'=>'Second Child')
                );

$threaded_comments = new Threaded_comments($comments);

$threaded_comments->print_comments();

Example Output:

Parent
	Child
		Child Third level
Second Parent
	Second Child

Read the latest posts

46 Responses to “PHP class for threaded comments”

  1. Kevin Day says:

    Thanks, that’s a great easy-to-follow example. I just plugged it into my application in about 10 minutes. I was considering a whole tree-based approach like you mentioned above but this is perfect.

  2. Vernon says:

    Thanks a lot. I’m actually working on a CodeIgniter project where I’m doing comments and wanted to do threaded comments. I did a quick Google for “php+threaded+comments” and came up first with the “laughable attempt” and then your great article… that just happens to be right in the CodeIgniter alley! Made my Friday a lot easier, thanks!

  3. thinsoldier says:

    Thanks, this is so much simpler than the code I started writing.

  4. Aziz Light says:

    Great Job. I was trying to figure out how to do something similar myself. Is that your CodeIgniter implementation of the class or did you do an CI emplementation of this class? Either was it would be great if you could submit the code for the CI Implementation if that’s OK with you.

    • The CodeIgniter version is slightly modified (mainly that it outputs using a View file), but the logic is identical. I switched it around so that the constructer loaded the CI instance ($this->CI =& get_instance()) as you can’t have arguments on CI library constructers. The constructer in the given class was changed into a method called load(). And then format_comment() was modified to output to the View file: echo $this->CI->load->view(‘comment/comment’, $data, TRUE);

      Feel free to mash it up however you’d like–it’s simple enough that I figured I would just show the recipe.

  5. Danny says:

    BTW how many lvls are there?
    mean like

    parent
    child lvl 1
    child lvl 2
    child lvl 3
    child lvl X

    • JG says:

      There is no set cap, that’s something you need to decide on your own (only allow replies to comments below whatever depth you want). For example when I used it, I only showed the reply button for comments three or fewer levels deep.

      • Brixter says:

        This is the logic when deleting a comment with child entries.

        Before deleting the parent comment which had 5 child comments, you first have to update the 5 child comments by giving them null/0 parent_ids. After that, you have to delete the parent comment.

        Then the existing child comments will print without parents.

  6. Jim says:

    Ok i got everything running now, great class 🙂
    One problem: When is delete a parent-comment, all the childs are gone from the output. How can i fix this?

    Thanks in advance.

    • Jim says:

      The Comment-Array is filled from automaticly filled from the database btw.

    • JG says:

      Instead of deleting the deleted comment from the database, I’d add a boolean column called deleted and then use that logic in your template for deciding whether or not to show the comment details. That way your tree stays intact.

      • Jim says:

        Thanks 🙂 But when someone floods my database with useless comments, they stay in the database with deleted=1, which could take a lot of useless space?

        Isn’t there any other way to fix this, like rebuilding the array?

        • JG says:

          There’s not a good way to preserve the tree with deleted comments if you don’t keep at least something around. I’d build in logic that actually deletes the row from the DB if there are no replies, but marks as deleted when there are replies. That preserves the tree when you need it and cleans up the DB if you get attacked by comment spammers.

  7. Jim says:

    Thanks JG, I figured it out :]
    If the parent-field of the deleted item is 0, you update the first child to be the new parent. Else you update the childs.

    if (empty($comment[‘parent’]))
    {
    $mysqli->query(‘UPDATE comments SET parent = 0 WHERE parent = ‘.$comment[‘id’]);
    }
    else
    {
    $mysqli->query(‘UPDATE comments SET parent = ‘.$comment[‘parent’].’ WHERE parent = ‘.$comment[‘id’]);
    }

  8. mcbeav says:

    I know this article is a bit old now, but hopefully someone will hear my cries for help. I feel like my head is about to explode. I am trying to use this excellent script to implement comments using mysqli prepared statements, but no matter what i try the function never prints anything.

    here is some code i have. maybe someone can point me in the right direction:

    $q = $DBH->prepare(“SELECT id, parent_id, comment FROM comments WHERE page = ?”);
    $q->bind_param(“i”, $page);
    $q->execute();

    $q->bind_result($id, $parent_id, $text);

    $all_results = array();

    while ($q->fetch()) {
    $all_results[] = array(
    ‘id’ => $id,
    ‘parent_id’ => $parent_id,
    ‘text’ => $text);
    }
    $q->close();

    $tc = new Threaded_comments($all_results);
    $tc->print_comments();

    • amit says:

      post the more code i didn’t or not any get properly
      thanks

    • andrew says:

      I know I’m replying to a very old comment, but in case anybody else is wondering how to utilize this code with a prepared statement, here is what I did to get it to work:

      $sth = $dbh->prepare(“SELECT id, parent_id, text FROM comments”);
      $sth->execute();
      $result = $sth->fetchAll(PDO::FETCH_ASSOC);
      $threaded_comments = new Threaded_comments($result);
      $threaded_comments->print_comments();

  9. I have modified your code slightly for it to create threaded unordered lists.

    http://i.nt.ro/2011/02/threaded-array-library-for-codeigniter/

  10. jona says:

    I have a question how does the html would look like? and what about if some Javascript slide Toggle function would be introduced into this class and how it would like like?

  11. Akhil says:

    is there any way to set limit the levels, please……

    • JG says:

      That’s completely up to you–set your code to not show reply links/forms for comments at a certain level and then re-check in the routine that processes incoming comments to make sure they’re not too deep in a thread. The above code is just a very basic example of what you can do.

  12. Gaurish says:

    This is simply excellent code…..

    very good job…

    thanks a lot..

  13. Sonyer says:

    Seems to be ok, I would check security stuff deeper

  14. shayan says:

    thanks. this article really help me

  15. Nikhil Joshi says:

    HI Thanks your code really help me in the situation where i needed to display tree structure for the chart this was very helpful

  16. Sanjay says:

    This is simply excellent example…
    Its capable of dealing with any level deep threaded comment

  17. Love this code. Just a quick thought though: the foreach loop on line 46 will never execute more than once per function call. Thus, you don’t really need it.

    Other than that, I maintain this is some of the best-written PHP on the web.

  18. My bad. Didn’t catch the sets of empty brackets on lines 16 and 20. You totally need that foreach after all.

  19. Amitav Roy says:

    Thanks for this super cool tutorial. I wanted to implement a threaded comment in Laravel 4 for one of my project and this helped me a lot. Had to change the approach a bit but the concept remained the same. Specially the recursive function to print comments.

    Thanks a lot mate.

  20. ????? says:

    very nice class i used it and working good but add some changes
    i added the query on the function __construct to load it when the class called
    nice thank you

  21. gamezat says:

    class Threaded_comments
    {
    public $total = 0;
    public $parents = array();
    public $children = array();

    /**
    * @param array $comments
    */
    function __construct($result)
    {

    global $DB;
    $comments = $DB->getMultiRowFromQuery( ‘SELECT * FROM comments where thread_id = \”.$result[‘id’].’\” );
    $this->total = count($comments );
    foreach ($comments as $comment)
    {
    if ($comment[‘parent_id’] parents[$comment[‘comment_id’]][] = $comment;
    }
    else
    {
    $this->children[$comment[‘parent_id’]][] = $comment;
    }
    }
    }

    /**
    * @param array $comment
    * @param int $depth
    */
    private function format_comment($comment, $depth)
    {
    for ($depth; $depth > 0; $depth–)
    {
    echo “\t”;
    }
    echo ‘

    ‘.$comment[‘author_name’].’ ‘.date(‘F j, Y at h:i a’, $comment[‘created’]).’

    ‘.nl2br ($comment[‘raw_message’]) .’

    ‘;

    }

    /**
    * @param array $comment
    * @param int $depth
    */
    private function print_parent($comment, $depth = 0)
    {
    foreach ($comment as $c)
    {
    $this->format_comment($c, $depth);

    if (isset($this->children[$c[‘comment_id’]]))
    {
    echo ”;
    $this->print_parent($this->children[$c[‘comment_id’]], $depth + 1);
    echo ”;
    }
    }
    }

    public function print_comments()
    {
    foreach ($this->parents as $c)
    {
    $this->print_parent($c);
    }
    }

    }

  22. Hercules says:

    Hi I have used this script for my application, but how can I style the output to get the comments displayed in threaded form or create threaded unordered lists?

    Appreciate any suggestions.

    Thanks

  23. S Coder says:

    good class. but how do add html tags for this class?

    • johannes says:

      since you can get the order and depth values of these comments, then you can create a new public array, say $threads, push the ordered comment with its depth value into $threads, and return it to your program with ‘getThreads’ method. that’s it. you get an ordered array, loop and format it with html tags with these array values.

      parents[$comment[‘cmtid’]][] = $comment;
      }
      else
      {
      $this->children[$comment[‘cmtparentid’]][] = $comment;
      }
      }
      }

      private function format_comment($comment, $depth)
      {
      $comment[‘depth’] = $depth;
      $this->threads[] = $comment;
      }

      private function print_parent($comment, $depth = 0)
      {
      foreach ($comment as $c)
      {
      $this->format_comment($c, $depth);

      if (isset($this->children[$c[‘cmtid’]]))
      {
      $this->print_parent($this->children[$c[‘cmtid’]], $depth + 1);
      }
      }
      }

      public function getThreads(){
      foreach ($this->parents as $c)
      {
      $this->print_parent($c);
      }
      return $this->threads;
      }

      }

      ?>

  24. ?????? says:

    My programmer is trying to persuade me to move to .net from PHP. I have always disliked the idea because of the costs. But he’s tryiong none the less. I’ve been using WordPress on several websites for about a year and am worried about switching to another platform. I have heard very good things about blogengine.net. Is there a way I can transfer all my wordpress content into it? Any kind of help would be really appreciated!

  25. Shawn says:

    HI Thanks your code really help me in the situation where i needed to display tree structure for the chart this was very helpful

Leave a Reply to JG