mrjake101
mrjake101

Reputation: 67

A custom PHP function to recursively iterate over directories and output a hierarchical, multidimensional array?

A custom PHP function to recursively iterate over directories and output a hierarchical, multidimensional array?

Using the new SPL Iterator class (RecursiveIterator*), I've been working on the following function:

function directoryToArray( $directory ) {

    $array = [];

    $objects = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $directory ), RecursiveIteratorIterator::SELF_FIRST );

    foreach ( $objects as $name => $object ) {

        if ( !( $object->getFilename() == "." | $object->getFilename() == ".." ) ) {
            $path = $object->isDir() ? [
                    [
                        'name' => $object->getFilename(),
                        'file' => $object->getFilename(),
                        'children' => []
                    ]
            ] : [
                [
                    'name' => friendlyName( $object->getFilename() ),
                    'file' => $object->getFilename(),
                ]
            ];

            for ( $depth = $objects->getDepth() - 1; $depth >= 0; $depth-- ) {
                $path = [
                    $objects->getSubIterator( $depth )->current()->getFilename() => $path,
                ];
            }

            $array = array_merge_recursive( $array, $path );

        }

    }
    return $array;
}

This currently results in the following output:

Array
(
    [0] => Array
        (
            [name] => Anchor Links
            [file] => anchor-links.php
        )

    [1] => Array
        (
            [name] => Columns
            [file] => columns.php
        )

    [2] => Array
        (
            [name] => Page Layouts
            [file] => page-layouts
            [children] => Array
                (
                )

        )

    [page-layouts] => Array
        (
            [0] => Array
                (
                    [name] => Right Sidebar
                    [file] => right-sidebar.php
                )

            [1] => Array
                (
                    [name] => Left Sidebar
                    [file] => left-sidebar.php
                )

            [2] => Array
                (
                    [name] => Right Sidebar
                    [file] => right-sidebar
                    [children] => Array
                        (
                        )

                )

            [right-sidebar] => Array
                (
                    [0] => Array
                        (
                            [name] => Other Options
                            [file] => other-options.php
                        )

                    [1] => Array
                        (
                            [name] => Option A
                            [file] => option-a.php
                        )

                    [2] => Array
                        (
                            [name] => Other Options
                            [file] => other-options
                            [children] => Array
                                (
                                )

                        )

                    [other-options] => Array
                        (
                            [0] => Array
                                (
                                    [name] => Sample
                                    [file] => sample.php
                                )

                        )

                )

            [3] => Array
                (
                    [name] => Changelog
                    [file] => changelog.php
                )

        )

)

However, the output I'm trying to achieve is the following:

Array
(
    [0] => Array
        (
            [name] => Anchor Links
            [file] => anchor-links.php
        )

    [1] => Array
        (
            [name] => Columns
            [file] => columns.php
        )

    [2] => Array
        (
            [name] => Page Layouts
            [file] => page-layouts
            [children] => Array
                (

                    [0] => Array
                        (
                            [name] => Right Sidebar
                            [file] => right-sidebar.php
                        )

                    [1] => Array
                        (
                            [name] => Left Sidebar
                            [file] => left-sidebar.php
                        )

                    [2] => Array
                        (
                            [name] => Right Sidebar
                            [file] => right-sidebar
                            [children] => Array
                                (
                                    [0] => Array
                                        (
                                            [name] => Other Options
                                            [file] => other-options.php
                                        )

                                    [1] => Array
                                        (
                                            [name] => Option A
                                            [file] => option-a.php
                                        )

                                    [2] => Array
                                        (
                                            [name] => Other Options
                                            [file] => other-options
                                            [children] => Array
                                                (
                                                    [0] => Array
                                                        (
                                                            [name] => Sample
                                                            [file] => sample.php
                                                        )
                                                )

                                        )

                                )

                        )

                    [3] => Array
                        (
                            [name] => Changelog
                            [file] => changelog.php
                        )

                )

        )
)

I think we're almost there, I'm just stumped on how to get the subdirectories to output to the (now) empty array inside of [children]. Also, do not be confused about the duplicate or similar names (e.g., there is a right-sidebar.php file and a right-sidebar subdirectory in the same directory).

Upvotes: 1

Views: 545

Answers (2)

sevavietl
sevavietl

Reputation: 3802

Using RecursiveDirectoryIterator, RecursiveIteratorIterator and some reference juggling you can do this with one explicit loop.

