9

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?

Community
  • 1
  • 1
ak85
  • 3,626
  • 16
  • 56
  • 107
  • If it doesn't return all directories inside www, what does it return? – Ja͢ck Jan 21 '13 at 07:46
  • it returns the directorys until it gets one that it doesn't have permission to read and I get the error, I want to be able to skip those restricted folders and return all other folders in the directory – ak85 Jan 21 '13 at 07:48

8 Answers8

7

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();
    }
}
Baba
  • 89,415
  • 27
  • 158
  • 212
2
function dirScan($dir, $fullpath = false){

    $ignore = array(".","..");
    if (isset($dir) && is_readable($dir)){
        $dlist = array();
        $dir = realpath($dir);
        $objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir,RecursiveDirectoryIterator::KEY_AS_PATHNAME),RecursiveIteratorIterator::SELF_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD);

        foreach($objects as $entry){    
                    if(!in_array(basename($entry), $ignore)){
                        if (!$fullpath){
                            $entry = str_replace($dir, '', $entry);
                        }           
                            $dlist[] = $entry;
                    }                        
        }       
        return $dlist;
    }

}

This code works 100%...

You can simply use this function in order to scan for files and folders in your desired directory or drive. You just need to pass the path of the desired directory into the function. The second parameter of the function is to show full-path of the scanned files and folder. False value of the second parameter means not to show full-path.

The array $ignore is used to exclude any desired filename or foldername from the listing.

The function returns the array containing list of files and folders.

This function skips the files and folders that are unreadable while recursion.

yuva
  • 2,472
  • 4
  • 15
  • 29
1

I've set up the following directory structure:

/
    test.php <-- the test script
    www/
        test1/ <-- permissions = 000
            file1
        test2/
            file2
        file3

I ran the following code (I've added the SKIP_DOTS flag to skip . and .. btw):

$i = new RecursiveIteratorIterator(
  new RecursiveDirectoryIterator("www", FilesystemIterator::SKIP_DOTS),
  RecursiveIteratorIterator::LEAVES_ONLY,
  RecursiveIteratorIterator::CATCH_GET_CHILD
);

print_r(iterator_to_array($i));

It outputs the following:

Array
(
    [www/test2/file2] => SplFileInfo Object
        (
            [pathName:SplFileInfo:private] => www/test2/file2
            [fileName:SplFileInfo:private] => file2
        )

    [www/file3] => SplFileInfo Object
        (
            [pathName:SplFileInfo:private] => www/file3
            [fileName:SplFileInfo:private] => file3
        )

)

This works as expected.

Update

Added the flags you've had in your original example (although I believe those are default anyway):

foreach (new RecursiveIteratorIterator(
  new RecursiveDirectoryIterator("www", FilesystemIterator::SKIP_DOTS | FilesystemIterator::KEY_AS_PATHNAME),
  RecursiveIteratorIterator::LEAVES_ONLY,
  RecursiveIteratorIterator::CATCH_GET_CHILD | RecursiveIteratorIterator::CHILD_FIRST
) as $file => $info) {
        echo $file, "\n";
        print_r($info);
        if ($info->isDir()) {
            echo $file . '<br>';
        }
}

Output:

www/test2/file2
SplFileInfo Object
(
    [pathName:SplFileInfo:private] => www/test2/file2
    [fileName:SplFileInfo:private] => file2
)
www/file3
SplFileInfo Object
(
    [pathName:SplFileInfo:private] => www/file3
    [fileName:SplFileInfo:private] => file3
)
Ja͢ck
  • 161,074
  • 33
  • 239
  • 294
  • Thanks this gives me the results in an array I will need to read up more on iterator_to_array as when I try and put your snippet into my array it runs the code but gives no results on initial testing. – ak85 Jan 21 '13 at 09:44
  • @ak85 what happens when you run my answer as stand-alone code? – Ja͢ck Jan 21 '13 at 09:45
  • I get the array as you have described skipping the secure folders but I am so far having trouble converting your code into a list of folder names like I had in my example. – ak85 Jan 21 '13 at 10:06
  • 2
    @ak85 I've updated my answer to mimic what you had at the start of your question; see if that works for you. – Ja͢ck Jan 21 '13 at 10:17
  • This looks good I am away from my testing environment now will confirm tomorrow. – ak85 Jan 21 '13 at 10:42
  • Sorry for the delay. Yes it seems to output the correct data in the testing I have done thanks. – ak85 Jan 24 '13 at 19:17
  • @ak85 great, let me know if there's anything I should improve in my answer :) – Ja͢ck Jan 25 '13 at 13:55
1
<?php
    $path = "D:/Movies";

    $directory_iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::KEY_AS_PATHNAME);

    $files = new RecursiveIteratorIterator($directory_iterator,
            RecursiveIteratorIterator::SELF_FIRST,
            RecursiveIteratorIterator::CATCH_GET_CHILD);

    try {
        foreach( $files as $fullFileName => $file) {
            $path_parts = pathinfo($fullFileName);

            if(is_file($fullFileName)){
                $path_parts = pathinfo($fullFileName);
                $fileName[] =  $path_parts['filename'];
                $extensionName[] =  $path_parts['extension'];
                $dirName[] =  $path_parts['dirname'];
                $baseName[] =  $path_parts['basename'];
                $fullpath[] = $fullFileName;
            }
        }


        foreach ($fullpath as $filles){
                echo $filles;
                echo "</br>";
        }

    }
    catch (UnexpectedValueException $e) {
        printf("Directory [%s] contained a directory we can not recurse into", $directory);
    }
?>
nhahtdh
  • 52,949
  • 15
  • 113
  • 149
Shashesh
  • 81
  • 3
0

The glob function skips read errors automatically and should simplify your code a bit as well.

RayViljoen
  • 1,251
  • 1
  • 11
  • 15
0

If you are getting unhandled exceptions, why don't you put that code in a try block, with an exception catch block to catch errors when it can't read directories? Just a simple suggestion by looking at your code and your problem. There is probably a neater way to do it in PHP.

Munim
  • 5,748
  • 2
  • 30
  • 41
0

You need to use SELF_FIRST constant if you want to return the unreadable directory name. When you're doing CHILD_FIRST, it attempt to get into the directory, fails, and the current directory name is not included.

$path = 'testing';

$directory_iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::KEY_AS_PATHNAME);

$iterator = new RecursiveIteratorIterator($directory_iterator,
    RecursiveIteratorIterator::SELF_FIRST,
    RecursiveIteratorIterator::CATCH_GET_CHILD);

foreach ($iterator as $file => $info) {
    if ($info->isDir()) {
        echo $file . "\n";
    }
}
German Rumm
  • 5,604
  • 1
  • 22
  • 30
0

What about try catch the UnexpectedValueException. Maybe there is even an unique exception code for that error you can check. Otherwise you can evil parse exception message for "permission denied".

I would suggest to examine the http://php.net/manual/de/class.unexpectedvalueexception.php

Jens Peters
  • 1,895
  • 1
  • 19
  • 30