Skip to content

Commit

Permalink
Merge pull request #2 from AlaaSarhan/issue_1_numeric_not_flattened
Browse files Browse the repository at this point in the history
Added support for FLAG_NUMERIC_NOT_FLATTENED flag.
  • Loading branch information
Alaa Sarhan authored Oct 15, 2016
2 parents ab77af1 + 7f50476 commit d622c2b
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 19 deletions.
106 changes: 104 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ and joining them with a customizable separator to from fully-qualified keys in t
composer require sarhan/php-flatten
```

## Examples
## Usage

**Example 1**

```php
use Sarhan\Flatten;
Expand All @@ -31,6 +33,7 @@ $array = Flatten::flatten($multiArray);
)
*/
```
**Example 2**

Custom Separator and initial prefix
```php
Expand All @@ -57,6 +60,7 @@ $allowAccess = Flatten::flatten($allowAccess, '/', '/');
}
*/
```
**Example 3**

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

Numeric arrays will be also flattened using the numeric keys.
**Example 4**

Numeric keys are treated as associative keys.

**Note:** This behavior can be changed using flags. See [FLAG_NUMERIC_NOT_FLATTENED](#numeric_not_flattened)

```php
use Sarhan\Flatten;

$nutrition = [
'nutrition',
'fruits' => [ 'oranges', 'apple', 'banana' ],
Expand All @@ -106,3 +117,94 @@ $nutrition = Flatten::flatten($nutrition, '-');
)
*/
```

### Flags

<a name="numeric_not_flattened"></a>**FLAG_NUMERIC_NOT_FLATTENED**

Turns off flattening values with numeric (integer) keys.

Those values will be wrapped in an array (preserving their keys) and associated to the parent FQK.
```php
use Sarhan\Flatten;

$examples = [
'templates' => [
['lang' => 'js', 'template' => "console.log('%s');"],
['lang' => 'php', 'template' => 'echo "%s";']
],
'values' => [3 => 'hello world', 5 => 'what is your name?']
];

$flattened = Flatten::flatten($examples, '.', 'examples.', Flatten::FLAG_NUMERIC_NOT_FLATTENED);

/* print_r($flattened):
Array
(
[examples.templates] => Array
(
[0] => Array
(
[lang] => js
[template] => console.log('%s');
)

[1] => Array
(
[lang] => php
[template] => echo "%s";
)

)

[examples.values] => Array
(
[3] => hello world
[5] => what is your name?
)

)
*/

```
Top level numeric (integer) keys will also be returned into an array assigned to the passed prefix.
```php
use Sarhan\Flatten;

$seats = [
'A1',
'A2',
'B1',
'B2',
'_reserved' => ['A1', 'B1'],
'_blocked' => ['B2']
];

$flattened = Flatten::flatten($seats, '_', 'seats', Flatten::FLAG_NUMERIC_NOT_FLATTENED);

/* print_r($flattened)

Array
(
[seats] => Array
(
[0] => A1
[1] => A2
[2] => B1
[3] => B2
)

[seats_reserved] => Array
(
[0] => A1
[1] => B1
)

[seats_blocked] => Array
(
[0] => B2
)
)

*/
```
58 changes: 41 additions & 17 deletions src/Flatten.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,69 @@
class Flatten
{
/**
* Flattens a variable, possible traversable, into a one-dimensional array, recursively.
*
* Each key in the returned one-dimensional array is the join of all keys leading to each value, in all dimensions,
* separated by a given separator. That is a fully-qualified key.
* Turn off flattening values with integer keys.
*/
const FLAG_NUMERIC_NOT_FLATTENED = 0b1;

/**
* Flattens a variable, possibly traversable, into a one-dimensional array, recursively.
*
* Non-traversable values will be returned as-is, after being put into the final array with the fully-qualified key.
* Each key (fully-qualified key or FQK) in the returned one-dimensional array is the join of all keys leading to
* each (non-traversable) value, in all dimensions, separated by a given separator.
*
* An initial prefix can be optionally provided, but it will not separated using the separator.
* An initial prefix can be optionally provided, but it will not be separated with the specified separator.
*
* @param mixed $var
* @param string $separator
* @param string $prefix
* @param int $flags
* @return array One-dimensional array containing all values from all possible traversable dimensions in given input.
* @see Flatten::FLAG_NUMERIC_NOT_FLATTENED
*/
public static function flatten($var, $separator = '.', $prefix = '')
public static function flatten($var, $separator = '.', $prefix = '', $flags = 0)
{
$flattened = [];
foreach (self::flattenGenerator($var, $separator, '') as $key => $value) {
foreach (self::flattenGenerator($var, $separator, '', $flags) as $key => $value) {
$flattened[$prefix . $key] = $value;
}
return $flattened;
}

private static function flattenGenerator($var, $separator, $prefix = '')
private static function flattenGenerator($var, $separator, $prefix = '', $flags)
{
if (self::canTraverse($var)) {
$prefix .= (empty($prefix) ? '' : $separator);
foreach ($var as $key => $value) {
foreach (self::flattenGenerator($value, $separator, $prefix .$key) as $k => $v) {
yield $k => $v;
}
}
} else {
if (!self::canTraverse($var)) {
yield $prefix => $var;
return;
}

if ($flags & self::FLAG_NUMERIC_NOT_FLATTENED) {
list ($values, $var) = self::filterNumericKeysAndGetValues($var);
yield $prefix => $values;
}

$prefix .= (empty($prefix) ? '' : $separator);
foreach ($var as $key => $value) {
foreach (self::flattenGenerator($value, $separator, $prefix . $key, $flags) as $k => $v) {
yield $k => $v;
}
}
}

private static function canTraverse($var)
{
return is_array($var) || ($var instanceof \Traversable);
}

private static function filterNumericKeysAndGetValues($var)
{
$values = [];
$var = array_filter($var, function($value, $key) use (&$values) {
if (is_int($key)) {
$values[$key] = $value;
return false;
}
return true;
}, ARRAY_FILTER_USE_BOTH);
return [$values, $var];
}
}
39 changes: 39 additions & 0 deletions test/FlattenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,43 @@ public function testFlattenTraversableWithSeparatorAndPrefix($var, $separator, $
$output = Flatten::flatten($var, $separator, $prefix);
$this->assertEquals($expectedOutput, $output);
}

public function flattenWithFlagsProvidor()
{
return [
'NUMERIC_NOT_FLATTENED' => [
[
1,
2,
100 => [3, 4],
'numericOnly' => ['A', 'B', 'C', 'D'],
'mixed' => ['A', 'B', 'digit' => 0],
'multidimensional' => [2 => 'A', 5 => 'B', [8 => 'C', 9 => 'D', 'digit' => 0], 'digit' => 1, []],
'emptyArray' => []
],
'.',
'_',
Flatten::FLAG_NUMERIC_NOT_FLATTENED,
[
'_' => [1, 2, 100 => [3, 4]],
'_numericOnly' => ['A', 'B', 'C', 'D'],
'_mixed' => ['A', 'B'],
'_mixed.digit' => 0,
'_multidimensional' => [2 => 'A', 5 => 'B', [8 => 'C', 9 => 'D', 'digit' => 0], []],
'_multidimensional.digit' => 1,
'_emptyArray' => []
]
]
];
}

/**
* @covers Flatten::flatten
* @dataProvider flattenWithFlagsProvidor
*/
public function testFlattenWithFlags($var, $separator, $prefix, $flags, $expectedOutput)
{
$output = Flatten::flatten($var, $separator, $prefix, $flags);
$this->assertEquals($expectedOutput, $output);
}
}

0 comments on commit d622c2b

Please sign in to comment.