function directoryToArray($directory)
{
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator(
            $directory,
            RecursiveDirectoryIterator::SKIP_DOTS
        ),
        RecursiveIteratorIterator::SELF_FIRST
    );

    $files = [];
    $references = [&$files];
    foreach ($iterator as $item) {
        $file = [
            'name' => $item->getFilename(),
            'file' => $item->getFilename(),
        ];

        if ($item->isDir()) {
            $file['children'] = [];

            $references[$iterator->getDepth() + 1] =& $file['children'];
        }

        $references[$iterator->getDepth()][] = $file;
    }

    unset($references);

    return $files;
}

Please, notice this flag RecursiveDirectoryIterator::SKIP_DOTS it filters out . and .. files.

One important point. The usage of RecursiveIteratorIterator should save you from using explicit nested loops. When you find yourself using explicit nested loops here, you either not using iterators correctly or iterators themselves do not fit for the task (probably this is the domain of the recursive function). Of course, there is always edge cases, but, for the most cases, this rule holds.

Upvotes: 2

Poiz
Poiz

Reputation: 7617

If your goal is to recursively scan a given directory (including its sub-directories) and return a Multidimensional Array with all the Folders & Files in a hierarchical fashion, the Function below: deepScan() could help you out.

This Function takes only one Parameter: $directory which is the Path to the Directory to scan. The other 2 Arguments are just for keeping track of things during the Recursions and thus should be left alone.

The Function returns a Multidimensional Array with the names of Directories & Sub-Directories as the Keys. All Files in each Sub-Directory are listed as children of that Sub-Directory.

<?php


    /**
     * THIS FUNCTION SCANS A DIRECTORY "RECURSIVELY",
     * BUILDING AN ARRAY TREE OF ALL FILES AND FOLDERS AS IT GOES...
     * THIS IMPLIES THAT EVEN SUB-DIRECTORIES WILL BE SCANNED AS WELL
     *
     * FULL-PATH TO THE DIRECTORY TO SCAN
     * @param $directory
     *
     * USED INTERNALLY DURING THE RECURSIVE TRIPS. LEAVE AS IS
     * @param $k
     *
     * USED INTERNALLY DURING THE RECURSIVE TRIPS. LEAVE AS IS
     * @param $key
     *
     * RETURNS THE RESULTING ARRAY
     * @return array
     */
    function deepScan($directory, &$k=null, $key=null) {
        $iterator           = new \DirectoryIterator ($directory);
        $firstDir           = basename($directory);
        $dirs               = [];
        $dirs[$firstDir]    = [];

        if(!$key){  $key    = $firstDir;    }
        if(!$k){    $k      = &$dirs[$key]; }
        if($k && $key){
            $k[$key]        = [];
            $k              = &$k[$key];
        }

        foreach($iterator as $info) {
            $fileDirName            = $info->getFilename();
            if($info->isFile () && !preg_match("#^\..*?#", $fileDirName)){
                $k[]                = $directory . DIRECTORY_SEPARATOR . $fileDirName;
            }else if($info->isDir()  && !$info->isDot()){
                $pathName           = $directory . DIRECTORY_SEPARATOR . $fileDirName;
                $k[$fileDirName]    = $pathName;
                $key                = $fileDirName;
                $it                 = &$k;
                deepScan($pathName, $it, $key);
            }
        }

        $dirs   = removeEmptyEntries($dirs);

        return $dirs;
    }

    /**
     * THIS FUNCTION REMOVES/FILTERS EMPTY ENTRIES
     * FROM THE RESULTING ARRAY TREE.
     *
     * THE ARRAY TO BE FILTERED
     * @param $data
     *
     * RETURNS THE RESULTING FILTERED ARRAY
     * @return array
     */
    function removeEmptyEntries(array &$data){
        foreach($data as $key=>&$item){
            if(is_array($item)){
                if(empty($item)) {
                    unset($data[$key]);
                }else{
                    removeEmptyEntries($item);
                }
            }
        }
        foreach($data as $key=>&$item){
            if(is_array($item) && empty($item)) {
                unset($data[$key]);
            }
        }
        return $data;
    }


    // USAGE:
    $dirTree  = deepScan( "/path_2_specific_directory" );

    echo "<pre>";
    print_r($dirTree);
    echo "</pre>";

It is hoped this gives you what you need. You might, however, tweak it further if you want so special output.

Cheers and Good Luck ;-)

Upvotes: 0

Related Questions