Search code examples
phpdirectorydirectory-walk

unable to skip unreadable directories with RecursiveDirectoryIterator


I want to get a list of all the subdirectories and my below code works except when I have readonly permissions on certain folders.

In the below question it shows how to skip a directory with RecursiveDirectoryIterator Can I make RecursiveDirectoryIterator skip unreadable directories? however my code is slightly different here and I am not able to get around the problem.

$path = 'www/';
foreach (new RecursiveIteratorIterator(
       new RecursiveDirectoryIterator($path,RecursiveDirectoryIterator::KEY_AS_PATHNAME),
            RecursiveIteratorIterator::CHILD_FIRST) as $file => $info) 
            {
                if ($info->isDir())
                {
                       echo $file . '<br>';                     
                }
            }   

I get the error

Uncaught exception 'UnexpectedValueException' with message 'RecursiveDirectoryIterator::__construct(../../www/special): failed to open dir: Permission denied'

I have tried replacing it with the accepted answer in the other question.

new RecursiveIteratorIterator(
new RecursiveDirectoryIterator("."), 
RecursiveIteratorIterator::LEAVES_ONLY,
RecursiveIteratorIterator::CATCH_GET_CHILD);

However this code will not give me a list of all the directories inside of www like I want, where am I going wrong here?


Solution

  • Introduction

    The main issue with your code is using CHILD_FIRST

    FROM PHP DOC

    Optional mode. Possible values are

    • RecursiveIteratorIterator::LEAVES_ONLY - The default. Lists only leaves in iteration.
    • RecursiveIteratorIterator::SELF_FIRST - Lists leaves and parents in iteration with parents coming first.
    • RecursiveIteratorIterator::CHILD_FIRST - Lists leaves and parents in iteration with leaves coming first.

    What you should use is SELF_FIRST so that the current directory is included. You also forgot to add optional parameters RecursiveIteratorIterator::CATCH_GET_CHILD

    FROM PHP DOC

    Optional flag. Possible values are RecursiveIteratorIterator::CATCH_GET_CHILD which will then ignore exceptions thrown in calls to RecursiveIteratorIterator::getChildren().

    Your CODE Revisited

    foreach (new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($path,RecursiveDirectoryIterator::KEY_AS_PATHNAME),
            RecursiveIteratorIterator::SELF_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD) as $file => $info)
    {
        if ($info->isDir())
        {
            echo $file . '<br>';
        }
    }
    

    You really want CHILD_FIRST

    If you really want to maintain the CHILD_FIRST structure then i suggest you use ReadableDirectoryIterator

    Example

    foreach ( new RecursiveIteratorIterator(
            new ReadableDirectoryIterator($path),RecursiveIteratorIterator::CHILD_FIRST) as $file ) {
        echo $file . '<br>';
    }
    

    Class Used

    class ReadableDirectoryIterator extends RecursiveFilterIterator {
        function __construct($path) {
            if (!$path instanceof RecursiveDirectoryIterator) {
                if (! is_readable($path) || ! is_dir($path))
                    throw new InvalidArgumentException("$path is not a valid directory or not readable");
                $path = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
            }
            parent::__construct($path);
        }
    
        public function accept() {
            return $this->current()->isReadable() && $this->current()->isDir();
        }
    }