STEPS
- Convert the paths into arrays.
- Find the maximum path depth.
- Group paths based on their level of depth.
- Merge groupings in a hierarchical format.
- Reset the result's array indices/keys.
- Print output.
$rawPaths = [
0 => "test",
1 => "files",
2 => "files/2",
3 => "files/2/Blocks",
4 => "files/2/Blocks/thumbs",
5 => "files/karma",
6 => "files/karma/foo",
7 => "files/karma/foo/bar",
8 => "files/shares",
];
// 1. Convert the paths into arrays.
$paths = array_map(function ($path) {
return explode("/", $path);
}, $rawPaths);
// 2. Find the maximum path depth.
$maxDepth = 0;
for ($i = 0; $i < count($rawPaths); $i++) {
if (($count = substr_count($rawPaths[$i], "/")) > $maxDepth) {
$maxDepth = $count;
}
}
// 3. Group paths based on their level of depth.
$groupings = [];
for ($j = 0; $j <= $maxDepth; $j++) {
$groupings[] = array_filter($paths, function ($p) use ($j) {
return count($p) === ($j + 1);
});
}
// 4. Merge groupings in a hierarchical format.
$result = [];
for ($depth = 0; $depth <= $maxDepth; $depth++) {
array_map(function ($grouping) use (&$result, $depth) {
setNode($result, $grouping, $depth);
}, $groupings[$depth]);
}
function setTree(&$grouping, &$depth): array
{
$pathBuilder = $grouping[$depth];
for ($i = 0; $i < $depth; $i++) {
$pathBuilder = $grouping[$depth - ($i + 1)] . "/" . $pathBuilder;
}
return [
"label" => $grouping[$depth],
"path" => $pathBuilder,
"children" => []
];
}
function setNode(&$result, $grouping, $depth)
{
$node = &$result[$grouping[0]];
if ($depth) {
for ($i = ($depth - 1); $i >= 0; $i--) {
$node = &$node["children"][$grouping[$depth - $i]];
}
}
$node = setTree($grouping, $depth);
}
// 5. Reset the result's array indices/keys.
$arrayIterator = new \RecursiveArrayIterator(array_values($result));
$recursiveIterator = new \RecursiveIteratorIterator($arrayIterator, \RecursiveIteratorIterator::SELF_FIRST);
foreach ($recursiveIterator as $key => $value) {
if (is_array($value) && ($key === "children")) {
$value = array_values($value);
// Get the current depth and traverse back up the tree, saving the modifications.
$currentDepth = $recursiveIterator->getDepth();
for ($subDepth = $currentDepth; $subDepth >= 0; $subDepth--) {
// Get the current level iterator.
$subIterator = $recursiveIterator->getSubIterator($subDepth);
// If we are on the level we want to change, use the replacements ($value), otherwise set the key to the parent iterators value.
$subIterator->offsetSet($subIterator->key(), ($subDepth === $currentDepth ? $value : $recursiveIterator->getSubIterator(($subDepth + 1))->getArrayCopy()));
}
}
}
// 6. Print output.
var_export($recursiveIterator->getArrayCopy());
// Output
array (
0 =>
array (
'label' => 'test',
'path' => 'test',
'children' =>
array (
),
),
1 =>
array (
'label' => 'files',
'path' => 'files',
'children' =>
array (
0 =>
array (
'label' => '2',
'path' => 'files/2',
'children' =>
array (
0 =>
array (
'label' => 'Blocks',
'path' => 'files/2/Blocks',
'children' =>
array (
0 =>
array (
'label' => 'thumbs',
'path' => 'files/2/Blocks/thumbs',
'children' =>
array (
),
),
),
),
),
),
1 =>
array (
'label' => 'karma',
'path' => 'files/karma',
'children' =>
array (
0 =>
array (
'label' => 'foo',
'path' => 'files/karma/foo',
'children' =>
array (
0 =>
array (
'label' => 'bar',
'path' => 'files/karma/foo/bar',
'children' =>
array (
),
),
),
),
),
),
2 =>
array (
'label' => 'shares',
'path' => 'files/shares',
'children' =>
array (
),
),
),
),
)