19

I'm trying to put some folders on my hard-drive into an array.

For instance, vacation pictures. Let's say we have this structure:

  • Set 1
    • Item 1 of Set 1
    • Item 2 of Set 1
    • Item ... of Set 1
  • Set 2
    • Subset 1 of Set 2
      • Item 1 of Subset 1 of Set 2
      • Item ... of Subset 1 of Set 2
    • Subset 2 of Set 2
    • Random file, not a dir.
  • Set 3
  • ...

I want to have something like that, as an array.
Meaning I have 1 big array and in that array are more arrays. Each set and subset gets its own array.

I'm trying to make it look something like this:

Array
(
    [Set 1] => Array([0] => Item 1 of Set 1, [1] => Item 1 of Set 1,...)
    [Set 2] => Array([Subnet 1] => Array([0] => Item 1 of Subset 1 of Set 2,[1] => ...), [Subnet 2] => Array([0] => ..., ..., ...), ..., [0] => Random File)
    [set 3] => Array(...)
    ...
)

I came across this: http://www.the-art-of-web.com/php/dirlist/

But that's not what I'm looking for. I've been meddling with it but it's giving me nothing but trouble.

Here's an example, view source for larger resolution(no clicking apparently...). Example

Community
  • 1
  • 1
KdgDev
  • 14,299
  • 46
  • 120
  • 156

6 Answers6

47

I recommend using DirectoryIterator to build your array

Here's a snippet I threw together real quick, but I don't have an environment to test it in currently so be prepared to debug it.

$fileData = fillArrayWithFileNodes( new DirectoryIterator( '/path/to/root/' ) );

function fillArrayWithFileNodes( DirectoryIterator $dir )
{
  $data = array();
  foreach ( $dir as $node )
  {
    if ( $node->isDir() && !$node->isDot() )
    {
      $data[$node->getFilename()] = fillArrayWithFileNodes( new DirectoryIterator( $node->getPathname() ) );
    }
    else if ( $node->isFile() )
    {
      $data[] = $node->getFilename();
    }
  }
  return $data;
}
Peter Bailey
  • 105,256
  • 31
  • 182
  • 206
  • I'm getting this error: Fatal error: Maximum function nesting level of '100' reached, aborting! And I don't see why I should get this...highest nesting I have is 5 levels. – KdgDev Jun 04 '09 at 20:38
  • 1
    Ok, I got a chance to run this and fixed the bugs - give it another shot. – Peter Bailey Jun 04 '09 at 20:53
  • What happens now is that whenever I go to a new primary folder, the old ones get overwritten. – KdgDev Jun 04 '09 at 22:05
  • 1
    Your original questions asks how to recursively walk over a directory tree and load that data into an array - my answer does that. If you're expecting something more you're going to have to provide more detail. – Peter Bailey Jun 04 '09 at 22:37
  • Agreed. I drew an example on a white-board and took a picture, you can view it in the original post. I've been working on my own solution and I'm kinda beginning to think that what I'm looking for is impossible. – KdgDev Jun 04 '09 at 22:45
  • 1
    Peter's solution looks like it does exactly what your whiteboard shows. What's the problem? – rojoca Jun 04 '09 at 23:09
  • @rojoca: what happen when Peter's function is called, is that the array that is returned is the very last set of pics from the very last album. All the previous ones have been overwritten. – KdgDev Jun 05 '09 at 01:27
  • Using the pic as an example, suppose there is only Paris and Barcelona, the returned array would contain Paris and sub-maps, but not Barcelona, which is actually overwritten by Paris. – KdgDev Jun 05 '09 at 01:30
  • When I tried using the function it worked perfectly. Are you using as follows: $fileData = fillArrayWithFileNodes(new DirectoryIterator('/around-the-world/')); var_dump($fileData); – rojoca Jun 05 '09 at 01:53
  • Hmz, the var dump shows the array, my debugger does not... Anyways, still one problem, it only goes 2 levels deep in folder structure... I'm gonna try and solve that, but any ideas are welcome. – KdgDev Jun 05 '09 at 16:20
  • Also, what about foreign characters? Like äää – KdgDev Jun 05 '09 at 16:21
  • For me, foreign characters work when adding utf-8-encode: `$data[] = utf8_encode($node->getFilename());` I also had to add $this when using the function within a class: `$data[$node->getFilename()] = $this->fillArrayWithFileNodes(...` – Ralf Dec 24 '14 at 20:42
14

A simple implementation without any error handling:

