user3847106 user3847106 - 1 month ago 9
JSON Question

php file list array into array tree

I have some file list arrays with elements exceeding 100s in number that I need to convert to a tree array and insert them into the database and return the tree on call for a json api directory system
I have spent a week trying different algorithms, all of them fail at some point.
This is the array:

$files[] = array('dir1/dir2/dir3/file1.mkv', 44444);
$files[] = array('dir1/dir2/dir3/file2.mkv', 44444);
$files[] = array('dir1/dir2/file1.mkv', 44444);
$files[] = array('dir1/dir2/file2.txt', 11111);
$files[] = array('dir1/file1.exe', 22222);
$files[] = array('dir1/file2.exe', 22222);
$files[] = array('file1.rar', 3333);


the first element is the file path, second element is filesize.
These files dont exist on this server, they are on a different server, this is the api server.
I need to convert it into a nested tree array like this:

Array
(
[dir1] => Array
(
[dir2] => Array
(
[dir3] => Array
(
[0] => Array
(
[name] => file1.mkv
[size] => 44444
)

[1] => Array
(
[name] => file2.mkv
[size] => 44444
)

)

[0] => Array
(
[name] => file1.mkv
[size] => 44444
)

[1] => Array
(
[name] => file2.txt
[size] => 11111
)

)

[0] => Array
(
[name] => file1.exe
[size] => 22222
)

[1] => Array
(
[name] => file2.exe
[size] => 22222
)

)

[0] => Array
(
[name] => file1.rar
[size] => 3333
)

)


I wrote some code but it only goes up to 2nd level (dir2), then on the third level (dir3), it fails to perform as expected.

the code:

foreach ($files as $file)
{
$info = pathinfo($file[0]);
if ($info['dirname'] != '.')
{
if (strpos($info['dirname'], '/') !== false)
{
$dirs[pathinfo($info['dirname'])['dirname']][pathinfo($info['dirname'])['basename']][] = array('name' => $info['basename'], 'size' => $file[1]);
}
else
{
$dirs[$info['dirname']][] = array('name' => $info['basename'], 'size' => $file[1]);
}
}
else
{
$dirs[] = array('name' => $info['basename'], 'size' => $file[1]);
}
}


This code returns the array fine if I remove the dir3 level from the file list array, but with the dir3 present, it gives:

Array
(
[dir1/dir2] => Array
(
[dir3] => Array
(
[0] => Array
(
[name] => file1.mkv
[size] => 44444
)

[1] => Array
(
[name] => file2.mkv
[size] => 44444
)

)

)

[dir1] => Array
(
[dir2] => Array
(
[0] => Array
(
[name] => file1.mkv
[size] => 44444
)

[1] => Array
(
[name] => file2.txt
[size] => 11111
)

)

[0] => Array
(
[name] => file1.exe
[size] => 22222
)

[1] => Array
(
[name] => file2.exe
[size] => 22222
)

)

[0] => Array
(
[name] => file1.rar
[size] => 3333
)

)


This [dir1/dir2] is the problem, I need it to be a perfect tree so I can recurs through it and insert the records into the database.

Answer

Since you don't know tree depth beforehand, you can't manage this with if/else loops. It is a use case that needs recursion.

<?php
function scanpath($path) {
    $myscan = scandir($path);
    $tree=[];
    foreach($myscan as $entry) {
        //echo '<br>'.$entry;
        if($entry==='.' || $entry ==='..') {
            // do nothing
        } else  if(is_dir($path.'/'.$entry)) {
            // this is a folder, I will recurse
            $tree[$entry] = scanpath($path.'/'.$entry);
        } else {
            // this is a file or link. Value is file size
            $tree[$entry] = filesize($path.'/'.$entry);
        }
    }
    return $tree;
}
$scanresult=scanpath(__DIR__);
echo '<pre>';
print_r($scanresult);
echo '</pre>';

You see, I call a function on a given path. For each entry on that path I

  • Discard . and ..
  • If it's a folder, the node will contain the result of applying that function to the subpath
  • In any other case, the node will contain the file size

You'll need to modify my code to suit your needs. I can tell your "is file or link" case has a different format.

EDIT: since there isn't a real file structure to scan, but instead you have an array of paths, this is the solution I came with

<?php
$thefiles=[];

$thefiles[] = array('dir1/dir2/dir3/file1.mkv', 44444);
$thefiles[] = array('dir1/dir2/dir3/file2.mkv', 44444);
$thefiles[] = array('dir1/dir2/file1.mkv', 44444);
$thefiles[] = array('dir1/dir2/file2.txt', 11111);
$thefiles[] = array('dir1/dir4/file5.mkv', 22444);
$thefiles[] = array('dir1/dir4/file6.txt', 15111);
$thefiles[] = array('dir1/file1.exe', 22222);
$thefiles[] = array('dir1/file2.exe', 22222);
$thefiles[] = array('file1.rar', 3333);

$filearray=[];

function scanpath($patharray,$filesize) {
    $tree=[];
    if(count($patharray)===1) {
        $filename=array_pop($patharray);
        $tree[] = ['name'=>$filename, 'size'=>$filesize];
    } else {
        $pathpart = array_pop($patharray);
        $tree[$pathpart] = scanpath($patharray,$filesize);
    }
    return $tree;
}

foreach($thefiles as $fileentry) {
    $patharray=array_reverse(explode('/',$fileentry[0]));
    $thisarray = scanpath($patharray,$fileentry[1]);
    $filearray= array_merge_recursive($filearray,$thisarray);
}

echo '<pre>';
print_r($filearray);
echo '</pre>';

I exploded each path using slash as separator, then recursed on the path, folder by folder, until I get to the final part and the recursion ends on a file name and size.