cyberomin's avatar

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.

0 likes
9 replies
bashy's avatar

Question is, why do you have comments separate from the Post::with() query?

bestmomo's avatar

Why not also eager load comments ?

 $discussion = Post::with('author',
        'comments',
            'category',
            'author.roles',
            'followers')->whereSlug($slug)->first();
        $comments = $discussion->comments()->paginate(5);
JarekTkaczyk's avatar

@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.

cyberomin's avatar

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";
            ?>  &raquo; {{ $discussion->title }} &mdash; @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">&times;</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
JarekTkaczyk's avatar

@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);
2 likes
cyberomin's avatar

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.

JarekTkaczyk's avatar

@cyberomin I'm glad mate! Tick the answer for the posterity's sake, in case anyone looks for solution for similar problem.

Please or to participate in this conversation.