Home > Software design >  Laravel Format AllDirectories() output into tree
Laravel Format AllDirectories() output into tree

Time:11-13

To start off with, I have checked all resources I could for examples but was not able to find one that brought me close enough so i can resolve this query (simple as it may seem). I've also seen there is a question that is the same but never resolved here: Get allDirectories() in Laravel and create a tree I'll also just use the same sample data cause it's the exact same scenario.

I basically get an output from laravel's AllDirectories() function which output's something like this:

array:20 [▼
  0 => "test"
  1 => "files"
  2 => "files/2"
  3 => "files/2/Blocks"
  4 => "files/2/Blocks/thumbs"
  5 => "files/shares"
]

And I want to convert that into a multidimensional array that looks something like this:

[
            ["label" => "test", "path" => "test", "children" => []],
            ["label" => "files", "path" => "files", "children" =>
                [
                    ["label" => "2", "path" => "files/2", "children" =>
                        [
                            ["label" => "Blocks", "path" => "files/2/Blocks", "children" =>
                                [
                                    [
                                        "label" => "thumbs", "path" => "files/2/Blocks/thumbs", "children" => []
                                    ]
                                ]
                            ]
                        ]
                    ],
                    ["label" => "shares", "path" => "files/shares", "children" => []]
                ]
            ],
        ];

How can one go about converting the output from AllDirectories() to a multidimensional array?

Thanks in advance for any tips or tricks :)

CodePudding user response:

You could exploit laravel collections and a bit of recursion to achieve what you need.

I wrote a function which works on a preprocessed output (array instead of plain string) and does the following steps:

  1. Take all given paths and create groups based on the first segment of each path.

  2. For each created group, take its children paths and remove the first segment from each children (filter out empty paths).

  3. Execute convertPathsToTree function on the children paths, and assign its output result to the children key of the resulting tree structure.

Here is the code:

function convertPathsToTree($paths, $separator = '/', $parent = null)
{
    return $paths
        ->groupBy(function ($parts) {
            return $parts[0];
        })->map(function ($parts, $key) use ($separator, $parent) {
            $childrenPaths = $parts->map(function ($parts) {
                return array_slice($parts, 1);
            })->filter();

            return [
                'label' => (string) $key,
                'path' => $parent . $key,
                'children' => $this->convertPathsToTree(
                    $childrenPaths,
                    $separator,
                    $parent . $key . $separator
                ),
            ];
        })->values();
}
Usage

First of all, let's assume the paths are assigned as a collection to a $data variable:

$data = collect([
    'test',
    'files',
    'files/2',
    'files/2/Blocks',
    'files/2/Blocks/thumbs',
    'files/shares',
]);

You first need to pre-process the array by splitting each path with the directory separator (/ in this example). This can be done with a simple map call:

$processedData = $data->map(function ($item) {
    return explode('/', $item);
});

Then, you can use the above function and provide the transformed input to obtain the requested structure:

convertPathsToTree($processedData);

If you would rather obtain an output array instead of an collection, add ->toArray(); after the ->values() call at the end of the function.

CodePudding user response:

STEPS

  1. Convert the paths into arrays.
  2. Find the maximum path depth.
  3. Group paths based on their level of depth.
  4. Merge groupings in a hierarchical format.
  5. Reset the result's array indices/keys.
  6. 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 (
        ),
      ),
    ),
  ),
)
  • Related