Question is, why do you have comments separate from the Post::with() query?
Eagerloading relations with pagination
Hi, I am working on a Forum system. I have my comments and post thread eager loaded. I want to paginate the comments results and render it on a page. I am using L5.
/**
* The author of a post
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function author() {
return $this->belongsTo('Community\User', 'user_id');
}
/**
* The category of a post
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function category() {
return $this->belongsTo('Community\Category');
}
/**
* Returns all the associated comments for a particular post
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function comments() {
return $this->hasMany('Community\PostThread');
}
My comments model is
public function author() {
return $this->belongsTo('Community\User', 'user_id');
}
/**
* Returns the thread to a post.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function post() {
return $this->belongsTo('Community\Post');
}
This is what I came up with
$discussion = Post::with('author',
'category',
'author.roles',
'followers')->whereSlug($slug)->first();
$comments = $discussion->comments()->paginate(5);
But I still suffer the N+1 problem.
Thank you.
Why not also eager load comments ?
$discussion = Post::with('author',
'comments',
'category',
'author.roles',
'followers')->whereSlug($slug)->first();
$comments = $discussion->comments()->paginate(5);
@cyberomin You have single main model loaded so there is no place for n+1 issue on that model, ie. on any of its relations. So I suppose you have n+1 on nested relation, or it's not n+1 at all :)
Show the code you execute (a view) and what queries you expect vs what you have now.
Hi all this is my code
public function getDiscussion($slug, Dispatcher $event)
{
Event::listen('illuminate.query', function($query)
{
var_dump($query);
});
$discussion = Post::with('author',
'comments',
'category',
'author.roles',
'followers')->whereSlug($slug)->first();
$comments = $discussion->comments()->paginate(5);
if ($discussion) {
$event->fire('UserViewedDiscussion', ['post_id' => $discussion->id]);
return view('discussion.index', ['discussion' => $discussion, 'comments' => $comments]);
} else {
$categories = Category::all();
return view('errors.404', ['categories' => $categories]);
}
}
My View
@extends('layout.app')
@section('title')<?php
echo $discussion->post_type == 'discussion' ? "Discussions" : "Annoucements";
?> » {{ $discussion->title }} — @stop
@section('content')
<!-- Modal -->
@if(Auth::check())
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">What is wrong with this post?</h4>
</div>
<form method="post" action="{{ URL::route('flag-discussion') }}">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="hidden" name="reporter" value="{{ Auth::user()->fname }} {{ Auth::user()->lname }}">
<input type="hidden" name="user_id" value="{{ Auth::user()->id }}">
<input type="hidden" name="flagged_comment" value="" id="flagged_comment">
<input type="hidden" name="flagged_discussion_url" value="" id="flagged_discussion_url">
<input type="hidden" name="author" value="" id="author">
<input type="hidden" name="title" value="" id="title">
<div class="modal-body">
<div class="form-group">
<textarea class="form-control" rows="3" name="comment"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-danger">Report</button>
</div>
</form>
</div>
</div>
</div>
@endif
<?php
$url_pattern = "#([-a-zA-Z0-9@:%_\+.~\#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~\#?&//=]*)?)#si";
$replace = "<a href='$1' target='_blank'>$1</a>";
?>
<div class="container">
<div class="breadcrumb_c">
<i class="fa fa-home"></i> <a href='/'>Home</a> >
<?php
echo $discussion->post_type == "discussion" ? "<a href='/discussions'>Discussions</a>" : "<a href='/announcements'>Annoucements</a>";
?>
>
<a href="{{ URL::route('category', $discussion->category->slug) }}"> {{ $discussion->category->name }}</a> > {{ $discussion->title }}
</div>
<div class="content">
<div class="col-md-9">
<ul class="list-unstyled thread">
<li>
<!--Allow authenticated users to sign follow discussions!-->
@if( Auth::check() && !Auth::user()->is_following($discussion->id) )
<form class="form-horizontal comment" role="form" method="POST" action="{{URL::route('follow-discussion')}}">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="hidden" name="post_id" value="{{ $discussion->id }}">
<input type="hidden" name="user_id" value="{{ Auth::user()->id }}">
<div class="form-group pull-right">
<button type="submit" class="btn btn-success" style="margin-right: 15px;">
Follow Discussion
</button><br>
{{ str_plural('Follower', $total) }}
</div>
</form>
@elseif( Auth::check() && Auth::user()->is_following($discussion->id) )
<form class="form-horizontal comment" role="form" method="POST" action="{{URL::route('unfollow-discussion')}}">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="hidden" name="post_id" value="{{ $discussion->id }}">
<input type="hidden" name="user_id" value="{{ Auth::user()->id }}">
<div class="form-group pull-right">
<button type="submit" class="btn btn-warning" style="margin-right: 15px;">
Unfollow Discussion
</button><br>
{{$total = $discussion->followers->count()}} {{ str_plural('Follower', $total) }}
</div>
</form>
@endif
<h2 class="text-danger">{{ $discussion->title }}</h2>
<div>
<span class="label label-success">{{ $discussion->category->name }}</span>
Updated <i class="fa fa-clock-o"></i> {{ \Carbon\Carbon::createFromTimeStamp(strtotime($discussion->updated_at))->diffForHumans() }}
</div>
</li>
<li>
@if($discussion->author->profile_pic)
<img src="{{ $discussion->author->profile_pic }}" alt="{{ $discussion->author->fname }} {{ $discussion->author->lname }}" class="img-circle">
@else
<div class="profile-pix-initials img-circle" style="margin:0 20px 0 -40px;">
{{ substr($discussion->author->fname,0,1) }}{{ substr($discussion->author->lname,0,1) }}
</div>
@endif
<!--Flag this comment!-->
<span class="pull-right">
@if(Auth::check())
<a href='#' data-toggle="modal" data-target="#myModal" data-title="{{ $discussion->title }}" data-author="{{ $discussion->author->fname }} {{ $discussion->author->lname }}" data-comment="{{ $discussion->post }}" data-comment_url="{{ URL::route('discussion' ,$discussion->slug) }}" class="report"><i class="fa fa-flag-o"></i> Flag Post</a><br>
@if(Auth::user()->hasRole('Admin') || Auth::user()->hasRole('Moderator'))
@include('admin.partials.deletepostbtn')
@endif
@endif
</span>
<strong>
{{ $discussion->author->fname }} {{ $discussion->author->lname }}
@if($discussion->author->hasRole('Admin') || $discussion->author->hasRole('Moderator'))
(Konga Moderator)
@endif
</strong> <br>
<?php
$post = preg_replace($url_pattern, $replace, $discussion->post);
?>
<pre>{!! $post !!}</pre>
<div class="clearfix"></div>
</li>
</ul>
@if(Auth::check())
@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>Whoops!</strong> There were some problems with your input.<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@if (Session::has('global'))
<div class="alert alert-success">
{{Session::get('global')}}
</div>
@endif
<!--If discussion is not closed, allow comments only for logged in users.!-->
@if($discussion->is_closed == 0 && $discussion->post_type == 'discussion')
<form class="form-horizontal comment" role="form" method="POST" action="{{URL::route('comment-post')}}">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="hidden" name="post_id" value="{{ $discussion->id }}">
<input type="hidden" name="user_id" value="{{ Auth::user()->id }}">
<div class="form-group">
<textarea placeholder="Add your voice. Comment here" name="comment">{{ old('comment') }}</textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" style="margin-right: 15px;">
Submit Comment
</button>
</div>
</form>
@elseif($discussion->is_closed == 1)
<div class="alert alert-danger">Discussion has been closed</div>
@endif
@endif
<!--Display all comments here!-->
@if($comments->count() > 0)
<h4><i class="fa fa-comments-o"></i> Comments</h4>
<ul class="list-unstyled thread">
@foreach($comments as $comment)
<li>
@if($comment->author->profile_pic)
<img src="{{ $comment->author->profile_pic }}" alt="" class="img-circle">
@else
<div class="profile-pix-initials img-circle" style="margin:0 20px 0 -40px;">
{{ substr($comment->author->fname,0,1) }}{{ substr($comment->author->lname,0,1) }}
</div>
@endif
<div>
<strong>
{{ $comment->author->fname }} {{ $comment->author->lname }}
@if($comment->author->hasRole('Admin') || $comment->author->hasRole('Moderator'))
(Konga Moderator)
@endif
</strong>
<!--Flag a post/delete discussion!-->
<div class='pull-right' style="text-align:right">
<strong>
Posted {{ \Carbon\Carbon::createFromTimeStamp(strtotime($comment->created_at))->diffForHumans() }}
</strong><br>
@if(Auth::check())
<a href='#' data-toggle="modal" data-target="#myModal" data-title="{{ $discussion->title }}" data-author="{{ $comment->author->fname }} {{ $comment->author->lname }}" data-comment="{{ $comment->comment }}" data-comment_url="{{ URL::route('get-single-comment' ,$discussion->slug) }}/{{$comment->id}}" class="report"><i class="fa fa-flag-o"></i> Flag Post</a>
@if(Auth::user()->hasRole('Admin') || Auth::user()->hasRole('Moderator'))
@include('admin.partials.deletecommentbtn')
@endif
@endif
</div>
</div>
<?php
$comment = preg_replace($url_pattern, $replace, $comment->comment);
?>
<pre>{!! $comment !!}</pre>
<div class="clearfix"></div>
</li>
@endforeach
</ul>
@endif
<?php echo $comments->render(); ?>
</div>
<div class="col-md-3 categories-home">
@include('partials.stranger')
<!--Display all the participant in a conversation!-->
<div class="category-list people-in-discussion">
<div class="sub-header">People in Conversation</div>
<ul class="list-unstyled" style="padding:0;padding-top:10px;">
<?php $people = [] ?>
@foreach($discussion->comments as $comment)
<?php if(!in_array($comment->author->id, $people)) { ?>
<li style="padding:0;">
<div class="col-md-4">
@if($comment->author->profile_pic)
<img src="{{ $comment->author->profile_pic }}" alt="" class="img-circle">
@else
<div class="profile-pix-initials img-circle" style="margin:0 10px 0 0;">
{{ substr($comment->author->fname,0,1) }}{{ substr($comment->author->lname,0,1) }}
</div>
@endif
</div>
<div class="col-md-8">
<strong>
<a href="/user/{{$comment->author->email}}"> {{ $comment->author->fname }} {{ $comment->author->lname }}</a>
</strong>
</div>
<div class="clearfix"></div>
</li>
<?php
$people[] = $comment->author->id;
}
?>
@endforeach
</ul>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
$(".report").click(function() {
$("#flagged_comment").val($(this).data('comment'));
$("#flagged_discussion_url").val($(this).data('comment_url'));
$("#author").val($(this).data('author'));
$("#title").val($(this).data('title'));
})
});
</script>
@stop
@section('footer')
@stop
@cyberomin This is the culprit:
@if($comment->author->profile_pic)
because you never eager loaded authors for the comments.
You need this:
$discussion = Post::with('author',
// 'comments', no need for this - remove
'category',
'author.roles',
'followers')->whereSlug($slug)->first();
$comments = $discussion->comments()->with('author')->paginate(5);
Dear @JarekTkaczyk, you don't have any idea what you have just done for me. Thank you very much. I do appreciate. Everything works fine now. @JeffreyWay please give that man a medal for me.
@cyberomin I'm glad mate! Tick the answer for the posterity's sake, in case anyone looks for solution for similar problem.
@JarekTkaczyk is a genius
@sabuncuserhat You're way to kind man!
Please or to participate in this conversation.