Building a multidimensional array dynamically from another array with PHP?

I have data contained in an array which is like so,

$file['info']['files'] = array(
    [0] => array(
         'length' => (int),
         'path' => array (
              [0] => 'file.txt',
         ),
    ),
    [1] => array(
         'length' => (int),
         'path' => array (
              [0] => 'directory one',
              [1] => 'file2.txt',
         ),
    ),
    [2] => array(
         'length' => (int),
         'path' => array (
              [0] => 'directory one',
              [1] => 'directory two',
              [2] => 'file3.txt',
         ),
    ),
);

The $file['info']['files'] array can contain any number of elements. The path array contained in each $file['info']['files'] array is where I am having trouble.

It contains information about a file structure. If just 1 element exists then it is a file. If more than one element exists then each element starting from the top is a parent folder of the next element and the last element is the file in the last folder. Taking the example above would be a file structure of

FILE file1.txt
FOLDER directory one
     FILE file2.txt
     FOLDER directory two
          FILE {file3.txt}

I would like to extract this data into my own array structure which is to be as follows,

 $sortedFiles = array(
     'file1.txt' => (int),
     'directory one' => array(
         'file2.txt' => (int),
         'directory two' => array(
              'file3.txt' => (int),
         ),
     ),
 );

I have this code so far,

foreach($file['info']['files'] as $file) {
    // LENGTH AND PATH ARE SET
    if(isset($file['length'], $file['path'])) {
        // GET COUNT OF FILE PATH ARRAY
        $count = count($file['path']);
        // SINGLE FILE
        if($count == 1) {
            $sortedFiles[$file['path'][0]] = $file['length'];
        // FILES IN DIRECTORY
        } else {
            // BUILD ARRAY STRUCTURE FOR DIRECTORIES
        }
    }
}

I am having trouble when it comes to adding directories to the array. I could do it manually and only go so many directories down each time checking if the array for the directory exists and if not create it, and if it does exist then add to it. I tried this with the code below but it only goes one directory deep (the code went where it says // BUILD ARRAY STRUCTURE above).

// FOLDER NOT SET
if(!isset($files[$file['path'][0]])) {
    $sortedFiles[$file['path'][0]] = array($file['path'][1] => $file['length'],);
// FOLDER SET
} else {
    $sortedFiles[$file['path'][0]][$file['path'][1]] = $file['length'];
}

How can I dynamically create arrays for each directory that exists and add the information that is needed bearing in mind that the directory structure could be any amount of levels deep?

Thanks for taking the time to read my rather long question and I appreciate any help that anyone gives to me.


ANSWERS:


You will need to call your function recursively, as in the example below:

function get_contents_dir( $dir )
{
    $names = array();

    if ( is_dir($dir) && is_readable($dir) )
    {
            foreach ( scandir($dir) as $file )
            {
                    if ( is_dir($dir."/".$file) && is_readable($dir."/".$file) )
                    {
                            $names[] = get_contents_dir($dir."/".$file);
                    }

                    if ( is_file($dir."/".$file) && is_readable($dir."/".$file) )
                    {
                            $names[] = $dir."/".$file;
                    }
            }
    }

    return $names;
}

This function first opens the set $dir folder and scans the list of files, adding each found file to the array which is, after scanning the folder, returned as the return value of the function.

The twist comes in when an entry of the scandir() result (list of files and folders in the folder) is actually a folder. If that happens, the function is called from it's internals, recursively (see the line $names[] = get_contents_dir($dir."/".$file); calling the function from within the function) and the subfolder will be indexed too. Rinse and repeat, until all subfolders are indexed.

If you call the function and let it execute, an array will be returned. Each key of the array will be an entry. If it was a file, the value linked to the key is the name of the file, if it was a folder, the value will be another array nested into the previous one.

Here is an example dump taken of the returned array:

array (
  0 => './libmysqlclient.so.16.0.0',
  1 => './libmysqlclient_r.so.16.0.0',
  2 => 
  array (
    0 => './libs/libboost_thread-mt.a',
    1 => './libs/libboost_thread-mt.so.1.38.0',
    2 => './libs/libmysql.dll',
    3 => './libs/libmysqlclient16_5.1.41-3ubuntu12_i386.deb',
  ),
  3 => 
  array (
    0 => './radio_sneaker/cl_auto.lua',
    1 => './radio_sneaker/sh_auto.lua',
    2 => './radio_sneaker/sh_coms.lua',
    3 => './radio_sneaker/sh_info.lua',
    4 => './radio_sneaker/sv_auto.lua',
    5 => './radio_sneaker/sv_hooks.lua',
  ),
  4 => './sv_auto.lua',
)

Compare this output against the tree command ran on the same folder:

|   libmysqlclient.so.16.0.0
|   libmysqlclient_r.so.16.0.0
|   sv_auto.lua
|   
+---libs
|       libboost_thread-mt.a
|       libboost_thread-mt.so.1.38.0
|       libmysql.dll
|       libmysqlclient16_5.1.41-3ubuntu12_i386.deb
|       
\---radio_sneaker
        cl_auto.lua
        sh_auto.lua
        sh_coms.lua
        sh_info.lua
        sv_auto.lua
        sv_hooks.lua

Why don't you use a join function in php to merge the path to check whether it then exists? If it doesn't then go level by level checking if the folder exists and if it doesn't then create in and move further. My point is that creating such a dynamic structure is, first of all, difficult and easy to mess up. Why not to go the easy way?


Imagine that you have an array representing the directory stucture which is initially empty and will be filled in piece by piece. You need to keep track of a "current" item in this array and iterate over the directory names. At each iteration you would create a sub-array under the current item if it does not already exist and then set the current to be this sub-array.

This can be done either with recursion or with iteration, and since it's PHP the "current" marker would need to be a reference.

With this overview in mind, have a look at this question and the answers there. The input there is in the form of a string, but that's just an implode away from your current situation.


I'll let you wrestle this into your code, but heres the important lessons.

turn the flat array into a nested structure

$a = array(
    'dir1', 'dir2', 'file.txt'
);

$structure = array(array_pop($a));
foreach (array_reverse($a) as $dir) {
    $structure = array($dir => $structure);
}

print_r($structure);

merge one structure into another

$result = array_merge_recursive($result, $structure);

Just iterate over all structures for the merging.



 MORE:


 ? Counting rows/records in a multidimensional array
 ? Php dynamic array name
 ? Get select option value from forms using jquery created dynamically in loop
 ? Get select option value from forms using jquery created dynamically in loop
 ? Get select option value from forms using jquery created dynamically in loop
 ? How do I create a dynamic form in Drupal 6 using jQuery?
 ? How do I get the data from forms dynamically created with jQuery into php?
 ? Dynamically selecting Dropdown's options
 ? jquery trigger button click, and get value of dynamic select/option
 ? Can't get value of select field in a form with jquery