phplaravellaravel-collection

Grouping only some elements in a Laravel collection


Given the following data:

manufacturers       makes
id | name           id | manufacturer_id | name
---|-----           -- | --------------- | ----
1  | Honda          1  | 1               | Honda
2  | Toyota         2  | 1               | Acura
3  | Mazda          3  | 2               | Toyota
4  | Other          4  | 2               | Lexus
                    5  | 3               | Mazda
                    6  | 4               | Other

I'm trying to get an array that looks like this:

[
  "Honda" => [1 => "Honda", 2 => "Acura"],
  "Toyota" => [3 => "Toyota", 4 => "Lexus"],
  5 => "Mazda",
  6 => "Other",
]

In other words, group by manufacturer, but only if the manufacturer has more than one make. The method for specifying this condition is eluding me. So far I've got this:

Make::with("manufacturer")
    ->get()
    ->groupBy(
        fn (Make $m) => $m->manufacturer->models->count() < 2 ? null : $m->manufacturer->name
    )
    ->map(
        fn (Collection $c) => $c->mapWithKeys(
            fn (Make $m) => [$m->id => $m->name]
        )
    )
    ->toArray();

I had hoped returning null from the groupBy callback would prevent the grouping, but no such luck. It gives me this:

[
  "" => [5 => "Mazda", 6 => "Other"]
  "Honda" => [1 => "Honda", 2 => "Acura"],
  "Toyota" => [3 => "Toyota", 4 => "Lexus"],
]

So I want a way to either conditionally group in the first place, or conditionally flatten after the grouping. I know I can do the latter manually, but it seems like something there should be a built-in Collection method for doing.


Solution

  • You can use

    $result = Make::with('manufacturer')->get()
        ->groupBy('manufacturer.name')
        ->flatMap(function ($makes, $manufacturerName) {
            if ($makes->count() > 1) {
                return [$manufacturerName => $makes->pluck('name', 'id')];
            }
    
            $make = $makes->first();
            return [$make->id => $make->name];
        })
        ->toArray();