Browse Source

Allow preserving keys when using Collection::groupBy().

Refs #17870
ADmad 1 year ago
parent
commit
b9653ca8cc
2 changed files with 72 additions and 3 deletions
  1. 48 3
      src/Collection/CollectionTrait.php
  2. 24 0
      tests/TestCase/Collection/CollectionTest.php

+ 48 - 3
src/Collection/CollectionTrait.php

@@ -278,13 +278,53 @@ trait CollectionTrait
     }
 
     /**
-     * @inheritDoc
+     * Splits a collection into sets, grouped by the result of running each value
+     * through the callback. If $callback is a string instead of a callable,
+     * groups by the property named by $callback on each of the values.
+     *
+     * When $callback is a string it should be a property name to extract or
+     * a dot separated path of properties that should be followed to get the last
+     * one in the path.
+     *
+     * ### Example:
+     *
+     * ```
+     * $items = [
+     *  ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
+     *  ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
+     *  ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
+     * ];
+     *
+     * $group = (new Collection($items))->groupBy('parent_id');
+     *
+     * // Or
+     * $group = (new Collection($items))->groupBy(function ($e) {
+     *  return $e['parent_id'];
+     * });
+     *
+     * // Result will look like this when converted to array
+     * [
+     *  10 => [
+     *      ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
+     *      ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
+     *  ],
+     *  11 => [
+     *      ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
+     *  ]
+     * ];
+     * ```
+     *
+     * @param callable|string $path The column name to use for grouping or callback that returns the value.
+     *   or a function returning the grouping key out of the provided element
+     * @param bool $preserveKeys Whether to preserve the keys of the existing
+     *   collection when the values are grouped. Defaults to false.
+     * @return \Cake\Collection\CollectionInterface
      */
-    public function groupBy(callable|string $path): CollectionInterface
+    public function groupBy(callable|string $path, bool $preserveKeys = false): CollectionInterface
     {
         $callback = $this->_propertyExtractor($path);
         $group = [];
-        foreach ($this->optimizeUnwrap() as $value) {
+        foreach ($this->optimizeUnwrap() as $key => $value) {
             $pathValue = $callback($value);
             if ($pathValue === null) {
                 throw new InvalidArgumentException(
@@ -298,6 +338,11 @@ trait CollectionTrait
                 $pathValue = $pathValue->name;
             }
 
+            if ($preserveKeys) {
+                $group[$pathValue][$key] = $value;
+                continue;
+            }
+
             $group[$pathValue][] = $value;
         }
 

+ 24 - 0
tests/TestCase/Collection/CollectionTest.php

@@ -668,6 +668,30 @@ class CollectionTest extends TestCase
         $this->assertEquals($expected, iterator_to_array($grouped));
     }
 
+    public function testGroupByPreserveIndex(): void
+    {
+        $items = [
+            'first' => ['name' => 'foo', 'type' => 'a'],
+            'second' => ['name' => 'bar', 'type' => 'b'],
+            'third' => ['name' => 'baz', 'type' => 'b'],
+            'fourth' => ['name' => 'aah', 'type' => 'a'],
+        ];
+
+        $collection = new Collection($items);
+        $grouped = $collection->groupBy('type', true);
+        $expected = [
+            'a' => [
+                'first' => ['name' => 'foo', 'type' => 'a'],
+                'fourth' => ['name' => 'aah', 'type' => 'a'],
+            ],
+            'b' => [
+                'second' => ['name' => 'bar', 'type' => 'b'],
+                'third' => ['name' => 'baz', 'type' => 'b'],
+            ],
+        ];
+        $this->assertEquals($expected, iterator_to_array($grouped));
+    }
+
     /**
      * Tests grouping by a deep key
      */