diff --git a/src/Map.php b/src/Map.php index d824b59..d42c9ea 100644 --- a/src/Map.php +++ b/src/Map.php @@ -20,6 +20,7 @@ use function array_values; use function asort; use function implode; +use function is_array; use function sprintf; use function strcmp; use function uasort; @@ -375,16 +376,10 @@ public function partition(callable $callback): array return [$instance1, $instance2]; } - /** - * @template TGroup of non-empty-string - * @psalm-param callable(TValue):TGroup $callback - * - * @psalm-return MapInterface> - */ public function group(callable $callback): MapInterface { /** - * @psalm-var MapInterface> $groups + * @psalm-var MapInterface> $groups */ $groups = new GenericMap([]); @@ -393,15 +388,21 @@ public function group(callable $callback): MapInterface * @psalm-suppress ImpureFunctionCall Upstream projects have to ensure that they do not manipulate the * value here. */ - $groupIdentifier = $callback($value); - try { - $group = $groups->get($groupIdentifier); - } catch (OutOfBoundsException) { - $group = clone $this; - $group->data = []; + $groupIdentifiers = $callback($value); + if (! is_array($groupIdentifiers)) { + $groupIdentifiers = [$groupIdentifiers]; } - $groups = $groups->put($groupIdentifier, $group->put($key, $value)); + foreach ($groupIdentifiers as $groupIdentifier) { + try { + $group = $groups->get($groupIdentifier); + } catch (OutOfBoundsException) { + $group = clone $this; + $group->data = []; + } + + $groups = $groups->put($groupIdentifier, $group->put($key, $value)); + } } return $groups; diff --git a/src/MapInterface.php b/src/MapInterface.php index 7661bb2..98a7553 100644 --- a/src/MapInterface.php +++ b/src/MapInterface.php @@ -215,10 +215,10 @@ public function partition(callable $callback): array; /** * Groups the items of this object by using the callback. * - * @template TGroup of non-empty-string + * @template TGroup of non-empty-string|non-empty-list * @psalm-param callable(TValue):TGroup $callback * - * @psalm-return MapInterface> + * @psalm-return MapInterface> */ public function group(callable $callback): MapInterface; diff --git a/src/OrderedList.php b/src/OrderedList.php index 3fe4ba3..c4a5868 100644 --- a/src/OrderedList.php +++ b/src/OrderedList.php @@ -22,6 +22,7 @@ use function assert; use function hash; use function implode; +use function is_array; use function is_callable; use function serialize; use function sort; @@ -367,30 +368,30 @@ public function partition(callable $callback): array return [$instance1, $instance2]; } - /** - * @template TGroup of non-empty-string - * @psalm-param callable(TValue):TGroup $callback - * - * @psalm-return MapInterface> - */ public function group(callable $callback): MapInterface { - /** @var MapInterface> $groups */ + /** @var MapInterface> $groups */ $groups = new GenericMap([]); foreach ($this as $value) { /** * @psalm-suppress ImpureFunctionCall Upstream projects have to ensure that they do not manipulate the * value here. */ - $groupName = $callback($value); - if (! $groups->has($groupName)) { - $groups = $groups->put($groupName, new GenericOrderedList([$value])); - continue; + $groupNames = $callback($value); + if (! is_array($groupNames)) { + $groupNames = [$groupNames]; } - $existingGroup = $groups->get($groupName); - $existingGroup = $existingGroup->add($value); - $groups = $groups->put($groupName, $existingGroup); + foreach ($groupNames as $groupName) { + if (! $groups->has($groupName)) { + $groups = $groups->put($groupName, new GenericOrderedList([$value])); + continue; + } + + $existingGroup = $groups->get($groupName); + $existingGroup = $existingGroup->add($value); + $groups = $groups->put($groupName, $existingGroup); + } } return $groups; diff --git a/src/OrderedListInterface.php b/src/OrderedListInterface.php index 53558ff..b5349f4 100644 --- a/src/OrderedListInterface.php +++ b/src/OrderedListInterface.php @@ -182,10 +182,10 @@ public function partition(callable $callback): array; /** * Groups the items by using the callback. * - * @template TGroup of non-empty-string + * @template TGroup of non-empty-string|non-empty-list * @psalm-param callable(TValue):TGroup $callback * - * @psalm-return MapInterface> + * @psalm-return MapInterface> */ public function group(callable $callback): MapInterface; diff --git a/tests/GenericMapTest.php b/tests/GenericMapTest.php index a595e55..9678180 100644 --- a/tests/GenericMapTest.php +++ b/tests/GenericMapTest.php @@ -736,6 +736,19 @@ public function testWillGroupValuesToNewInstancesOfInitialInstance(): void self::assertEquals($object2, $b->get('bar')); } + public function testWillGroupValueIntoMultipleGroups(): void + { + $map = new GenericMap([ + 'foo' => $object1 = new GenericObject(1), + ]); + + $grouped = $map->group(fn () => ['a', 'b']); + self::assertTrue($grouped->has('a')); + self::assertTrue($grouped->has('b')); + self::assertTrue($grouped->get('a')->contains($object1)); + self::assertTrue($grouped->get('b')->contains($object1)); + } + /** * @template T * @psalm-param array $elements diff --git a/tests/GenericOrderedListTest.php b/tests/GenericOrderedListTest.php index 09ab77f..a2357c3 100644 --- a/tests/GenericOrderedListTest.php +++ b/tests/GenericOrderedListTest.php @@ -1048,6 +1048,19 @@ public function testWillGroupValuesToNewInstancesOfInitialInstance(): void self::assertEquals($object2, $b->at(0)); } + public function testWillGroupValueIntoMultipleGroups(): void + { + $list = new GenericOrderedList([ + $object1 = new GenericObject(1), + ]); + + $grouped = $list->group(fn () => ['a', 'b']); + self::assertTrue($grouped->has('a')); + self::assertTrue($grouped->has('b')); + self::assertTrue($grouped->get('a')->contains($object1)); + self::assertTrue($grouped->get('b')->contains($object1)); + } + /** * @template T * @psalm-param list $data