Recursive contains?

#1

Hii there,

I’m trying to load my comments for articles from the Comments table.
The table has two columns relevant to this:

  • id (id of the comment)
  • reply (id of the comment being replied to, null if not a reply)

Now my question is how I can contain the comments but have them be recursive.
eg. the first comment is not a reply, but has two replies, one of which has a reply, which in term has it’s own reply.

I hope it made sense :^)

#2

Just create an association from Comments to itself? But if there’s effectively no limit to the level of recursion, you won’t be able to specify enough ['Comments' => ['Comments' => ['Comments' => levels to be sure to get them all. You’d need to use some sort of lazy loading instead, maybe with a plugin like this one, or just calling loadInto in an accessor function.

#3

Currently my CommentsTable looks like this:

public function initialize(array $config)
    {
        // ... Mostly irrelevant stuff stripped out

        $this->belongsTo('Reply', [
            'foreignKey' => 'reply',
            'className' => 'FinlayDaG33k/Comments.Comments',
            'propertyName' => 'reply'
        ]);
    }

Shouldn’t that have done the trick already?

#4

Surely, a Comment would haveMany Replies, and maybe belongTo a “ReplyTo”?

#5

Ah, I see.
So I need to add a hasMany as well.
I’ll try that when I’m at work tomorrow :slight_smile:

#6

With a different name. belongsTo will get you the parent, hasMany will get the children.

1 Like
#7

I have tried fiddling around with it and added the following to my CommentsTable:

$this->belongsTo('ReplyTo', [
            'foreignKey' => 'reply',
            'className' => 'FinlayDaG33k/Comments.Comments',
            'propertyName' => 'replyto'
]);
$this->hasMany('Replies',[
            'foreignKey' => 'reply',
            'className' => 'FinlayDaG33k/Comments.Comments',
            'propertyName' => 'replies'
]);

But I still only get the comment with the first level of replies.

I’ve also tried the LazyLoad plugin, but I don’t see how I should implement it…

#8

I’ve managed to get the LazyLoad plugin to partially work, however, I can only get the first level of replies with this as well :\

I guess that for now I’ll have to abandon this idea (and instead just have 1 huge line of comments, in a fashion we have here as well as opposed to the “reddit style” of comments) for now.

#9

Show the code that you’re using to load the entity and display the comments?

#10

I stashed it right now (tho it’s at my work PC so I don’t have access to it right now) while I continued on the “forum style” comments but here is what it looked like:

$article = $this->Articles->findBySlug($slug)->contain(['Tags', 'Comments'])->firstOrFail();
$this->set(compact('articles'));

then to display it, I actually didn’t really do anything but:

foreach($article->Comments as $comment):
  pr($this->Comments->reply);
endforeach;

It did load up the 1st replies just fine, but not the replies on the 1st replies (3rd level when looking from the initial comment).

#11

Assuming that your display code here is from memory, as it has a couple of obvious errors in it. Your data structure, after loading, would look like $article->comments[0]->replies[0]->replies[0], etc. You probably need to make an element that would output a comment and it’s replies, where the replies are in turn outputted with the same element.

echo '<ul>';
foreach ($article->comments as $comment) {
    echo $this->element('comment', ['comment' => $comment]);
}
echo '</ul>';

Templates/Element/comment.ctp:

echo '<li>' . $comment->text_field;
if (!empty($comment->replies)) {
    echo '<ul>';
    foreach ($comment->replies as $reply) {
        echo $this->element('comment', ['comment' => $reply]);
    }
    echo '</ul>';
}
echo '</li>';
#12

Hm… I’ll have a look at it when I’m back at work.

And yes, the little code was from my memory.
Also, shouldn’t the code snipped you send limit itself to only as much foreach loops as you code into it?

#13

Not sure what you mean about limiting itself. If you only want to show a certain number of levels of replies, then you could pass 'level' => 1 to the first invocation, and 'level' => $level + 1 to the recursive call, and use that to decide whether to continue to another level or not.

1 Like
#14

Oh I see :slight_smile:

That seems to work pretty fine :slight_smile:
Though I’ve (on advice of one of my co-workers) hardcoded it to limit to 10 “threads” max to prevent the famous Reddit issue in /r/counting :^)