Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

lat4732's avatar
Level 12

How to improve this code with eager loading?

Hey!

I have these lines of code

@if(auth()->user()->reviewReports()->where('review_id', $modalRev->id)->where('solved', 1)->where('report_reason', 'harmful_illegal')->doesntExist())
<input type="radio" class="btn-check" name="reason" id="reason01{{ $modalRev->id }}" autocomplete="off" value="harmful_illegal" required>
<label class="btn btn-outline-danger" for="reason01{{ $modalRev->id }}">@lang('main.global__report_reason01')</label>
@endif

@if(auth()->user()->reviewReports()->where('review_id', $modalRev->id)->where('solved', 1)->where('report_reason', 'personal_information')->doesntExist())
<input type="radio" class="btn-check" name="reason" id="reason02{{ $modalRev->id }}" autocomplete="off" value="personal_information" required>
<label class="btn btn-outline-danger" for="reason02{{ $modalRev->id }}">@lang('main.global__report_reason02')</label>
@endif

@if(auth()->user()->reviewReports()->where('review_id', $modalRev->id)->where('solved', 1)->where('report_reason', 'advertising_promotional')->doesntExist())
<input type="radio" class="btn-check" name="reason" id="reason03{{ $modalRev->id }}" autocomplete="off" value="advertising_promotional" required>
<label class="btn btn-outline-danger" for="reason03{{ $modalRev->id }}">@lang('main.global__report_reason03')</label>
@endif

@if(auth()->user()->reviewReports()->where('review_id', $modalRev->id)->where('solved', 1)->where('report_reason', 'inappropriate_media')->doesntExist())
<input type="radio" class="btn-check" name="reason" id="reason05{{ $modalRev->id }}" autocomplete="off" value="inappropriate_media" required>
<label class="btn btn-outline-danger" for="reason05{{ $modalRev->id }}">@lang('main.global__report_reason04')</label>
@endif

@if(auth()->user()->reviewReports()->where('review_id', $modalRev->id)->where('solved', 1)->where('report_reason', 'other')->doesntExist())
<input type="radio" class="btn-check" name="reason" id="reason04{{ $modalRev->id }}" autocomplete="off" value="other" required>
<label class="btn btn-outline-danger" for="reason04{{ $modalRev->id }}">@lang('main.global__report_reason05')</label>
@endif

which is executing like 40 queries. How can I improve that?

0 likes
28 replies
Snapey's avatar

in the controller, load the reviewReports for the user, then remove the brackets in the blade

eg

Auth::user()->load('reviewReports');

