laracastsluvr's avatar

Nested Collection Sorting

Hello,

(UPDATE: in a laravel 6 code base)

I think I'm missing something while trying to sort a "multi-dimensional" (or nested) collection.

Initially I have a list of models which I groupBy() a given field they all have, lets say 'group_field'.

This gives me a collection of the models now grouped under each one's group_field.

- group_field
  -- Model id:1, sort_property: 2
  -- Model id:3, sort_property: 1
- group_field
  -- Model id:2, sort_property: 1
  -- Model id:4, sort_property: 3
  -- Model id:7, sort_property: 2
- group_field
  -- Model id:5, sort_property: 2
  -- Model id:6, sort_property: 1

Internally each model also contains a sort property (not cleverly named 'sort').

I'd like to sortBy() that internal property after grouping the models together, but it seems like it is "ignoring" the sortBy methods. I just need to sort them in-place and return the entire collection further down the line to a blade view or what ever.

// this is how I'm grouping
return $items->groupBy('group_field');

Shouldn't now a sortBy/sortByDesc method on each group sort the "items" internally without the need to return or cast to an array/collection?

// silly example that does not work
return
	$items->groupBy('group_field')->each(function($group) {
		$group->sortBy('sort_property');
	});

// expected result
- group_field
  -- Model id:3, sort_property: 1
  -- Model id:1, sort_property: 2
- group_field
  -- Model id:2, sort_property: 1
  -- Model id:7, sort_property: 2
  -- Model id:4, sort_property: 3
- group_field
  -- Model id:6, sort_property: 1
  -- Model id:5, sort_property: 2

I've also tried passing a callback to the sortBy() method but I still get the original grouped collection with the internal items in their original order. What am I missing?

// Dumping in place shows correctly sorted list.
	$items->groupBy('group_field')->each(function($group) {
		dump($group->sortBy('sort_property')); // I can see them sorted.
	});

return $items; // not sorted anymore.
// or
dd($items); // not sorted

I'm clearly doing something wrong.

0 likes
20 replies
adamprickett's avatar

Use map() instead of each() to affect the collection in-situ.

return $items->groupBy('group_field')->map(function ($group) {
	$group->sortBy('sort_property');
});
1 like
trin's avatar

goto link, all works fine

laracastsluvr's avatar

Already checked... here is your JSON

{
  "1": {
    "0": {
      "id": 1,
      "sort_property": 2,
      "group_field": 1
    },
    "1": {
      "id": 3,
      "sort_property": 1,
      "group_field": 1
    }
  },
  "2": {
    "0": {
      "id": 2,
      "sort_property": 2,
      "group_field": 2
    },
    "1": {
      "id": 5,
      "sort_property": 3,
      "group_field": 2
    },
    "2": {
      "id": 6,
      "sort_property": 1,
      "group_field": 2
    }
  },
  "3": {
    "0": {
      "id": 4,
      "sort_property": 3,
      "group_field": 3
    },
    "1": {
      "id": 7,
      "sort_property": 1,
      "group_field": 3
    }
  }
}

It clearly isn't sorted.

trin's avatar

strange, i see

{
    "1": {
        "1": {
            "id": 3,
            "sort_property": 1,
            "group_field": 1
        },
        "0": {
            "id": 1,
            "sort_property": 2,
            "group_field": 1
        }
    },
    "2": {
        "2": {
            "id": 6,
            "sort_property": 1,
            "group_field": 2
        },
        "0": {
            "id": 2,
            "sort_property": 2,
            "group_field": 2
        },
        "1": {
            "id": 5,
            "sort_property": 3,
            "group_field": 2
        }
    },
    "3": {
        "1": {
            "id": 7,
            "sort_property": 1,
            "group_field": 3
        },
        "0": {
            "id": 4,
            "sort_property": 3,
            "group_field": 3
        }
    }
}
laracastsluvr's avatar

Well, now that you've added JSON_PRETTY_PRINT I see the same... but for me on my code it doesn't do anything at all.

trin's avatar

can u create phpsandbox with your code?

laracastsluvr's avatar

It requires a lot of migrations, models, and relationships to get the data I'm trying to work with.

sr57's avatar

Hi @laracastsluvr

I'd like to sortBy() that internal property after grouping the models together

is not fully understandable.

To sure we understand altogether we have to work on the same sample data.

Let's begin with this example, gf = group_field, sf = sort_field

gf;sf
a;1
a;5
b;2

What do you want as result :

a,b because 1<2 (ex min price)

b,a because 2<3 (ex average price)

@trin , @adamprickett

laracastsluvr's avatar

Hello,

