Skip to content

Commit 2812586

Browse files
committed
groupBy() return Table-of-Tables with key mode support
groupBy() now collects all rows per group into sub-Tables instead of keeping only the first row. An optional GroupByKeyMode enum parameter controls how group keys are derived (VarExport, Strval, or Int). Also adds get() to retrieve a row/group by key, toJson() for JSON serialization, and widens type annotations across the class to support the mixed Arr|Table row structure. Updates tests, docs, and examples.
1 parent 3c63914 commit 2812586

9 files changed

Lines changed: 805 additions & 158 deletions

File tree

README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,11 @@ The methods:
206206
- select(): select some fields
207207
- except(): exclude some fields
208208
- where(): filter data
209-
- groupBy(): grouping data
209+
- groupBy(): grouping data into a Table of Tables, with an optional key mode (`GroupByKeyMode::VarExport`, `GroupByKeyMode::Strval`, `GroupByKeyMode::Int`)
210210
- transform(): transforms a specific field with the provided function
211211
- orderBy(): sorting data (ascending or descending)
212212
- toArray(): transform Table object into a native PHP array
213+
- toJson(): convert Table object into a JSON string
213214

214215

215216
`Table` now implements `\Countable` and `\Iterator`, this allows you to count the number of rows
@@ -329,6 +330,47 @@ HiFolks\DataType\Table::__set_state(array(
329330
))
330331
```
331332

333+
### Grouping data with `groupBy()`
334+
335+
The `groupBy()` method groups rows by a field value and returns a **Table of Tables**. You can then use `get()` to access each group by its key and drill into the rows:
336+
337+
```php
338+
use HiFolks\DataType\Table;
339+
340+
$table = Table::make([
341+
['product' => 'Desk', 'price' => 200, 'active' => true],
342+
['product' => 'Chair', 'price' => 100, 'active' => true],
343+
['product' => 'Door', 'price' => 300, 'active' => false],
344+
['product' => 'Bookcase', 'price' => 150, 'active' => true],
345+
['product' => 'Door', 'price' => 100, 'active' => true],
346+
]);
347+
348+
$grouped = $table->groupBy('product');
349+
$grouped->count(); // 4 groups: Desk, Chair, Door, Bookcase
350+
351+
$deskItems = $grouped->get('Desk');
352+
$deskItems->count(); // 1
353+
354+
$doorItems = $grouped->get('Door');
355+
$doorItems->count(); // 2
356+
357+
$firstDoor = $doorItems->first();
358+
$firstDoor->get('price'); // 300
359+
$firstDoor->get('product'); // 'Door'
360+
$firstDoor->get('active'); // false
361+
```
362+
363+
Because `groupBy()` returns a Table, you can chain it with `where()` and `orderBy()` to build powerful queries. For example, to get the cheapest active product in each group:
364+
365+
```php
366+
$cheapest = $table
367+
->where('active', '=', true)
368+
->orderBy('price', 'asc')
369+
->groupBy('product');
370+
371+
$cheapest->get('Door')->first()->get('price'); // 100
372+
```
373+
332374
## Testing
333375

334376
```bash

