diff --git a/system/Helpers/array_helper.php b/system/Helpers/array_helper.php index 9b88cc2a0e0f..632e8a3a6c7c 100644 --- a/system/Helpers/array_helper.php +++ b/system/Helpers/array_helper.php @@ -218,3 +218,68 @@ function array_flatten_with_dots(iterable $array, string $id = ''): array return $flattened; } } + +if (! function_exists('array_group_by')) { + /** + * Groups all rows by their index values. Result's depth equals number of indexes + * + * @param array $array Data array (i.e. from query result) + * @param array $indexes Indexes to group by. Dot syntax used. Returns $array if empty + * @param bool $includeEmpty If true, null and '' are also added as valid keys to group + * + * @return array Result array where rows are grouped together by indexes values. + */ + function array_group_by(array $array, array $indexes, bool $includeEmpty = false): array + { + if ($indexes === []) { + return $array; + } + + $result = []; + + foreach ($array as $row) { + $result = _array_attach_indexed_value($result, $row, $indexes, $includeEmpty); + } + + return $result; + } +} + +if (! function_exists('_array_attach_indexed_value')) { + /** + * Used by `array_group_by` to recursively attach $row to the $indexes path of values found by + * `dot_array_search` + * + * @internal This should not be used on its own + */ + function _array_attach_indexed_value(array $result, array $row, array $indexes, bool $includeEmpty): array + { + if (($index = array_shift($indexes)) === null) { + $result[] = $row; + + return $result; + } + + $value = dot_array_search($index, $row); + + if (! is_scalar($value)) { + $value = ''; + } + + if (is_bool($value)) { + $value = (int) $value; + } + + if (! $includeEmpty && $value === '') { + return $result; + } + + if (! array_key_exists($value, $result)) { + $result[$value] = []; + } + + $result[$value] = _array_attach_indexed_value($result[$value], $row, $indexes, $includeEmpty); + + return $result; + } +} diff --git a/tests/system/Helpers/ArrayHelperTest.php b/tests/system/Helpers/ArrayHelperTest.php index a2db5b06e7d7..e19c410a32cd 100644 --- a/tests/system/Helpers/ArrayHelperTest.php +++ b/tests/system/Helpers/ArrayHelperTest.php @@ -498,4 +498,798 @@ public function arrayFlattenProvider(): iterable ], ]; } + + /** + * @dataProvider arrayGroupByIncludeEmptyProvider + */ + public function testArrayGroupByIncludeEmpty(array $indexes, array $data, array $expected): void + { + $actual = array_group_by($data, $indexes, true); + + $this->assertSame($expected, $actual, 'array including empty not the same'); + } + + /** + * @dataProvider arrayGroupByExcludeEmptyProvider + */ + public function testArrayGroupByExcludeEmpty(array $indexes, array $data, array $expected): void + { + $actual = array_group_by($data, $indexes, false); + + $this->assertSame($expected, $actual, 'array excluding empty not the same'); + } + + public function arrayGroupByIncludeEmptyProvider(): iterable + { + yield 'simple group-by test' => [ + ['color'], + [ + [ + 'id' => 1, + 'item' => 'ball', + 'color' => 'blue', + ], + [ + 'id' => 2, + 'item' => 'book', + 'color' => 'red', + ], + [ + 'id' => 3, + 'item' => 'bird', + 'age' => 5, + ], + [ + 'id' => 4, + 'item' => 'jeans', + 'color' => 'blue', + ], + ], + [ + 'blue' => [ + [ + 'id' => 1, + 'item' => 'ball', + 'color' => 'blue', + ], + [ + 'id' => 4, + 'item' => 'jeans', + 'color' => 'blue', + ], + ], + 'red' => [ + [ + 'id' => 2, + 'item' => 'book', + 'color' => 'red', + ], + ], + '' => [ + [ + 'id' => 3, + 'item' => 'bird', + 'age' => 5, + ], + ], + ], + ]; + + yield '2 index data' => [ + ['gender', 'country'], + [ + [ + 'id' => 1, + 'first_name' => 'Scarface', + 'gender' => 'Male', + 'country' => 'Germany', + ], + [ + 'id' => 2, + 'first_name' => 'Fletch', + 'gender' => 'Male', + 'country' => 'France', + ], + [ + 'id' => 3, + 'first_name' => 'Wrennie', + 'gender' => 'Female', + 'country' => 'France', + ], + [ + 'id' => 4, + 'first_name' => 'Virgilio', + 'gender' => 'Male', + 'country' => 'France', + ], + [ + 'id' => 5, + 'first_name' => 'Cathlene', + 'gender' => 'Polygender', + 'country' => 'France', + ], + [ + 'id' => 6, + 'first_name' => 'Far', + 'gender' => 'Male', + 'country' => 'Canada', + ], + [ + 'id' => 7, + 'first_name' => 'Dolores', + 'gender' => 'Female', + 'country' => 'Canada', + ], + [ + 'id' => 8, + 'first_name' => 'Sissy', + 'gender' => 'Female', + 'country' => null, + ], + [ + 'id' => 9, + 'first_name' => 'Chlo', + 'gender' => 'Female', + 'country' => 'France', + ], + [ + 'id' => 10, + 'first_name' => 'Gabbie', + 'gender' => 'Male', + 'country' => 'Canada', + ], + ], + [ + 'Male' => [ + 'Germany' => [ + [ + 'id' => 1, + 'first_name' => 'Scarface', + 'gender' => 'Male', + 'country' => 'Germany', + ], + ], + 'France' => [ + [ + 'id' => 2, + 'first_name' => 'Fletch', + 'gender' => 'Male', + 'country' => 'France', + ], + [ + 'id' => 4, + 'first_name' => 'Virgilio', + 'gender' => 'Male', + 'country' => 'France', + ], + ], + 'Canada' => [ + [ + 'id' => 6, + 'first_name' => 'Far', + 'gender' => 'Male', + 'country' => 'Canada', + ], + [ + 'id' => 10, + 'first_name' => 'Gabbie', + 'gender' => 'Male', + 'country' => 'Canada', + ], + ], + ], + 'Female' => [ + 'France' => [ + [ + 'id' => 3, + 'first_name' => 'Wrennie', + 'gender' => 'Female', + 'country' => 'France', + ], + [ + 'id' => 9, + 'first_name' => 'Chlo', + 'gender' => 'Female', + 'country' => 'France', + ], + ], + 'Canada' => [ + [ + 'id' => 7, + 'first_name' => 'Dolores', + 'gender' => 'Female', + 'country' => 'Canada', + ], + ], + '' => [ + [ + 'id' => 8, + 'first_name' => 'Sissy', + 'gender' => 'Female', + 'country' => null, + ], + ], + ], + 'Polygender' => [ + 'France' => [ + [ + 'id' => 5, + 'first_name' => 'Cathlene', + 'gender' => 'Polygender', + 'country' => 'France', + ], + ], + ], + ], + ]; + + yield 'nested data with dot syntax' => [ + ['gender', 'hr.department'], + [ + [ + 'id' => 1, + 'first_name' => 'Urbano', + 'gender' => null, + 'hr' => [ + 'country' => 'Canada', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 2, + 'first_name' => 'Case', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Marketing', + ], + ], + [ + 'id' => 3, + 'first_name' => 'Emera', + 'gender' => 'Female', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 4, + 'first_name' => 'Richy', + 'gender' => null, + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + [ + 'id' => 5, + 'first_name' => 'Mandy', + 'gender' => null, + 'hr' => [ + 'country' => 'France', + 'department' => 'Sales', + ], + ], + [ + 'id' => 6, + 'first_name' => 'Risa', + 'gender' => 'Female', + 'hr' => [ + 'country' => null, + 'department' => 'Engineering', + ], + ], + [ + 'id' => 7, + 'first_name' => 'Alfred', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 8, + 'first_name' => 'Tabby', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Marketing', + ], + ], + [ + 'id' => 9, + 'first_name' => 'Ario', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + [ + 'id' => 10, + 'first_name' => 'Somerset', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'Germany', + 'department' => 'Marketing', + ], + ], + ], + [ + '' => [ + 'Engineering' => [ + [ + 'id' => 1, + 'first_name' => 'Urbano', + 'gender' => null, + 'hr' => [ + 'country' => 'Canada', + 'department' => 'Engineering', + ], + ], + ], + 'Sales' => [ + [ + 'id' => 4, + 'first_name' => 'Richy', + 'gender' => null, + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + [ + 'id' => 5, + 'first_name' => 'Mandy', + 'gender' => null, + 'hr' => [ + 'country' => 'France', + 'department' => 'Sales', + ], + ], + ], + ], + 'Male' => [ + 'Marketing' => [ + [ + 'id' => 2, + 'first_name' => 'Case', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Marketing', + ], + ], + [ + 'id' => 8, + 'first_name' => 'Tabby', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Marketing', + ], + ], + [ + 'id' => 10, + 'first_name' => 'Somerset', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'Germany', + 'department' => 'Marketing', + ], + ], + ], + 'Engineering' => [ + [ + 'id' => 7, + 'first_name' => 'Alfred', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + ], + 'Sales' => [ + [ + 'id' => 9, + 'first_name' => 'Ario', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + ], + ], + 'Female' => [ + 'Engineering' => [ + [ + 'id' => 3, + 'first_name' => 'Emera', + 'gender' => 'Female', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 6, + 'first_name' => 'Risa', + 'gender' => 'Female', + 'hr' => [ + 'country' => null, + 'department' => 'Engineering', + ], + ], + ], + ], + ], + ]; + } + + public function arrayGroupByExcludeEmptyProvider(): iterable + { + yield 'simple group-by test' => [ + ['color'], + [ + [ + 'id' => 1, + 'item' => 'ball', + 'color' => 'blue', + ], + [ + 'id' => 2, + 'item' => 'book', + 'color' => 'red', + ], + [ + 'id' => 3, + 'item' => 'bird', + 'age' => 5, + ], + [ + 'id' => 4, + 'item' => 'jeans', + 'color' => 'blue', + ], + ], + [ + 'blue' => [ + [ + 'id' => 1, + 'item' => 'ball', + 'color' => 'blue', + ], + [ + 'id' => 4, + 'item' => 'jeans', + 'color' => 'blue', + ], + ], + 'red' => [ + [ + 'id' => 2, + 'item' => 'book', + 'color' => 'red', + ], + ], + ], + ]; + + yield '2 index data' => [ + ['gender', 'country'], + [ + [ + 'id' => 1, + 'first_name' => 'Scarface', + 'gender' => 'Male', + 'country' => 'Germany', + ], + [ + 'id' => 2, + 'first_name' => 'Fletch', + 'gender' => 'Male', + 'country' => 'France', + ], + [ + 'id' => 3, + 'first_name' => 'Wrennie', + 'gender' => 'Female', + 'country' => 'France', + ], + [ + 'id' => 4, + 'first_name' => 'Virgilio', + 'gender' => 'Male', + 'country' => 'France', + ], + [ + 'id' => 5, + 'first_name' => 'Cathlene', + 'gender' => 'Polygender', + 'country' => 'France', + ], + [ + 'id' => 6, + 'first_name' => 'Far', + 'gender' => 'Male', + 'country' => 'Canada', + ], + [ + 'id' => 7, + 'first_name' => 'Dolores', + 'gender' => 'Female', + 'country' => 'Canada', + ], + [ + 'id' => 8, + 'first_name' => 'Sissy', + 'gender' => 'Female', + 'country' => null, + ], + [ + 'id' => 9, + 'first_name' => 'Chlo', + 'gender' => 'Female', + 'country' => 'France', + ], + [ + 'id' => 10, + 'first_name' => 'Gabbie', + 'gender' => 'Male', + 'country' => 'Canada', + ], + ], + [ + 'Male' => [ + 'Germany' => [ + [ + 'id' => 1, + 'first_name' => 'Scarface', + 'gender' => 'Male', + 'country' => 'Germany', + ], + ], + 'France' => [ + [ + 'id' => 2, + 'first_name' => 'Fletch', + 'gender' => 'Male', + 'country' => 'France', + ], + [ + 'id' => 4, + 'first_name' => 'Virgilio', + 'gender' => 'Male', + 'country' => 'France', + ], + ], + 'Canada' => [ + [ + 'id' => 6, + 'first_name' => 'Far', + 'gender' => 'Male', + 'country' => 'Canada', + ], + [ + 'id' => 10, + 'first_name' => 'Gabbie', + 'gender' => 'Male', + 'country' => 'Canada', + ], + ], + ], + 'Female' => [ + 'France' => [ + [ + 'id' => 3, + 'first_name' => 'Wrennie', + 'gender' => 'Female', + 'country' => 'France', + ], + [ + 'id' => 9, + 'first_name' => 'Chlo', + 'gender' => 'Female', + 'country' => 'France', + ], + ], + 'Canada' => [ + [ + 'id' => 7, + 'first_name' => 'Dolores', + 'gender' => 'Female', + 'country' => 'Canada', + ], + ], + ], + 'Polygender' => [ + 'France' => [ + [ + 'id' => 5, + 'first_name' => 'Cathlene', + 'gender' => 'Polygender', + 'country' => 'France', + ], + ], + ], + ], + ]; + + yield 'nested data with dot syntax' => [ + ['gender', 'hr.department'], + [ + [ + 'id' => 1, + 'first_name' => 'Urbano', + 'gender' => null, + 'hr' => [ + 'country' => 'Canada', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 2, + 'first_name' => 'Case', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Marketing', + ], + ], + [ + 'id' => 3, + 'first_name' => 'Emera', + 'gender' => 'Female', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 4, + 'first_name' => 'Richy', + 'gender' => null, + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + [ + 'id' => 5, + 'first_name' => 'Mandy', + 'gender' => null, + 'hr' => [ + 'country' => 'France', + 'department' => 'Sales', + ], + ], + [ + 'id' => 6, + 'first_name' => 'Risa', + 'gender' => 'Female', + 'hr' => [ + 'country' => null, + 'department' => 'Engineering', + ], + ], + [ + 'id' => 7, + 'first_name' => 'Alfred', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 8, + 'first_name' => 'Tabby', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Marketing', + ], + ], + [ + 'id' => 9, + 'first_name' => 'Ario', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + [ + 'id' => 10, + 'first_name' => 'Somerset', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'Germany', + 'department' => 'Marketing', + ], + ], + ], + [ + 'Male' => [ + 'Marketing' => [ + [ + 'id' => 2, + 'first_name' => 'Case', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Marketing', + ], + ], + [ + 'id' => 8, + 'first_name' => 'Tabby', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Marketing', + ], + ], + [ + 'id' => 10, + 'first_name' => 'Somerset', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'Germany', + 'department' => 'Marketing', + ], + ], + ], + 'Engineering' => [ + [ + 'id' => 7, + 'first_name' => 'Alfred', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + ], + 'Sales' => [ + [ + 'id' => 9, + 'first_name' => 'Ario', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + ], + ], + 'Female' => [ + 'Engineering' => [ + [ + 'id' => 3, + 'first_name' => 'Emera', + 'gender' => 'Female', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 6, + 'first_name' => 'Risa', + 'gender' => 'Female', + 'hr' => [ + 'country' => null, + 'department' => 'Engineering', + ], + ], + ], + ], + ], + ]; + } } diff --git a/user_guide_src/source/changelogs/v4.4.0.rst b/user_guide_src/source/changelogs/v4.4.0.rst index df5b2c6da321..388690daecfe 100644 --- a/user_guide_src/source/changelogs/v4.4.0.rst +++ b/user_guide_src/source/changelogs/v4.4.0.rst @@ -87,6 +87,9 @@ Libraries Helpers and Functions ===================== +- **Array:** Added :php:func:`array_group_by()` helper function to group data + values together. Supports dot-notation syntax. + Others ====== diff --git a/user_guide_src/source/helpers/array_helper.rst b/user_guide_src/source/helpers/array_helper.rst index cf9517925234..730dae957222 100644 --- a/user_guide_src/source/helpers/array_helper.rst +++ b/user_guide_src/source/helpers/array_helper.rst @@ -111,3 +111,27 @@ The following functions are available: will be supplying an initial ``$id``, it will be prepended to all keys. .. literalinclude:: array_helper/011.php + +.. php:function:: array_group_by(array $array, array $indexes[, bool $includeEmpty = false]): array + + :param array $array: Data rows (most likely from query results) + :param array $indexes: Indexes to group values. Follows dot syntax + :param bool $includeEmpty: If true, ``null`` and ``''`` values are not filtered out + :rtype: array + :returns: An array grouped by indexes values + + This function allows you to group data rows together by index values. + The depth of returned array equals the number of indexes passed as parameter. + + The example shows some data (i.e. loaded from an API) with nested arrays. + + .. literalinclude:: array_helper/012.php + + We want to group them first by "gender", then by "hr.department" (max depth = 2). + First the result when excluding empty values: + + .. literalinclude:: array_helper/013.php + + And here the same code, but this time we want to include empty values: + + .. literalinclude:: array_helper/014.php diff --git a/user_guide_src/source/helpers/array_helper/012.php b/user_guide_src/source/helpers/array_helper/012.php new file mode 100644 index 000000000000..8aeb6d705c7d --- /dev/null +++ b/user_guide_src/source/helpers/array_helper/012.php @@ -0,0 +1,94 @@ + 1, + 'first_name' => 'Urbano', + 'gender' => null, + 'hr' => [ + 'country' => 'Canada', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 2, + 'first_name' => 'Case', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Marketing', + ], + ], + [ + 'id' => 3, + 'first_name' => 'Emera', + 'gender' => 'Female', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 4, + 'first_name' => 'Richy', + 'gender' => null, + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + [ + 'id' => 5, + 'first_name' => 'Mandy', + 'gender' => null, + 'hr' => [ + 'country' => 'France', + 'department' => 'Sales', + ], + ], + [ + 'id' => 6, + 'first_name' => 'Risa', + 'gender' => 'Female', + 'hr' => [ + 'country' => null, + 'department' => 'Engineering', + ], + ], + [ + 'id' => 7, + 'first_name' => 'Alfred', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 8, + 'first_name' => 'Tabby', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Marketing', + ], + ], + [ + 'id' => 9, + 'first_name' => 'Ario', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + [ + 'id' => 10, + 'first_name' => 'Somerset', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'Germany', + 'department' => 'Marketing', + ], + ], +]; diff --git a/user_guide_src/source/helpers/array_helper/013.php b/user_guide_src/source/helpers/array_helper/013.php new file mode 100644 index 000000000000..5b7e722fce75 --- /dev/null +++ b/user_guide_src/source/helpers/array_helper/013.php @@ -0,0 +1,81 @@ + [ + 'Marketing' => [ + [ + 'id' => 2, + 'first_name' => 'Case', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Marketing', + ], + ], + [ + 'id' => 8, + 'first_name' => 'Tabby', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Marketing', + ], + ], + [ + 'id' => 10, + 'first_name' => 'Somerset', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'Germany', + 'department' => 'Marketing', + ], + ], + ], + 'Engineering' => [ + [ + 'id' => 7, + 'first_name' => 'Alfred', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + ], + 'Sales' => [ + [ + 'id' => 9, + 'first_name' => 'Ario', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + ], + ], + 'Female' => [ + 'Engineering' => [ + [ + 'id' => 3, + 'first_name' => 'Emera', + 'gender' => 'Female', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 6, + 'first_name' => 'Risa', + 'gender' => 'Female', + 'hr' => [ + 'country' => null, + 'department' => 'Engineering', + ], + ], + ], + ], +]; diff --git a/user_guide_src/source/helpers/array_helper/014.php b/user_guide_src/source/helpers/array_helper/014.php new file mode 100644 index 000000000000..99089d236598 --- /dev/null +++ b/user_guide_src/source/helpers/array_helper/014.php @@ -0,0 +1,114 @@ + [ + 'Engineering' => [ + [ + 'id' => 1, + 'first_name' => 'Urbano', + 'gender' => null, + 'hr' => [ + 'country' => 'Canada', + 'department' => 'Engineering', + ], + ], + ], + 'Sales' => [ + [ + 'id' => 4, + 'first_name' => 'Richy', + 'gender' => null, + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + [ + 'id' => 5, + 'first_name' => 'Mandy', + 'gender' => null, + 'hr' => [ + 'country' => 'France', + 'department' => 'Sales', + ], + ], + ], + ], + 'Male' => [ + 'Marketing' => [ + [ + 'id' => 2, + 'first_name' => 'Case', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Marketing', + ], + ], + [ + 'id' => 8, + 'first_name' => 'Tabby', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Marketing', + ], + ], + [ + 'id' => 10, + 'first_name' => 'Somerset', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'Germany', + 'department' => 'Marketing', + ], + ], + ], + 'Engineering' => [ + [ + 'id' => 7, + 'first_name' => 'Alfred', + 'gender' => 'Male', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + ], + 'Sales' => [ + [ + 'id' => 9, + 'first_name' => 'Ario', + 'gender' => 'Male', + 'hr' => [ + 'country' => null, + 'department' => 'Sales', + ], + ], + ], + ], + 'Female' => [ + 'Engineering' => [ + [ + 'id' => 3, + 'first_name' => 'Emera', + 'gender' => 'Female', + 'hr' => [ + 'country' => 'France', + 'department' => 'Engineering', + ], + ], + [ + 'id' => 6, + 'first_name' => 'Risa', + 'gender' => 'Female', + 'hr' => [ + 'country' => null, + 'department' => 'Engineering', + ], + ], + ], + ], +];