If you take my pseudo grouped example

- group_field_1
  -- Model id:1, sort_property: 2
  -- Model id:3, sort_property: 1
- group_field_2
  -- Model id:2, sort_property: 1
  -- Model id:4, sort_property: 3
  -- Model id:7, sort_property: 2
- group_field_3
  -- Model id:5, sort_property: 2
  -- Model id:6, sort_property: 1

The starting data is just:

Model id:1, sort_property: 2, group_field: 1
Model id:2, sort_property: 1, group_field: 2
Model id:3, sort_property: 1, group_field: 1
Model id:4, sort_property: 3, group_field: 2
Model id:5, sort_property: 2, group_field: 3
Model id:6, sort_property: 1, group_field: 3
Model id:7, sort_property: 2, group_field: 2

This is how it comes from the database. Every "row" is an Eloquent Model, the group_field is actually a relationship to that model so it self is an Eloquent model.

- Model
  -- {model properties: contains the 'sort': # and the parent_group_id foreign key }
  -- { contains a pivot }
  -- parent_group (rel. eloquent model)
    -- { has own properties }

So I group them by the relationship id of parent_group. I wish I could post the entire thing, but it has a lot of parts. It is not a great example I know, but I need to group about 25-30 of those "rows" that have the same parent_group and then sort them when every row is inside the group collection by their set 'sort' order.

laracastsluvr's avatar

Also looking at your example

gf;sf
a;1
a;5
b;2

And assuming the number is my sort_order I would expect them ordered in ascending direction

gf;sf
a;1
b;2
a;5

So how do you sort many gf:sf groups that are nested ?

gf;sf_1
	a;1
	b;2
	a;5
gf;sf_2
	a;3
	d;4
	f;9
gf;sf_3
	c;2
	b;4
    a;6
laracastsluvr's avatar

BTW code is in Laravel 6

I just dd() the result and it was sorted as expected... This is exactly what I have, literally 1 line of code

public function edit(Product $product)
{
	return
		$product->attributes->each(function($attribute) {
			$attribute->attribute_group;
		})
		->groupBy('attribute_group.id')
		->map(function($group) {
			return $group->sortBy('sort');
            });
}

BUT... to my surprise this works

public function edit(Product $product)
{
	dd(
		$product->attributes->each(function($attribute) {
			$attribute->attribute_group;
		})
		->groupBy('attribute_group.id')
		->map(function($group) {
			return $group->sortBy('sort');
            })
	);
}

Why can't I return the data sorted while I can dd() and get what I want... seems to be some Laravel magic at work.

sr57's avatar

We are moving forward

-1- You clarify 'grouped' (you don't want to group by gf)

-2- You don''t answer my main question, what type of order YOU want, ie what do YOU expect with your example

gf;sf_1
	a;1
	b;2
	a;5
gf;sf_2
	a;3
	d;4
	f;9
gf;sf_3
	c;2
	b;4
    a;6

order by gf only, by sf only, by gf,sf or sf,gf ? It should be obvious in your head but not in ours.

laracastsluvr's avatar

Order only by sort field sf. Only the items inside the group.

sr57's avatar

As I read the previous posts I thought there is a misunderstanding, so no, to go ahead, as @trin wrote you have to share with us more info

  • your migrations and models for the tables concerned by this pb

  • data samples for these tables

laracastsluvr's avatar

Sorry to bump this.

But I can't figure out why DD() will output the expected result sorted while return will not.


// This will not have the array inside the group sorted
public function edit(Product $product)
{
	return
		$product->attributes->each(function($attribute) {
			$attribute->attribute_group;
		})
		->groupBy('attribute_group.id')
		->map(function($group) {
			return $group->sortBy('sort');
            });
}

// But this will have the array inside the group sorted.
public function edit(Product $product)
{
	dd(
		$product->attributes->each(function($attribute) {
			$attribute->attribute_group;
		})
		->groupBy('attribute_group.id')
		->map(function($group) {
			return $group->sortBy('sort');
            })
	);
}
sr57's avatar

I (we) already saw this, it's strange, but if you want help we need to redo the SAME thing

laracastsluvr's avatar
laracastsluvr
OP
Best Answer
Level 4

After leaving my last post I went back to the documentation and read this..

sortBy()

The sortBy method sorts the collection by the given key. The sorted collection keeps the original array keys, so in this example we'll use the values method to reset the keys to consecutively numbered indexes:

I just had to return with values()->all() after sorting...

And that is what I was missing. I guess dd() was doing something similar to the collection when converting it to an array, but return was "reverting" to sort by the keys inside the group.

Silly me.

Please or to participate in this conversation.