amitsolanki24_'s avatar

Take 5 countries from relationship inside each()

Why take method is not working in collection each method? It's returning all the countries instead of 5.

     ->each(function ($item) {
        // Limit countries to 5
        $data = $item->countries->take(5);
        $item->countries = $data;
	   return $item;
    });
0 likes
12 replies
tisuchi's avatar

@amitsolanki24_ It's because you applied the take method within each is not modifying the original collection items because each is used for iteration without modifying the items, and the changes to the countries relationship need to be explicitly assigned back using setRelation.

Try this:

$items->each(function ($item) {
    // Limit countries to 5
    $item->setRelation('countries', $item->countries->take(5));
});

BTW, setRelation is performant also. You can give a read this article: https://freek.dev/2311-increase-performance-by-using-eloquents-setrelation-method

1 like
amitsolanki24_'s avatar

@tisuchi It's working thanks.

##below code is also giving same result.

->each(function ($item) {
        // Limit countries to 5
        $data = $item->countries->take(5);
        unset($item->countries);
        $item->countries = $data;

I have a doubt which one is best and fast this one or that one.

tisuchi's avatar

@amitsolanki24_ Honestly, there are many ways to solve your issue.

However, I don't prefer your approach.

Why?

  • You are bypassing Eloquent’s relationship management methods that ca lead to unexpected behavior or issues.
  • Also, you are manually unsetting and reassigning properties might lead to inconsistencies or bugs if there are other parts of the code relying on the original relationship.

Ofcourse, it's your preference which approach you will go, but surely I won't prefer to use manual unset().

2 likes
amitsolanki24_'s avatar

@tisuchi so what is the best approach to solve this problem in your opinion?

setRelation() or something else?

$item->setRelation('countries', $item->countries->take(5));
1 like
tisuchi's avatar

@amitsolanki24_ None of the approaches is the best approach. It's subjective.

Personally, I prefer setRelation() since it serves our purpose without introducing any N+1 issue.

1 like
Snapey's avatar

make sure you eager load countries before this code to ensure you avoid n+1 issues.

2 likes
amitsolanki24_'s avatar

@Snapey yes, I'm eager loading

 ->with([
        'countries' => function ($queryBuilder) {
          $queryBuilder
            ->where('is_active', 1)
            ->select('name', 'continent');
        },
Snapey's avatar

Why not just take the number you need in the view when you iterate countries (assuming thats what you are doing)

@foreach($item->countries->take(5) as $country)

Saving that loop in the controller.

1 like
amitsolanki24_'s avatar

@Snapey I think passing unwanted data in view is not good and it might be slow down webpage loading speed.

JussiMannisto's avatar

@amitsolanki24_ Your thinking is wrong. Objects are passed to views by reference, so they're not going to consume any more memory or slow down the page load.

And even if they weren't, i.e. if the data was deep-copied, the amount of memory would be meaningless in the big picture.

1 like

Please or to participate in this conversation.