Search code examples
phprecursionmkdir

Using PHP how do I use recursive programming and mkdir() to create this hierarchy?


I want to create a directory structure based on a partition map, as in these examples. I've tried various approaches with failure. Recursive programming makes my brain hurt!

Example 1

$partitionMap = '#/#'; //An example of two levels deep

myfolder/0
myfolder/1
myfolder/2  
...
myfolder/f
myfolder/f/0
myfolder/f/1
myfolder/f/2
myfolder/f/3
...

Example 2

$partitionMap = '#/#/#'; //An example of three levels deep

myfolder/a
myfolder/a/4
myfolder/a/4/f

Example 3

$partitionMap = '#/##/###'; //An example of three levels deep

myfolder/0
myfolder/1
myfolder/2
...
myfolder/a/00
myfolder/a/01
myfolder/e/02
myfolder/e/03
...
myfolder/e/03/af0
myfolder/e/03/af1
myfolder/e/03/af2
myfolder/e/03/af3

The # is a hexadecimal placeholder presenting 0-F as you can see above.

So I need something that works like this..

$partitionMap = '#/##'; 

makeFoldersRecursive( './myfolder', $partitionMap );

function makeFoldersRecursive( $path, $pmap ){
    $pmapArr = explode( '/', $pmap );

    $numNibbles = strlen( $pmapArr[0] );
    //Get hex folder count but in dec, # = 16, ## = 256, ### = 4096
    $folder_count_this_level = pow( 16, $numNibbles );

    for($i=0; $i<$count; $i++ ) {
        //Get left padded hex folder name from $i
        $folder = sprintf("%0" . $numNibbles . "s", dechex($i));
        mkdir( $path .  DIRECTORY_SEPARATOR . $folder , 0755 );

        if( array_key_exists( 1, $pmapArr ) ) {
            $passMap = $pmapArr; 
            array_shift( $pmapArr );
            makeFoldersRecursive( $path .  DIRECTORY_SEPARATOR . $folder, $passMap );
        }
    }



}

There's got to be a more elegant way of doing this, Any help would be appreciated!


Solution

  • A purer recursive solution

    You already have one, but I 'll give another spin on it that looks "purer" to my eyes and also explain how I got there.

    Here's the code:

    function create_all_dirs($directory, $map) {
        // 1: TERMINATION CONDITION
        if (empty($map)) {
            mkdir($directory, 0755, true);
            return;
        }
    
        // 2: PROCESS PART OF DATA, PREPARE DATA TO PASS ON RECURSION
        $parts = explode('/', $map);
        $partLength = strlen(array_shift($parts));
        $dirCount = pow(16, $partLength);    
        $map = implode('/', $parts);
    
        // 3: RECURSE
        for($i = 0; $i < $dirCount; ++$i) {
            $dir = $directory.DIRECTORY_SEPARATOR
                             .sprintf('%0'.$partLength.'s', dechex($i));
            create_all_dirs($dir, $map);
        }
    }
    

    Explanation

    While I won't even suggest that there's one good way of reaching a solution for recursive functions, I can explain an approach that has worked well in practice for me.

    Step 1: Think about what data you will need to pass when recursing.

    In this case, every level of the function will be responsible for creating one level of the directory structure. So it makes sense that if you call

    create_all_dirs('c:/', '#/#')
    

    then at some point the function will make a recursive call that looks like this:

    create_all_dirs('c:/0', '#')
    

    That's all you need to pin down at this point. You can tell that it makes sense and that after a sequence of such calls the function will have (somehow, we haven't yet written it) done its job.

    Step 2: Termination condition

    With this knowledge, pin down your function's termination condition. In our case, given the above a reasonable termination condition would be "when the second parameter is the empty string". So what would the function do if called with a directory and an empty "map" string?

    function create_all_dirs($directory, $map) {
        if(empty($map)) {
            mkdir($directory, 0755, true);
            return;
        }
    
        // ???
    }
    

    Obviously it should create the directory. I 'm calling mkdir in such a way that it will recursively create all directories on the path if they don't already exist because the final solution needs it to work, but that's not necessary. You already know how to do it without this convenience.

    Step 3: Prepare the parameters common to all recursive calls

    OK, so what do we need to make the recursive call? You already know that at each level we need to pop part of $map string and call the function recursively a number of times. We also need to feed it a new, deeper directory string and a new, shorter map string (all of this you could tell from the conclusion of step 1, even if you had not already created a solution).

    So we have:

    // cut off part of $map; we also need the length of the part we cut
    // off, to know how many times we need to recursively call at this level
    $parts = explode('/', $map);
    $partLength = strlen(array_shift($parts));
    
    // how many recursive calls?
    $dirCount = pow(16, $partLength);
    
    // this will be the $map parameter for all recursive calls
    $map = implode('/', $parts);
    

    Step 4: Call the function recursively

    At this point I don't think there is anything more to explain. We calculate the value of $directory, which is different for each recursive call, and make it:

    for($i = 0; $i < $dirCount; ++$i) {
        $dir = $directory.DIRECTORY_SEPARATOR
                         .sprintf('%0'.$partLength.'s', dechex($i));
        create_all_dirs($dir, $map);
    }