Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to display comments in a nested way with parent ID

Tags:

php

I have a table comments, thats look like this, added some mockup content as well:

+------------+---------+----------+-------------------+------------------------------------+---------------------------+
| comment_id | user_id | movie_id | comment_parent_id |          comment_content           | comment_creation_datetime |
+------------+---------+----------+-------------------+------------------------------------+---------------------------+
|         26 |       1 |    16329 |                 0 | Första                             | 2016-01-24 10:42:49       |
|         27 |       1 |    16329 |                26 | Svar till första                   | 2016-01-24 10:42:55       |
|         28 |       1 |    16329 |                26 | Andra svar till förta              | 2016-01-24 10:43:06       |
|         29 |       1 |    16329 |                28 | Svar till "andra svar till första" | 2016-01-24 10:43:23       |
+------------+---------+----------+-------------------+------------------------------------+---------------------------+

Im trying to display the comments Reddit style, like this image:

enter image description here

Im trying to fetch all comments SELECT * FROM comments WHERE movie_id = :movie_id ORDER BY comment_creation_datetime DESC and then recursively echo them out.

I have tried a bunch of foreachloops, but none is working as expected

foreach($this->comments as $value){ ?>
    <div class="comment">
        Comment content <?php echo $value->comment_content; ?>
            <?php if($value->comment_parent_id > 0){
    foreach($value as $sub_comment){ ?>
                <div class="comment">
                    comment comment on comment: <?php echo $value->comment_content; ?>
                </div>
            <?php }} ?>
    </div>
<?php }

My question:

How do I echo out the comments in a nested Reddit style with foreach loop?

like image 622
Adam Avatar asked Jan 24 '16 12:01

Adam


3 Answers

You need to both make a list of root comments, and hierarchically organize all of them. You can do both in one go:

$roots = [];
$all = [];
foreach($comments as $comment)
{
  // Make sure all have a list of children
  $comment->comments = [];

  // Store all by ID in associative array
  $all[$comment->comment_id] = $comment;

  // Store the root comments in the roots array, and the others in their parent
  if(empty($comment->comment_parent_id))
    $roots[] = $comment;
  else
    $all[$comment->comment_parent_id]->comments[] = $comment;
}
// Check it's all done correctly!
print_r($roots);

You presorted the list by date, that's preserved in this approach. Also, as you only reorganized by reference this is lightning fast, and ready to be used in templating engines or anything - no need to print out inline like the other answers.

like image 70
Niels Keurentjes Avatar answered Oct 19 '22 01:10

Niels Keurentjes


Working with the adjacency list model can be more problematic with SQL. You need to retrieves all the rows with a single query and store a reference of any parent's child in a lookup table.

$sth = $pdo->prepare("SELECT * FROM comments WHERE movie_id = ? ORDER BY comment_creation_datetime DESC");
$sth->execute([$movie_id]);

$comments = $sth->fetchAll(PDO::FETCH_ASSOC);

$lookup_table = [];
foreach ($comments as $comment_key => $comment) {
    $lookup_table[$comment['comment_parent_id']][$comment_key] = $comment['comment_id'];
}

Now you can display them with

function recursive_child_display($comments, $lookup_table, $root = 0, $deep = 0)
{
    if (isset($lookup_table[$root])) {
        foreach ($lookup_table[$root] as $comment_key => $comment_id) {
            // You can use $deep to test if you're in a comment of a comment

            echo '<div class="comment">';
            echo 'Comment content ', $comments[$comment_key]['comment_content'];
            recursive_child_display($comments, $lookup_table, $comment_id, $deep+1);
            echo '</div>';
        }
    }
}

Example:

// display all the comments from the root
recursive_child_display($comments, $lookup_table, 0);

// display all comments that are parent of comment_id 26
recursive_child_display($comments, $lookup_table, 26);
like image 2
Federkun Avatar answered Oct 19 '22 02:10

Federkun


I would use some recursive function, you start with the ones with parent_id == 0 and recursively print all those who are their direct children.

This code is not tested, but you can get the idea:

function printComment($comment, $comments)
{
    foreach($comments as $c)
    {
        if($c->parent_id == $comment->comment_id)
        {
            $output .= "<li>".printCommment($c)."</li>";
        }
    }

    $output = "<ul>".$comment->comment_content."</ul>".$output;
    return $output;

}


foreach($this->comments as $comment)
{
    if($comment->parent_id == 0)
    {
        echo printComment($comment,$this->comments);
    }
}
like image 2
javier_domenech Avatar answered Oct 19 '22 02:10

javier_domenech