Closed josoroma-zz closed 8 years ago
I have no idea how you managed to get groupBy('$v["RoleName"]', '$v["AreaName"]', '$v["PermissionName"]')
to work, because it shouldn't. :laughing: The arguments aren't just key selectors, they're key selector (value to group by), value selector (value of item) and result selector (two functions joining results from key and value selectors), each with distinct functionality (please read PHPDoc).
What you need is nested grouping. Let's assume:
$items = [
[ 'category' => 'Foos', 'group' => 'Big foos', 'name' => 'Foo 1' ],
[ 'category' => 'Foos', 'group' => 'Big foos', 'name' => 'Foo 2' ],
[ 'category' => 'Foos', 'group' => 'Small foos', 'name' => 'Foo 3' ],
[ 'category' => 'Bars', 'group' => 'Big bars', 'name' => 'Bar A' ],
[ 'category' => 'Bars', 'group' => 'Small bars', 'name' => 'Bar B' ],
[ 'category' => 'Bars', 'group' => 'Small bars', 'name' => 'Bar C' ],
];
First you need to group by category. In the result selector for category's items, you group sequence again, by group.
$grouped = from($items)
->groupBy(
function ($item) { return $item['category']; },
null,
function ($subitems, $category) {
return from($subitems)
->groupBy(
function ($subitem) { return $subitem['group']; }
);
});
// -or-
$grouped = from($items)
->groupBy(
'$v["category"]', '$v',
function ($subitems) {
return from($subitems)->groupBy('$v["group"]');
});
print_r($grouped->toArrayDeep());
Output:
Array
(
[Foos] => Array
(
[Big foos] => Array
(
[0] => Array
(
[category] => Foos
[group] => Big foos
[name] => Foo 1
)
[1] => Array
(
[category] => Foos
[group] => Big foos
[name] => Foo 2
)
)
[Small foos] => Array
(
[0] => Array
(
[category] => Foos
[group] => Small foos
[name] => Foo 3
)
)
)
[Bars] => Array
(
[Big bars] => Array
(
[0] => Array
(
[category] => Bars
[group] => Big bars
[name] => Bar A
)
)
[Small bars] => Array
(
[0] => Array
(
[category] => Bars
[group] => Small bars
[name] => Bar B
)
[1] => Array
(
[category] => Bars
[group] => Small bars
[name] => Bar C
)
)
)
)
null
as key selector means "use default", in this case just return value from an array's item (function ($item) { return $item['item']; }
or '$v'
). I haven't specified result selectors for keys, the default is used too (the value from key selector is used as item key).
Here is a more complex example which uses all arguments of the groupBy
method:
$grouped = from($items)
->groupBy(
function ($item) { return $item['category']; },
function ($item) { return $item; },
function ($subitems, $category) {
return [
'category' => $category,
'items' => from($subitems)
->groupBy(
function ($subitem) { return $subitem['group']; },
function ($subitem) { return $subitem['name']; },
function ($subsubitems, $group) {
return [
'group' => $group,
'items' => $subsubitems,
];
},
Functions::increment()
)
];
},
Functions::increment());
// -or-
$grouped = from($items)
->groupBy(
'$v["category"]', '$v',
function ($subitems, $category) {
return [
'category' => $category,
'items' => from($subitems)
->groupBy(
'$v["group"]', '$v["name"]',
function ($subsubitems, $group) {
return [
'group' => $group,
'items' => $subsubitems,
];
},
Functions::increment()
)
];
},
Functions::increment());
print_r($grouped->toArrayDeep());
Output:
Array
(
[0] => Array
(
[category] => Foos
[items] => Array
(
[0] => Array
(
[group] => Big foos
[items] => Array
(
[0] => Foo 1
[1] => Foo 2
)
)
[1] => Array
(
[group] => Small foos
[items] => Array
(
[0] => Foo 3
)
)
)
)
[1] => Array
(
[category] => Bars
[items] => Array
(
[0] => Array
(
[group] => Big bars
[items] => Array
(
[0] => Bar A
)
)
[1] => Array
(
[group] => Small bars
[items] => Array
(
[0] => Bar B
[1] => Bar C
)
)
)
)
)
If you need three levels of grouping, the same logic applies:
$grouped = from($items)
->groupBy(
function ($item) { return $item['category']; },
null,
function ($catitems) {
return from($catitems)
->groupBy(
function ($item) { return $item['group']; },
null,
function ($groupitems) {
return from($groupitems)
->groupBy(
function ($item) { return $item['name']; }
);
}
);
});
// -or-
$grouped = from($items)
->groupBy(
'$v["category"]', '$v',
function ($catitems) {
return from($catitems)
->groupBy(
'$v["group"]', '$v',
function ($groupitems) {
return from($groupitems)->groupBy(
'$v["name"]', '$v'
);
});
});
Grouping and joining methods are the most complex in LINQ, so it takes some time to get used to them fully.
Grouping by multiple keys produces a bit too nested code, so you can write helper methods like this:
function group_by_multiple ($items, array $keySelectors, $valueSelector = null)
{
$resultSelector = null;
foreach (array_reverse($keySelectors) as $keySelector) {
$resultSelector = function ($subitems) use ($keySelector, $valueSelector, $resultSelector) {
return from($subitems)->groupBy(
$keySelector,
$resultSelector == null ? $valueSelector : null,
$resultSelector);
};
}
return $resultSelector($items);
}
print_r(
group_by_multiple($items,
[ '$v["category"]', '$v["group"]', '$v["name"]' ],
'$v["name"]'
)->toArrayDeep());
Amazing, doing my homework, thank you @Athari!
Hi!
Beautiful package, but I have probably an easy roadblock to solve since this morning, please let me explain my scenario, I am gathering the following array result (Role/Area/Permissions) from a Laravel Builder Query:
and I was wondering how to produce the following array result using your groupBy method:
This what I am currently using right now:
Thanks in advance!