I have made a blogging application in Laravel 8.
The (single) article view displays comments under the article and I am now working on adding a comments reply functionality (link to the current code).
In appModelsComment.php
I have:
class Comment extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'article_id',
'parent_id',
'body',
'approved'
];
// Join users to comments
public function user() {
return $this->belongsTo(User::class);
}
// Join articles to comments
public function article() {
return $this->belongsTo(Article::class);
}
// Join comment replies to comments
public function replies() {
return $this->hasMany(Comment::class, 'parent_id');
}
}
The comment replies are those comments for wichb rhe parent_id
column value is not null.
In the comments view (comments-list.blade.php
) I have:
@foreach ($comments as $comment)
@if (null == $comment->parent_id)
<li class="depth-1 comment">
<div class="comment__avatar">
<img class="avatar" src="{{ asset('images/avatars/' . $comment->user->avatar) }}" alt="" width="50" height="50">
</div>
<div class="comment__content">
<div class="comment__info">
<div class="comment__author">{{ $comment->user->first_name }} {{ $comment->user->last_name }}</div>
<div class="comment__meta">
<div class="comment__time">{{ date('jS M Y', strtotime($comment->created_at)) }}</div>
<div class="comment__reply">
<a class="comment-reply-link" href="#0">Reply</a>
</div>
</div>
</div>
<div class="comment__text">
<p>{{ $comment->body }}</p>
</div>
</div>
@include('themes/' . $theme_directory . '/partials/comment-form')
{{-- Comment replies --}}
@if (count($comment->replies))
<ul class="children">
@foreach ($comment->replies as $reply)
<li class="depth-2 comment">
<div class="comment__avatar">
<img class="avatar" src="{{ asset('images/avatars/' . $reply->user->avatar) }}" alt="" width="50" height="50">
</div>
<div class="comment__content">
<div class="comment__info">
<div class="comment__author">{{ $reply->user->first_name }} {{ $reply->user->last_name }}</div>
<div class="comment__meta">
<div class="comment__time">{{ date('jS M Y', strtotime($reply->created_at)) }}</div>
</div>
</div>
<div class="comment__text">
<p>{{ $reply->body }}</p>
</div>
</div>
</li>
@endforeach
</ul>
@endif
</li>
@endif
@endforeach
In the ArticlesController controller, I have the logic that displays a single article, with comments, if it has any :
class ArticlesController extends FrontendController {
public function show( $slug ) {
// Single article
$article = Article::firstWhere( 'slug', $slug );
$old_article = Article::where( 'id', '<', $article->id )->orderBy( 'id', 'DESC' )->first();
$new_article = Article::where( 'id', '>', $article->id )->orderBy( 'id', 'ASC' )->first();
// Comments
$commentsQuery = $this->get_commentQuery( $article->id );
$comments_count = $commentsQuery->count();
// If infinite scroll, paginate comments (to be loaded one page per scroll),
// Else show them all
if (boolval($this->is_infinitescroll)) {
$comments = $commentsQuery->paginate($this->comments_per_page);
} else {
$comments = $commentsQuery->get();
}
return view( 'themes/' . $this->theme_directory . '/templates/single',
array_merge( $this->data, [
'categories' => $this->article_categories,
'article' => $article,
'old_article' => $old_article,
'new_article' => $new_article,
'comments' => $comments,
'comments_count' => $comments_count,
'comments_per_page' => $this->comments_per_page,
'tagline' => $article->title,
'is_infinitescroll' => $this->is_infinitescroll
] )
);
}
/**
* AJAX Call for Loading extra comments
*
* @param Request $request
*
* @return void
*/
public function get_comments_ajax( Request $request ) {
if ( ! $request->ajax() ) {
// Redirect to Home Page or just BOMB OUT!
exit();
}
$more_comments_to_display = TRUE;
/** @todo - 5 - This shouldcould be a setting */
$article_id = $request->post( 'article_id' );
$page_number = $request->post( 'page' );
$offset = $this->comments_per_page * $page_number;
$data['comments'] = $this->get_commentQuery( $article_id, $this->comments_per_page, $offset )->get();
$content = '';
if ( $data['comments']->count() ) {
$content .= view('themes/' . $this->theme_directory . '/partials/comments-list',
array_merge( $data, [
'is_infinitescroll' => $this->is_infinitescroll
])
);
} else {
$more_comments_to_display = FALSE;
}
echo json_encode( [ 'html' => $content, 'page' => $page_number, 'more_comments_to_display' => $more_comments_to_display, 'article_id' => $article_id ] );
exit();
}
/**
* get_commentQuery
*
* @param int $article_id
* @param int $limit
* @param int $offset
*
* @return object
*/
private function get_commentQuery( int $article_id, int $limit = 0, int $offset = 0 ): object {
$commentQuery = Comment::where( [ 'article_id' => $article_id, 'approved' => 1 ] )->orderBy( 'id', $this->comments_orderby_direction );
if ( $offset > 0 ) {
$commentQuery = $commentQuery->offset( $offset );
}
if ( $limit > 0 ) {
$commentQuery = $commentQuery->limit( $limit );
}
return $commentQuery;
}
public function add_comment( Request $request ) {
$rules = [
'msg' => 'required'
];
$messages = [
'msg.required' => 'Please enter a message'
];
$validator = Validator::make( $request->all(), $rules, $messages );
if ( $validator->fails() ) {
return redirect()->back()
->withErrors( $validator->errors() )
->withInput()
->with( 'error', 'Please correct the errors below );
}
$fields = $validator->validated();
$comment = [
'user_id' => Auth::user()->id,
'article_id' => $request->get( 'article_id' ),
'parent_id' => $request->get( 'parent_id' ),
'body' => $fields['msg'],
'approved' => 0
];
// Insert comment in the 'comments' table
$query = Comment::create( $comment );
if ( $query ) {
return redirect()->back()->with( 'success', 'Your comment is pending.' );
} else {
return redirect()->back()->with( 'error', 'Adding comment failed' );
}
}
}
The problem
I have been unable to add the comment replies under each comment that has any.
The $comment->replies
array is empty for every comment.
Questions
- Where is my mistake?
- What is the most reliable way to fix this problem?
2
Answers
First Thing: (update following relation for replies)
Second Thing
you must have to assign a clone of
get_commentQuery()
to multiple variables in order to get the correctcomments-count
and for comments list in separate queries. According to your logicget_commentQuery()
method pointing to the same memory reference and first you manipulating that reference value by count query then applying fetch comments query (in short you are overriding the query by mistake so you need clone value to difference reference byclone
keyword)Example Query would be like that:
Third Thing (Eager load replies within the comment)
while querying for
comment
you should have to eager loadreplies
relations. So, in this way, eachcomment
will have its allreplies
in its child relation:Example:
If you don’t understand anything then let me know:
To retrieve comment replies using Eloquent, you can define a relationship in the Comment model that points to itself using the belongsTo() method. Here’s an example:
The parent() method defines a relationship where a comment belongs to another comment as its parent. The replies() method defines a relationship where a comment has many other comments as its replies.
To retrieve the replies of a comment, you can use the replies relationship:
To eager load comment replies for a collection of comments, you can use the with() method:
This will retrieve all the comments and their associated replies using a single query, rather than executing a separate query for each comment’s replies. The resulting $comments collection will include all the comments and their replies, which you can access using the replies property of each comment:
This will output the body of each comment and its associated replies. By eager loading the replies, you can avoid the N+1 query problem that can occur if you were to load each comment’s replies individually.
If above solution doesn’t just let me know.