@if(auth()->user()->reviewReports->where('review_id'......

or get the users reviewReports as a separate variable and pass to the view

lat4732's avatar
Level 12

@Snapey I'm getting

Method Illuminate\Database\Eloquent\Collection::doesntExist does not exist.

Also is that actually OK for a page?

visualization

lat4732's avatar
Level 12

@Snapey How to apply ->doesntExist() in this case?

@if(auth()->user()->reviewReports->where('review_id', $modalRev->id)->where('solved', 1)->where('report_reason', 'inappropriate_media'))

@endif
frankielee's avatar

@Laralex

Method Illuminate\Database\Eloquent\Collection::doesntExist does not exist.

The error is telling you that the method doesntExist is doesntExist

If you checked the documentation, you will find that there is doesntHave

Snapey's avatar

by removing the brackets you are now using an eloquent collection rather than a query, so you need to adapt your code to suit

I've no idea how many records you have loaded, so there may be better approaches. For instance if solved always must =1 then this would be better as a query scope when loading the relation from the database

lat4732's avatar
Level 12

@Snapey This code is in a foreach loop in a page that contains reviews. A review can be reported by user only once per reason. That's what I'm checking. If the user is able to report a review for a specific reason - display it. The reasons are 5. The foreach loop is to display as many modals as needed (1 modal per review).

@foreach($reviews as $modalRev)

@if(auth()->user()->reviewReports()->where('review_id', $modalRev->id)->where('solved', 1)->where('report_reason', 'harmful_illegal')->doesntExist())
    <input type="radio" class="btn-check" name="reason" id="reason01{{ $modalRev->id }}" autocomplete="off" value="harmful_illegal" required>
    <label class="btn btn-outline-danger" for="reason01{{ $modalRev->id }}">@lang('main.global__report_reason01')</label>
@endif

@if(auth()->user()->reviewReports()->where('review_id', $modalRev->id)->where('solved', 1)->where('report_reason', 'personal_information')->doesntExist())
    <input type="radio" class="btn-check" name="reason" id="reason02{{ $modalRev->id }}" autocomplete="off" value="personal_information" required>
    <label label class="btn btn-outline-danger" for="reason02{{ $modalRev->id }}">@lang('main.global__report_reason02')</label>
@endif

@if(auth()->user()->reviewReports()->where('review_id', $modalRev->id)->where('solved', 1)->where('report_reason', 'advertising_promotional')->doesntExist())
    <input type="radio" class="btn-check" name="reason" id="reason03{{ $modalRev->id }}" autocomplete="off" value="advertising_promotional" required>
    <label class="btn btn-outline-danger" for="reason03{{ $modalRev->id }}">@lang('main.global__report_reason03')</label>
@endif

@if(auth()->user()->reviewReports()->where('review_id', $modalRev->id)->where('solved', 1)->where('report_reason', 'inappropriate_media')->doesntExist())
    <input type="radio" class="btn-check" name="reason" id="reason05{{ $modalRev->id }}" autocomplete="off" value="inappropriate_media" required>
    <label class="btn btn-outline-danger" for="reason05{{ $modalRev->id }}">@lang('main.global__report_reason04')</label>
@endif

@if(auth()->user()->reviewReports()->where('review_id', $modalRev->id)->where('solved', 1)->where('report_reason', 'other')->doesntExist())
    <input type="radio" class="btn-check" name="reason" id="reason04{{ $modalRev->id }}" autocomplete="off" value="other" required>
    <label class="btn btn-outline-danger" for="reason04{{ $modalRev->id }}">@lang('main.global__report_reason05')</label>
@endif

@endforeach
lat4732's avatar
Level 12

I really need to improve this. Please, suggest me a solution...

visualization

frankielee's avatar

@Laralex

Probably you can start by reducing the the query called?

Example:

$ids = $reviews->pluck('id');
$filterReviews = auth()->user()->reviewReports()->whereIn('review_id', $ids)->where('solved', 1)->get();

// do the comparison here. 

Just an example.

splatEric's avatar

@Laralex The example that @frankielee has provided is getting you a collection of the User reviewReports that match the reviews that you are iterating through with your foreach loop.

I think I would be inclined to create a data structure of review report reasons indexed by the review id:

$userReportedReasonsByReviewId = $filterReviews->mapToGroups(function ($reviewReport) {
    return [$reviewReport->review_id => $reviewReport->report_reason];
});

This should give you a collection keyed by reviewId for all the report reasons that have so far been created by that user.

So if you include that in the your data, your conditional can be:

@if(!isset($userReportedReasonsByReviewId[$modalRev->id]) || !$userReportedReasonsByReviewId[$modalRev->id]->contains('harmful_illegal'))
<input ... >
@endif

(you could probably do something useful with null coalesce, on the if conditional here, which might make it a bit more readable ...)

lat4732's avatar
Level 12

@splatEric I'm getting

Call to a member function getKey() on string

controller

$reviews = $website->reviews()->where('report_status', '!=', 3)->orderBy('created_at', 'DESC')->with('reply', 'reports', 'media', 'user')->get();

$ids = $reviews->pluck('id');
$filterReviews = auth()->user()->reviewReports()->whereIn('review_id', $ids)->where('solved', 1)->get();

$userReportedReasonsByReviewId = $filterReviews->mapToGroups(function ($reviewReport) {
    return [$reviewReport->review_id => $reviewReport->report_reason];
});

blade

@if(!isset($userReportedReasonsByReviewId[$modalRev->id]) || !$userReportedReasonsByReviewId[$modalRev->id]->contains('harmful_illegal'))
    <input type="radio" class="btn-check" name="reason" id="reason01{{ $modalRev->id }}" autocomplete="off" value="harmful_illegal" required>
    <label class="btn btn-outline-danger" for="reason01{{ $modalRev->id }}">@lang('main.global__report_reason01')</label>
@endif

@if(!isset($userReportedReasonsByReviewId[$modalRev->id]) || !$userReportedReasonsByReviewId[$modalRev->id]->contains('personal_information'))
    <input type="radio" class="btn-check" name="reason" id="reason02{{ $modalRev->id }}" autocomplete="off" value="personal_information" required>
    <label label class="btn btn-outline-danger" for="reason02{{ $modalRev->id }}">@lang('main.global__report_reason02')</label>
@endif

@if(!isset($userReportedReasonsByReviewId[$modalRev->id]) || !$userReportedReasonsByReviewId[$modalRev->id]->contains('advertising_promotional'))
    <input type="radio" class="btn-check" name="reason" id="reason03{{ $modalRev->id }}" autocomplete="off" value="advertising_promotional" required>
    <label class="btn btn-outline-danger" for="reason03{{ $modalRev->id }}">@lang('main.global__report_reason03')</label>
@endif

@if(!isset($userReportedReasonsByReviewId[$modalRev->id]) || !$userReportedReasonsByReviewId[$modalRev->id]->contains('inappropriate_media'))
    <input type="radio" class="btn-check" name="reason" id="reason05{{ $modalRev->id }}" autocomplete="off" value="inappropriate_media" required>
    <label class="btn btn-outline-danger" for="reason05{{ $modalRev->id }}">@lang('main.global__report_reason04')</label>
@endif

@if(!isset($userReportedReasonsByReviewId[$modalRev->id]) || !$userReportedReasonsByReviewId[$modalRev->id]->contains('other'))
    <input type="radio" class="btn-check" name="reason" id="reason04{{ $modalRev->id }}" autocomplete="off" value="other" required>
    <label class="btn btn-outline-danger" for="reason04{{ $modalRev->id }}">@lang('main.global__report_reason05')</label>
@endif
lat4732's avatar
Level 12

@splatEric I've tested your code

$userReportedReasonsByReviewId = $filterReviews->mapToGroups(function ($reviewReport) {
    return [$reviewReport->review_id => $reviewReport->report_reason];
});

and it is working pretty good but why am I getting this error? I understand what's the problem but I have no solution to it. This is an example dd($userReportedReasonsByReviewId) result

visualization

splatEric's avatar
Level 2

@Laralex interesting, it turns out that the collection from mapToGroups is an Eloquent collection, where the contains method behaves slightly differently because it expects each entry to be a model instance.

This might not be the best way of doing this, but what you can do to rectify this is:

$userReportedReasonsByReviewId = $filterReviews
	->mapToGroups(
		function ($reviewReport) {
		    return [$reviewReport->review_id => $reviewReport->report_reason];
		}
	)
	->mapWithKeys(
		function ($reasons, $reviewId) {
			return [$reviewId => collect($reasons)];
		}
	)

This effectively recasts the Eloquent collection to a standard Support collection, and you can then call contains as originally described.

Thinking about this a bit more, you could go a step further and create a collection that has an empty list of report reasons for those reviews that the user has not reported at all:

$userReportedReasonsByReviewId = $filterReviews->mapToGroups(function ($reviewReport) {
    return [$reviewReport->review_id => $reviewReport->report_reason];
});

$filterReviewsReportedReasons = $filterReviews->mapWithKeys(
	function ($filterReview) use ($userReportedReasonsByReviewId) {
		return [$filterReview->id => collect(userReportedReasonsByReviewId[$filterReview->id] ?? [])
	}
);

I've not tested this last approach, but I am pretty sure this should give you an entry in filterReviewsReportedReasons for every review you are going to iterate over, with an empty collection when no report has been raised. as such, your if statements can be simplified to:

@if(!$userReportedReasonsByReviewId[$modalRev->id]->contains('harmful_illegal'))

HTH

splatEric's avatar

As an aside, I'd also consider having a loop for the input field generation. You could do something like:

// shortened for brevity
$reportingReasons = ['reason01' => 'harmful_illegal', 'reason02' => 'personal_information'];

blade

@foreach($reportingReasons as $reasonCode => $reasonString)
			@if(!$filterReviewsReportedReasons[$modalRev->id]->contains($reasonString))
						<input type="radio" class="btn-check" name="reason" id="{{$reasonCode}}{{ $modalRev->id }}" autocomplete="off" value="{{$reasonString}}" required>
									<label class="btn btn-outline-danger" for="{{$reasonCode}}{{ $modalRev->id}}">@lang('main.global__report_' . $reasonCode)</label>
			@endif
@endforeach
lat4732's avatar
Level 12

@splatEric I already have an array that is included everywhere. But I don't have the reasonCode in it :(. It is as follows:

'report_reasons' => [
        'harmful_illegal', 'personal_information', 'advertising_promotional', 'inappropriate_media', 'other'
    ],
lat4732's avatar
Level 12

@splatEric I realized that I actually don't even use these ids so I'll remove them. I did this

@foreach(config('app.report_reasons') as $reason)
    @if(isset($userReportedReasonsByReviewId[$modalRev->id]) && !$userReportedReasonsByReviewId[$modalRev->id]->contains($reason))
        <input type="radio" class="btn-check" name="reason" id="reason01{{ $modalRev->id }}" autocomplete="off" value="{{ $reason }}" required>
        <label class="btn btn-outline-danger" for="reason01{{ $modalRev->id }}">{{ Str::ucfirst(Str::replace('_', ' ', $reason)) }}</label>
    @endif
@endforeach

but the problem is that when a review hasn't been reported by the certain user there are no reasons displayed. Why?

splatEric's avatar

@Laralex Hmmm, well you could use the numerical indexes to create the reasonCode string:

@foreach($reportingReasons as $reasonString)
			@if(!$filterReviewsReportedReasons[$modalRev->id]->contains($reasonString))
						<input type="radio" class="btn-check" name="reason" id="reason0{{$loop->iteration}}{{ $modalRev->id }}" autocomplete="off" value="{{$reasonString}}" required>
									<label class="btn btn-outline-danger" for="reason0{{$loop->iteration}}{{ $modalRev->id}}">@lang('main.global__report_reason0' . $loop->iteration)</label>
			@endif
@endforeach

but that will fail if you wind up with more than 9 reasons in the future.

To be fair the id attribute can be anything as long as it's unique, so it doesn't matter too much. But you could change your lang indexes to be based on the reason strings you are storing. This would have the added benefit of removing the opacity of reasonN keys.

so your lang file winds up as something like:

'global__report' => [
		reasons => [
				'harmful_illegal' => 'Harmful/illegal',
				'personal_information' => 'Contains personal information'
		]
]

and your lang reference would then be:

@lang('main.global__report.reasons.' . $reasonString)

But I'm second guessing a lot at this point. Glad I've been able to help with the main problem at least!!

splatEric's avatar

@Laralex If you are using the first option I provided, you need to negate the isset check, as it will only contain the review id if at least one review has been provided. The second option removes the need for the isset check entirely.

splatEric's avatar

@Laralex Make sure you create unique ids for each of the inputs - you're hardcoding 01 for each iteration of the reason loop, which will mean they'll be the same for every reason for reach review.

1 like
lat4732's avatar
Level 12

@splatEric I'm using the first option you provided. The second option leads to the same error as the first option (Undefined array key).

controller

        $ids = $reviews->pluck('id');
        $filterReviews = auth()->user()->reviewReports()->whereIn('review_id', $ids)->where('solved', 1)->get();

        $userReportedReasonsByReviewId = $filterReviews
        ->mapToGroups(
            function ($reviewReport) {
                return [$reviewReport->review_id => $reviewReport->report_reason];
            }
        )
        ->mapWithKeys(
            function ($reasons, $reviewId) {
                return [$reviewId => collect($reasons)];
            }
        );

blade

@foreach(config('app.report_reasons') as $reason)
    @if(!$userReportedReasonsByReviewId[$modalRev->id]->contains($reason))
        <input type="radio" class="btn-check" name="reason" autocomplete="off" value="{{ $reason }}" required>
        <label class="btn btn-outline-danger">
            {{ Str::ucfirst(Str::replace('_', ' ', $reason)) }}
        </label>
    @endif
@endforeach

I totally understand why this is happening but how can I combine avoiding this error (undefined array key) and display reasons (if the review hasn't been reported) at the same time?

splatEric's avatar

@Laralex Try the following:

@foreach(config('app.report_reasons') as $reason)
    @if(!isset($userReportedReasonsByReviewId[$modalRev->id]) || !$userReportedReasonsByReviewId[$modalRev->id]->contains($reason))
        <input type="radio" class="btn-check" name="reason" autocomplete="off" value="{{ $reason }}" required>
        <label class="btn btn-outline-danger">
            {{ Str::ucfirst(Str::replace('_', ' ', $reason)) }}
        </label>
    @endif
@endforeach

Note the negation on the isset check.

When you tried the second approach, did you switch to using the $filterReviewsReportedReasons collection in your blade template?

1 like
lat4732's avatar
Level 12

@splatEric

When you tried the second approach, did you switch to using the $filterReviewsReportedReasons collection in your blade template?

Yeah, I did.

I think it's working properly now. Thanks a ton man! If something goes wrong I'll reply back. Thanks again!

lat4732's avatar
Level 12

@splatEric I'm stucked again. I tried to implement what we have done in another blade file which has the same construction but without success. What we've done was for report modals which are stored on the main layout blade file before the </body> tag. But the reviews are actually loaded from AJAX call which is hitting this controller method:

$reviews = $website->reviews()->where('report_status', '!=', 3)->with('reply', 'reports', 'media', 'user')
                        ->when($request->stars, function($builder, $stars) {
                            $builder->when(is_array($stars), function($query) use ($stars) {
                                $query->where(function($query) use ($stars) {
                                    foreach($stars as $star) {
                                        $query->orWhere('review_stars', $star);
                                    }
                                });
                            }, function($query) use($stars) {
                                $query->where('review_stars', $stars);
                            });
                        })
                        ->when($request->search, function($builder, $search) {
                            $builder->where(function($query) use ($search) {
                                $query->orWhereRaw('LOWER(review_header) LIKE LOWER(?)', ["%{$search}%"])
                                        ->orWhereRaw('LOWER(review_content) LIKE LOWER(?)', ["%{$search}%"]);
                            });
                        })
                        ->reorder()
                        ->when(
                            $request->sort, 
                            function ($builder, $sort) {
                                $builder->when(
                                    $sort == "useful",
                                    fn ($builder) => $builder->orderBy('useful', 'desc'),
                                    fn($builder) =>$builder->orderBy('created_at', $sort)
                                );
                            },
                            fn ($builder) => $builder->orderByRaw("user_id = ? DESC, created_at DESC", [auth()->id()])
                        )
                        ->paginate(15);

$user_have_opened_reports = 0;

if(auth()->check()) {
    auth()->user()->with('reviewReports');

    $available_review_ids = $reviews->pluck('id');
    $user_have_opened_reports = auth()->user()->reviewReports()->whereIn('review_id', $available_review_ids)->where('solved', 0)->doesntExist();
}
                    
return response()->json([
    'pagination' => collect($reviews->toArray())->except('data'),
    'html' => view('website.ajaxViews.reviews', [
        'website' => $website,
        'reviews' => $reviews
    ])->render(),
    'user_have_opened_reports' => (auth()->check()) ? $user_have_opened_reports : []
]);

and the following blade file is displayed in the main layout blade file through the AJAX call

// .............. more code

@auth
    @if(auth()->user()->id != $review->user_id && auth()->user()->is_company != 1)
        @if(auth()->user()->reviewReports->where('review_id', $review->id)->count() < count(config('app.report_reasons')))
            @if(auth()->user()->reviewReports()->where('review_id', $review->id)->where('solved', 0)->doesntExist())
                <a href="javascript:void(0);" class="ReportReview jikh8000_{{ $review->id }}" data-id="{{ $review->id }}" data-toggle="tooltip" title="@lang('main.reviews_report_this_review')" data-bs-toggle="modal" data-bs-target="#reportModal{{ $review->id }}"><i class="icon-flag text-danger"></i></a></li>
            @endif
        @endif
    @endif
@endauth

// .............. more code

and I'm stucked now. On a page that has 26 reviews, this line is executing like 28-29 queries.

@if(auth()->user()->reviewReports()->where('review_id', $review->id)->where('solved', 0)->doesntExist())

How can I replace it with something like what we've done above? I've tried to do something as you can see in the controller method but I have no idea how to implement it in the if statement.

splatEric's avatar

@Laralex You need to follow the same principle of having a data structure that you can check for the logged in user, rather than running the query for each review on the user.

The difference here seems to be that you are checking that the user doesn't have "unsolved" reports for the review. So in it's simplest form you could construct a collection of reported review ids that are unsolved for the user:

$unsolvedReportsByReviewId = auth()->user()->reviewReports->where('solved', 0)->pluck('review_id')->unique();

and then you can do the check in blade as:

@if(!$unsolvedReportsByReviewId->contains($review->id))
....
1 like

Please or to participate in this conversation.