function dirToArray($dir) {
    $contents = array();
    # Foreach node in $dir
    foreach (scandir($dir) as $node) {
        # Skip link to current and parent folder
        if ($node == '.')  continue;
        if ($node == '..') continue;
        # Check if it's a node or a folder
        if (is_dir($dir . DIRECTORY_SEPARATOR . $node)) {
            # Add directory recursively, be sure to pass a valid path
            # to the function, not just the folder's name
            $contents[$node] = dirToArray($dir . DIRECTORY_SEPARATOR . $node);
        } else {
            # Add node, the keys will be updated automatically
            $contents[] = $node;
        }
    }
    # done
    return $contents;
}
soulmerge
  • 73,842
  • 19
  • 118
  • 155
  • What happens here is that the directory listing is flattened and all files are listed right after one another. – KdgDev Jun 04 '09 at 20:23
  • I just ran the on part of my music compilation, works as expected. – soulmerge Jun 05 '09 at 08:08
  • I know this is old, but this is *exactly* what I was looking for. Perfection. Thank you! – Shelly Aug 28 '12 at 18:42
  • positively nice, yes. I made some small adjustments. using `DIRECTORY_ITERATOR` isn't really necessary most of the time. oh boy, the comment editor wont let me format. ugh. I'll add an answer below with my snippet. – tim Jan 21 '13 at 20:21
  • @tim Just a heads-up that there is no `DIRECTORY_ITERATOR` in @soulmerge's code... `(DIRECTORY_SEPARATOR ≠ DIRECTORY_ITERATOR)` – e-sushi Dec 16 '13 at 12:45
6

Based on the code of @soulmerge's answer. I just removed some nits and the comments and use $startpath as my starting directory. (THANK YOU @soulmerge!)

function dirToArray($dir) {
    $contents = array();
    foreach (scandir($dir) as $node) {
        if ($node == '.' || $node == '..') continue;
        if (is_dir($dir . '/' . $node)) {
            $contents[$node] = dirToArray($dir . '/' . $node);
        } else {
            $contents[] = $node;
        }
    }
    return $contents;
}

$r = dirToArray($startpath);
print_r($r);
Community
  • 1
  • 1
tim
  • 3,823
  • 5
  • 34
  • 39
2

I've had success with the PEAR File_Find package, specifically the mapTreeMultiple() method. Your code would become something like:

require_once 'File/Find.php';
$fileList = File_Find::mapTreeMultiple($dirPath);

This should return an array similar to what you're asking for.

Michael Johnson
  • 2,287
  • 16
  • 21
  • What's this 'File/Find.php' file? I've scanned my harddrive and I have no such file on it. I do have PEAR installed. Might be that I'm using Windows. – KdgDev Jun 04 '09 at 20:11
1

I would like to point out a fact that may surprise people for which indexes in the resulting tables are important. Solutions presented here using sequences:

    $contents[$node] = ...
                       ...
    $contents[] =      ...

will generate unexpected results when directory names contain only numbers. Example:

/111000/file1 
       /file2
/700030/file1 
       /file2
       /456098
             /file1 
             /file2 
/999900/file1
       /file2
/file1
/file2

Result:

Array
(
    [111000] => Array
        (
            [0] => file1
            [1] => file2
        )

    [700030] => Array
        (
            [456098] => Array
                (
                    [0] => file1
                    [1] => file2
                )

            [456099] => file1 <---- someone can expect 0
            [456100] => file2 <---- someone can expect 1
        )

    [999900] => Array
        (
            [0] => file1
            [1] => file2
        )

    [999901] => file1   <---- someone can expect 0
    [999902] => file2   <---- someone can expect 1
)

As you can see 4 elements has index as incrementation of last name of directory.

0kph
  • 11
  • 3
0

So 6 years later....

I needed a solution like the answers by @tim & @soulmerge. I was trying to do bulk php UnitTest templates for all of the php files in the default codeigniter folders.

This was step one in my process, to get the full recursive contents of a directory / folder as an array. Then recreate the file structure, and inside the directories have files with the proper name, class structure, brackets, methods and comment sections ready to fill in for the php UnitTest.

To summarize: give a directory name, get a single layered array of all directories within it as keys and all files within as values.

I expanded a their answer a bit further and you get the following:

function getPathContents($path)
{
     // was a file path provided?
     if ($path === null){
        // default file paths in codeigniter 3.0
        $dirs = array(
            './models',
            './controllers'
        );
    } else{
        // file path was provided
        // it can be a comma separated list or singular filepath or file
        $dirs = explode(',', $path);
    }
    // final array
    $contents = array();
    // for each directory / file given
    foreach ($dirs as $dir) {
        // is it a directory?
        if (is_dir($dir)) {
            // scan the directory and for each file inside
            foreach (scandir($dir) as $node) {
                // skip current and parent directory
                if ($node === '.' || $node === '..') {
                    continue;
                } else {
                    // check for sub directories
                    if (is_dir($dir . '/' . $node)) {
                        // recursive check for sub directories
                        $recurseArray = getPathContents($dir . '/' . $node);
                        // merge current and recursive results
                        $contents = array_merge($contents, $recurseArray);
                    } else {
                        // it a file, put it in the array if it's extension is '.php'
                        if (substr($node, -4) === '.php') {
                            // don'r remove periods if current or parent directory was input
                            if ($dir === '.' || $dir === '..') {
                                $contents[$dir][] = $node;
                            } else {
                                // remove period from directory name 
                                $contents[str_replace('.', '', $dir)][] = $node;
                            }
                        }
                    }
                }
            }
        } else {
            // file name was given
            $contents[] = $dir; 
        }
    }
    return $contents;
}
modnarrandom
  • 142
  • 1
  • 13