unable to skip unreadable directories with RecursiveDirectoryIterator
Asked Answered
T

8

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?

Transhumance answered 19/1, 2013 at 3:46 Comment(2)
If it doesn't return all directories inside www, what does it return?Milano
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 directoryTranshumance
J
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();
    }
}
Jea answered 21/1, 2013 at 11:54 Comment(2)
This code appears to generate the format I am after for the foldersTranshumance
@Transhumance it definitely would .. i only added a decorator in the second exampleJea
S
2

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
)
Sabrina answered 21/1, 2013 at 8:5 Comment(7)
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.Transhumance
@Transhumance what happens when you run my answer as stand-alone code?Milano
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.Transhumance
@Transhumance I've updated my answer to mimic what you had at the start of your question; see if that works for you.Milano
This looks good I am away from my testing environment now will confirm tomorrow.Transhumance
Sorry for the delay. Yes it seems to output the correct data in the testing I have done thanks.Transhumance
@Transhumance great, let me know if there's anything I should improve in my answer :)Milano
H
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.

Halhalafian answered 26/4, 2013 at 9:50 Comment(0)
S
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);
    }
?>
Sayed answered 27/4, 2013 at 8:29 Comment(1)
Tested... and Works very well.Sayed
T
0

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

Tridentum answered 21/1, 2013 at 7:58 Comment(2)
I did look into this thanks but wasn't able to get the sub directories of www?Transhumance
You should be able to get sub directories by setting the glob pattern to **/*.Tridentum
S
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.

Stevie answered 21/1, 2013 at 8:3 Comment(0)
S
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";
    }
}
Stomacher answered 21/1, 2013 at 10:20 Comment(0)
L
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

Lineolate answered 21/1, 2013 at 17:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.