Skip to content

Commit 7f50476

Browse files
author
Alaa Sarhan
committed
Added support for FLAG_NUMERIC_NOT_FLATTENED flag. Closes #1.
1 parent ab77af1 commit 7f50476

File tree

3 files changed

+184
-19
lines changed

3 files changed

+184
-19
lines changed

README.md

+104-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ and joining them with a customizable separator to from fully-qualified keys in t
99
composer require sarhan/php-flatten
1010
```
1111

12-
## Examples
12+
## Usage
13+
14+
**Example 1**
1315

1416
```php
1517
use Sarhan\Flatten;
@@ -31,6 +33,7 @@ $array = Flatten::flatten($multiArray);
3133
)
3234
*/
3335
```
36+
**Example 2**
3437

3538
Custom Separator and initial prefix
3639
```php
@@ -57,6 +60,7 @@ $allowAccess = Flatten::flatten($allowAccess, '/', '/');
5760
}
5861
*/
5962
```
63+
**Example 3**
6064

6165
Notice that the prefix will not be separated in FQkeys. If it should be separated, separator must be appeneded to the prefix string.
6266
```php
@@ -84,8 +88,15 @@ $uris = Flatten::flatten($api, '/', 'https://api.dummyhost.domain/');
8488
*/
8589
```
8690

87-
Numeric arrays will be also flattened using the numeric keys.
91+
**Example 4**
92+
93+
Numeric keys are treated as associative keys.
94+
95+
**Note:** This behavior can be changed using flags. See [FLAG_NUMERIC_NOT_FLATTENED](#numeric_not_flattened)
96+
8897
```php
98+
use Sarhan\Flatten;
99+
89100
$nutrition = [
90101
'nutrition',
91102
'fruits' => [ 'oranges', 'apple', 'banana' ],
@@ -106,3 +117,94 @@ $nutrition = Flatten::flatten($nutrition, '-');
106117
)
107118
*/
108119
```
120+
121+
### Flags
122+
123+
<a name="numeric_not_flattened"></a>**FLAG_NUMERIC_NOT_FLATTENED**
124+
125+
Turns off flattening values with numeric (integer) keys.
126+
127+
Those values will be wrapped in an array (preserving their keys) and associated to the parent FQK.
128+
```php
129+
use Sarhan\Flatten;
130+
131+
$examples = [
132+
'templates' => [
133+
['lang' => 'js', 'template' => "console.log('%s');"],
134+
['lang' => 'php', 'template' => 'echo "%s";']
135+
],
136+
'values' => [3 => 'hello world', 5 => 'what is your name?']
137+
];
138+
139+
$flattened = Flatten::flatten($examples, '.', 'examples.', Flatten::FLAG_NUMERIC_NOT_FLATTENED);
140+
141+
/* print_r($flattened):
142+
Array
143+
(
144+
[examples.templates] => Array
145+
(
146+
[0] => Array
147+
(
148+
[lang] => js
149+
[template] => console.log('%s');
150+
)
151+
152+
[1] => Array
153+
(
154+
[lang] => php
155+
[template] => echo "%s";
156+
)
157+
158+
)
159+
160+
[examples.values] => Array
161+
(
162+
[3] => hello world
163+
[5] => what is your name?
164+
)
165+
166+
)
167+
*/
168+
169+
```
170+
Top level numeric (integer) keys will also be returned into an array assigned to the passed prefix.
171+
```php
172+
use Sarhan\Flatten;
173+
174+
$seats = [
175+
'A1',
176+
'A2',
177+
'B1',
178+
'B2',
179+
'_reserved' => ['A1', 'B1'],
180+
'_blocked' => ['B2']
181+
];
182+
183+
$flattened = Flatten::flatten($seats, '_', 'seats', Flatten::FLAG_NUMERIC_NOT_FLATTENED);
184+
185+
/* print_r($flattened)
186+
187+
Array
188+
(
189+
[seats] => Array
190+
(
191+
[0] => A1
192+
[1] => A2
193+
[2] => B1
194+
[3] => B2
195+
)
196+
197+
[seats_reserved] => Array
198+
(
199+
[0] => A1
200+
[1] => B1
201+
)
202+
203+
[seats_blocked] => Array
204+
(
205+
[0] => B2
206+
)
207+
)
208+
209+
*/
210+
```

src/Flatten.php

+41-17
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,69 @@
88
class Flatten
99
{
1010
/**
11-
* Flattens a variable, possible traversable, into a one-dimensional array, recursively.
12-
*
13-
* Each key in the returned one-dimensional array is the join of all keys leading to each value, in all dimensions,
14-
* separated by a given separator. That is a fully-qualified key.
11+
* Turn off flattening values with integer keys.
12+
*/
13+
const FLAG_NUMERIC_NOT_FLATTENED = 0b1;
14+
15+
/**
16+
* Flattens a variable, possibly traversable, into a one-dimensional array, recursively.
1517
*
16-
* Non-traversable values will be returned as-is, after being put into the final array with the fully-qualified key.
18+
* Each key (fully-qualified key or FQK) in the returned one-dimensional array is the join of all keys leading to
19+
* each (non-traversable) value, in all dimensions, separated by a given separator.
1720
*
18-
* An initial prefix can be optionally provided, but it will not separated using the separator.
21+
* An initial prefix can be optionally provided, but it will not be separated with the specified separator.
1922
*
2023
* @param mixed $var
2124
* @param string $separator
2225
* @param string $prefix
26+
* @param int $flags
2327
* @return array One-dimensional array containing all values from all possible traversable dimensions in given input.
28+
* @see Flatten::FLAG_NUMERIC_NOT_FLATTENED
2429
*/
25-
public static function flatten($var, $separator = '.', $prefix = '')
30+
public static function flatten($var, $separator = '.', $prefix = '', $flags = 0)
2631
{
2732
$flattened = [];
28-
foreach (self::flattenGenerator($var, $separator, '') as $key => $value) {
33+
foreach (self::flattenGenerator($var, $separator, '', $flags) as $key => $value) {
2934
$flattened[$prefix . $key] = $value;
3035
}
3136
return $flattened;
3237
}
3338

34-
private static function flattenGenerator($var, $separator, $prefix = '')
39+
private static function flattenGenerator($var, $separator, $prefix = '', $flags)
3540
{
36-
if (self::canTraverse($var)) {
37-
$prefix .= (empty($prefix) ? '' : $separator);
38-
foreach ($var as $key => $value) {
39-
foreach (self::flattenGenerator($value, $separator, $prefix .$key) as $k => $v) {
40-
yield $k => $v;
41-
}
42-
}
43-
} else {
41+
if (!self::canTraverse($var)) {
4442
yield $prefix => $var;
43+
return;
44+
}
45+
46+
if ($flags & self::FLAG_NUMERIC_NOT_FLATTENED) {
47+
list ($values, $var) = self::filterNumericKeysAndGetValues($var);
48+
yield $prefix => $values;
49+
}
50+
51+
$prefix .= (empty($prefix) ? '' : $separator);
52+
foreach ($var as $key => $value) {
53+
foreach (self::flattenGenerator($value, $separator, $prefix . $key, $flags) as $k => $v) {
54+
yield $k => $v;
55+
}
4556
}
4657
}
4758

4859
private static function canTraverse($var)
4960
{
5061
return is_array($var) || ($var instanceof \Traversable);
5162
}
63+
64+
private static function filterNumericKeysAndGetValues($var)
65+
{
66+
$values = [];
67+
$var = array_filter($var, function($value, $key) use (&$values) {
68+
if (is_int($key)) {
69+
$values[$key] = $value;
70+
return false;
71+
}
72+
return true;
73+
}, ARRAY_FILTER_USE_BOTH);
74+
return [$values, $var];
75+
}
5276
}

test/FlattenTest.php

+39
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,43 @@ public function testFlattenTraversableWithSeparatorAndPrefix($var, $separator, $
150150
$output = Flatten::flatten($var, $separator, $prefix);
151151
$this->assertEquals($expectedOutput, $output);
152152
}
153+
154+
public function flattenWithFlagsProvidor()
155+
{
156+
return [
157+
'NUMERIC_NOT_FLATTENED' => [
158+
[
159+
1,
160+
2,
161+
100 => [3, 4],
162+
'numericOnly' => ['A', 'B', 'C', 'D'],
163+
'mixed' => ['A', 'B', 'digit' => 0],
164+
'multidimensional' => [2 => 'A', 5 => 'B', [8 => 'C', 9 => 'D', 'digit' => 0], 'digit' => 1, []],
165+
'emptyArray' => []
166+
],
167+
'.',
168+
'_',
169+
Flatten::FLAG_NUMERIC_NOT_FLATTENED,
170+
[
171+
'_' => [1, 2, 100 => [3, 4]],
172+
'_numericOnly' => ['A', 'B', 'C', 'D'],
173+
'_mixed' => ['A', 'B'],
174+
'_mixed.digit' => 0,
175+
'_multidimensional' => [2 => 'A', 5 => 'B', [8 => 'C', 9 => 'D', 'digit' => 0], []],
176+
'_multidimensional.digit' => 1,
177+
'_emptyArray' => []
178+
]
179+
]
180+
];
181+
}
182+
183+
/**
184+
* @covers Flatten::flatten
185+
* @dataProvider flattenWithFlagsProvidor
186+
*/
187+
public function testFlattenWithFlags($var, $separator, $prefix, $flags, $expectedOutput)
188+
{
189+
$output = Flatten::flatten($var, $separator, $prefix, $flags);
190+
$this->assertEquals($expectedOutput, $output);
191+
}
153192
}

0 commit comments

Comments
 (0)