Skip to content

Commit d44c73c

Browse files
committed
New method ArrayHelper::nestedArray() to transform simple array into multidimensional array
Complex paths allowed for `ArrayHelper::traverse*` methods
1 parent 4c32d6b commit d44c73c

File tree

3 files changed

+162
-13
lines changed

3 files changed

+162
-13
lines changed

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. This projec
44
to [Semantic Versioning] (http://semver.org/). For change log format,
55
use [Keep a Changelog] (http://keepachangelog.com/).
66

7+
## [1.11.0] - 2025-03-27
8+
9+
### Added
10+
11+
- New method `ArrayHelper::nestedArray()` to transform simple array into multidimensional array
12+
13+
### Changed
14+
15+
- Complex paths allowed for `ArrayHelper::traverse*` methods
16+
717
## [1.10.0] - 2025-03-14
818

919
### Changed

src/ArrayHelper.php

+90-9
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
use SimpleXMLElement;
2222
use Traversable;
2323

24-
use function array_is_list;
25-
2624
/**
2725
* Class ArrayHelper.
2826
*
@@ -242,6 +240,17 @@ public static function mergeRecursive(array ...$arrays): array
242240
return $arraySrc;
243241
}
244242

243+
private static function parseKey(string $key): array
244+
{
245+
$normalized = preg_replace('/\.([^.\[\]]+)/', '[$1]', $key);
246+
preg_match_all('/([^\[\]]+)/', $normalized, $matches);
247+
if (substr($key, -2) == '[]') {
248+
$matches[1][] = '';
249+
}
250+
251+
return $matches[1];
252+
}
253+
245254
/**
246255
* Traverse array with path and return if path exists.
247256
*
@@ -252,7 +261,7 @@ public static function mergeRecursive(array ...$arrays): array
252261
*/
253262
public static function traverseExists(iterable &$mixed, string $path): bool
254263
{
255-
$path = explode('.', $path);
264+
$path = self::parseKey($path);
256265

257266
$temp = &$mixed;
258267
foreach ($path as $key) {
@@ -287,7 +296,7 @@ public static function traverseExists(iterable &$mixed, string $path): bool
287296
*/
288297
public static function traverseGet(iterable &$mixed, string $path, $default = null)
289298
{
290-
$path = explode('.', $path);
299+
$path = self::parseKey($path);
291300

292301
$temp = &$mixed;
293302
foreach ($path as $key) {
@@ -322,14 +331,21 @@ public static function traverseGet(iterable &$mixed, string $path, $default = nu
322331
*/
323332
public static function traverseSet(iterable &$mixed, string $path, $value): bool
324333
{
325-
$path = explode('.', $path);
334+
$path = self::parseKey($path);
326335

327336
$temp = &$mixed;
328337
foreach ($path as $key) {
329338
if (null !== $temp && !is_iterable($temp)) {
330339
return false;
331340
}
332341

342+
if ($key === '') {
343+
$temp[] = null;
344+
end($temp);
345+
$temp = &$temp[key($temp)];
346+
continue;
347+
}
348+
333349
if (!isset($temp[$key])) {
334350
$temp[$key] = null;
335351
}
@@ -351,7 +367,7 @@ public static function traverseSet(iterable &$mixed, string $path, $value): bool
351367
*/
352368
public static function simpleArray(array $array, ?string $prefix = null): array
353369
{
354-
$metaData = [];
370+
$output = [];
355371

356372
foreach ($array as $key => $value) {
357373
// Prefix key if necessary
@@ -360,13 +376,78 @@ public static function simpleArray(array $array, ?string $prefix = null): array
360376
}
361377

362378
if (is_array($value)) {
363-
$metaData = array_merge($metaData, self::simpleArray($value, $key));
379+
$output = array_merge($output, self::simpleArray($value, $key));
364380
continue;
365381
}
366382

367-
$metaData[$key] = $value;
383+
$output[$key] = $value;
384+
}
385+
386+
return $output;
387+
}
388+
389+
/**
390+
* Transform simple level array to multidimensional.
391+
*
392+
* @param array $array
393+
*
394+
* @return array
395+
*/
396+
public static function nestedArray(array $array): array
397+
{
398+
$output = [];
399+
400+
foreach ($array as $key => $value) {
401+
// Normalize dot notation to bracket notation
402+
$normalized = preg_replace('/\.([^.\[\]]+)/', '[$1]', $key);
403+
404+
// Extract segments (ex: foo[bar][baz] → ['foo', 'bar', 'baz'])
405+
preg_match_all('/([^\[\]]+)/', $normalized, $matches);
406+
$segments = $matches[1];
407+
408+
$ref = &$output;
409+
410+
foreach ($segments as $i => $segment) {
411+
$isLast = ($i === count($segments) - 1);
412+
413+
if ($isLast) {
414+
if ($segment === '') {
415+
$ref[] = $value;
416+
continue;
417+
}
418+
419+
if (is_numeric($segment)) {
420+
$ref[(int)$segment] = $value;
421+
continue;
422+
}
423+
424+
$ref[$segment] = $value;
425+
continue;
426+
}
427+
428+
if ($segment === '') {
429+
$ref[] = [];
430+
end($ref);
431+
$ref = &$ref[key($ref)];
432+
continue;
433+
}
434+
435+
if (is_numeric($segment)) {
436+
$segment = (int)$segment;
437+
if (!isset($ref[$segment]) || !is_array($ref[$segment])) {
438+
$ref[$segment] = [];
439+
}
440+
$ref = &$ref[$segment];
441+
continue;
442+
}
443+
444+
if (!isset($ref[$segment]) || !is_array($ref[$segment])) {
445+
$ref[$segment] = [];
446+
}
447+
$ref = &$ref[$segment];
448+
}
368449
}
369450

370-
return $metaData;
451+
return $output;
371452
}
372453
}

tests/ArrayHelperTest.php

+62-4
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ public function testTraverseExists()
299299
$this->assertTrue(ArrayHelper::traverseExists($tArray, 'foo2.foo6.foo9'));
300300
$this->assertFalse(ArrayHelper::traverseExists($tArray, 'bar'));
301301
$this->assertFalse(ArrayHelper::traverseExists($tArray, 'foo2.foo999.foo8'));
302+
$this->assertFalse(ArrayHelper::traverseExists($tArray, 'foo2[foo999][foo8]'));
302303
$this->assertFalse(ArrayHelper::traverseExists($tArray, 'foo3.foo4'));
303304
$this->assertFalse(ArrayHelper::traverseExists($tArray, 'foo.bar.foo'));
304305
$this->assertFalse(ArrayHelper::traverseExists($tArray, 'bar.foo'));
@@ -324,6 +325,7 @@ public function testTraverseGet()
324325
$this->assertEquals(null, ArrayHelper::traverseGet($tArray, 'foo2.foo6.foo9'));
325326
$this->assertEquals(null, ArrayHelper::traverseGet($tArray, 'foo2.foo999.foo8'));
326327
$this->assertEquals('bar', ArrayHelper::traverseGet($tArray, 'foo2.foo999.foo8', 'bar'));
328+
$this->assertEquals('bar', ArrayHelper::traverseGet($tArray, 'foo2[foo999].foo8', 'bar'));
327329
$this->assertEquals(null, ArrayHelper::traverseGet($tArray, 'foo3.foo4'));
328330
$this->assertEquals(null, ArrayHelper::traverseGet($tArray, 'foo.bar.foo'));
329331
$this->assertEquals('bar', ArrayHelper::traverseGet($tArray, 'bar.foo', 'bar'));
@@ -344,13 +346,37 @@ public function testTraverseSet()
344346
];
345347

346348
$this->assertTrue(ArrayHelper::traverseSet($tArray, 'foo', 'bob'));
347-
$this->assertEquals('bob', ArrayHelper::traverseGet($tArray, 'foo'));
348349
$this->assertTrue(ArrayHelper::traverseSet($tArray, 'foo2.foo6.foo8', 'bob8'));
349-
$this->assertEquals('bob8', ArrayHelper::traverseGet($tArray, 'foo2.foo6.foo8'));
350350
$this->assertTrue(ArrayHelper::traverseSet($tArray, 'foo2.foo999.foo8', 'bob999'));
351-
$this->assertEquals('bob999', ArrayHelper::traverseGet($tArray, 'foo2.foo999.foo8'));
352351
$this->assertFalse(ArrayHelper::traverseSet($tArray, 'foo.bar.foo', 'bar'));
353-
$this->assertTrue(ArrayHelper::traverseSet($tArray, 'bar.foo', 'baz'));
352+
$this->assertTrue(ArrayHelper::traverseSet($tArray, 'bar[foo]', 'baz'));
353+
$this->assertTrue(ArrayHelper::traverseSet($tArray, 'foo3[]', 'foo3.1'));
354+
$this->assertTrue(ArrayHelper::traverseSet($tArray, 'foo3[]', 'foo3.2'));
355+
356+
$this->assertEquals(
357+
[
358+
'foo' => 'bob',
359+
'foo2' => [
360+
'foo3' => ['foo4' => 'bar4'],
361+
'foo5' => 'bar5',
362+
'foo6' => [
363+
'foo7' => 'bar7',
364+
'foo8' => 'bob8',
365+
],
366+
'foo999' => [
367+
'foo8' => 'bob999'
368+
]
369+
],
370+
'bar' => [
371+
'foo' => 'baz',
372+
],
373+
'foo3' => [
374+
'foo3.1',
375+
'foo3.2'
376+
]
377+
],
378+
$tArray,
379+
);
354380
}
355381

356382
public function testSimpleArray()
@@ -388,4 +414,36 @@ public function testSimpleArray()
388414
ArrayHelper::simpleArray($arr, 'prefix'),
389415
);
390416
}
417+
418+
public function testNestedArray()
419+
{
420+
$arr = [
421+
'foo' => 'bar',
422+
'foo2.foo3.foo4' => 'bar4',
423+
'foo2.foo5' => 'bar5',
424+
'foo2.foo6.foo7' => 'bar7',
425+
'foo2.foo6[foo8]' => 'bar8',
426+
'foo2.foo6.foo9[0]' => 'bar9',
427+
'foo2[foo6].foo9[1]' => 'bar9bis',
428+
];
429+
430+
$this->assertEquals(
431+
[
432+
'foo' => 'bar',
433+
'foo2' => [
434+
'foo3' => ['foo4' => 'bar4'],
435+
'foo5' => 'bar5',
436+
'foo6' => [
437+
'foo7' => 'bar7',
438+
'foo8' => 'bar8',
439+
'foo9' => [
440+
'bar9',
441+
'bar9bis'
442+
]
443+
],
444+
],
445+
],
446+
ArrayHelper::nestedArray($arr),
447+
);
448+
}
391449
}

0 commit comments

Comments
 (0)