composer.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,16 @@
3535
},
3636
"scripts": {
3737
"all": [
38-
"@test",
3938
"@format",
40-
"@phpstan"
39+
"@rector",
40+
"@phpstan",
41+
"@test"
4142
],
4243
"test": "vendor/bin/phpunit",
4344
"test-coverage": "vendor/bin/phpunit --coverage-html ./.build/tests",
4445
"phpstan": "vendor/bin/phpstan",
45-
"format": "vendor/bin/pint"
46+
"format": "vendor/bin/pint",
47+
"rector": "vendor/bin/rector --dry-run"
4648
},
4749
"config": {
4850
"sort-packages": true

doc/table.md

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,10 @@ var_export(
190190
Both orders can be inforced by using `asc` and `desc`
191191

192192
## Group By a column
193-
For example if you want to group the products by their `product` value you can use *groupBy()* method.
193+
The `groupBy()` method groups rows by a field value and returns a **Table of Tables**. Each entry in the returned Table is itself a Table containing all rows that share the same field value.
194194

195195
```php
196196
use HiFolks\DataType\Table;
197-
use HiFolks\DataType\Classes\Operation;
198197

199198
$table = Table::make([
200199
['product' => 'Desk', 'price' => 200, 'active' => true],
@@ -203,21 +202,62 @@ $table = Table::make([
203202
['product' => 'Bookcase', 'price' => 150, 'active' => true],
204203
['product' => 'Door', 'price' => 100, 'active' => true],
205204
]);
206-
var_export(
207-
$table->groupBy('product');
208-
);
209-
/*
210-
[
211-
['product' => 'Door', 'price' => 100, 'active' => true],
212-
['product' => 'Chair', 'price' => 100, 'active' => true],
213-
['product' => 'Bookcase', 'price' => 150, 'active' => true],
214-
['product' => 'Desk', 'price' => 200, 'active' => true]
215-
]
216-
*/
205+
$firstItem = $table->first();
206+
var_dump($firstItem);
207+
$grouped = $table->groupBy('product');
208+
209+
// $grouped contains 4 groups: Desk, Chair, Door, Bookcase
210+
// Each group is a Table with all matching rows
211+
count($grouped); // 4
212+
count($grouped->first()); // 1 (Desk has 1 row)
213+
214+
// Iterate over the groups
215+
foreach ($grouped as $key => $groupTable) {
216+
echo $key . ': ' . count($groupTable) . ' rows' . PHP_EOL;
217+
}
218+
// Output:
219+
// 'Desk': 1 rows
220+
// 'Chair': 1 rows
221+
// 'Door': 2 rows
222+
// 'Bookcase': 1 rows
223+
```
224+
225+
You can combine `groupBy()` with other methods. For example, to get the cheapest active product in each group:
226+
227+
```php
228+
$cheapest = $table
229+
->where('active', '=', true)
230+
->orderBy('price', 'asc')
231+
->groupBy('product');
232+
233+
// Each group's first row is the cheapest for that product
234+
$cheapest->first()->first()->get('price'); // 100 (Chair)
235+
```
236+
237+
### Key mode
238+
239+
The `groupBy()` method accepts an optional second parameter to control how the group keys are generated. The `GroupByKeyMode` enum provides three options:
240+
241+
```php
242+
use HiFolks\DataType\Enums\GroupByKeyMode;
243+
```
244+
245+
- **`GroupByKeyMode::VarExport`** (default): uses `var_export()` to generate keys. This avoids collisions between values like `true` and `1` or `false` and `0`, because they produce distinct string keys (`'true'` vs `'1'`).
246+
- **`GroupByKeyMode::Strval`**: uses `strval()` to generate keys. This produces cleaner string keys (e.g. `'Desk'` instead of `"'Desk'"`), but `true` and `1` will collide (both become `"1"`).
247+
- **`GroupByKeyMode::Int`**: uses `intval()` to generate integer keys. Useful for numeric grouping or when you want to truncate float values (e.g. `1.5` and `1.9` both become key `1`).
248+
249+
```php
250+
// Default (VarExport): keys are "'Desk'", "'Chair'", etc.
251+
$grouped = $table->groupBy('product');
252+
253+
// Strval: keys are "Desk", "Chair", etc.
254+
$grouped = $table->groupBy('product', GroupByKeyMode::Strval);
255+
256+
// Int: keys are integer values, e.g. 200, 100, 300, 150
257+
$grouped = $table->groupBy('price', GroupByKeyMode::Int);
217258
```
218259

219-
The `groupBy` method will always keep the first one that it finds, so if you want to keep only the product
220-
that costs the most you'll have to use the `orderBy` method before the `groupBy` method.
260+
Non-scalar field values (arrays, objects) and `null` values are skipped during grouping.
221261

222262

223263
## Transform a column with a custom function

examples/cheatsheet_table.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,13 @@
9999
echo PHP_EOL.'-------'.PHP_EOL;
100100
var_export($table->except('active', 'price'));
101101

102-
// Group the Table by the column
102+
// Group the Table by the column (returns a Table of Tables)
103103
echo PHP_EOL.'-------'.PHP_EOL;
104104
$result = $table->groupBy('product');
105-
var_export($result);
105+
foreach ($result as $groupKey => $groupTable) {
106+
echo "Group: $groupKey (" . count($groupTable) . " rows)" . PHP_EOL;
107+
}
108+
var_export($result->toArray());
106109

107110
// Order the table by the column using default value (desc)
108111
echo PHP_EOL.'-------'.PHP_EOL;
@@ -156,7 +159,11 @@
156159
['product' => 'Door', 'price' => 100, 'active' => true],
157160
]);
158161

159-
var_export($table->groupBy('product'));
162+
$grouped = $table->groupBy('product');
163+
foreach ($grouped as $groupKey => $groupTable) {
164+
echo "Group: $groupKey (" . count($groupTable) . " rows)" . PHP_EOL;
165+
}
166+
var_export($grouped->toArray());
160167

161168
print_r($table->toArray());
162169

src/Enums/GroupByKeyMode.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HiFolks\DataType\Enums;
6+
7+
enum GroupByKeyMode
8+
{
9+
case VarExport;
10+
case Strval;
11+
case Int;
12+
}

0 commit comments

Comments
 (0)