4

I have this code to recursive delete files and directories. It works fine but has a little problem. If $path = /var/www/foo/ it will delete everything inside of foo, but not foo. I want to delete foo directory too. Any idea?

public function delete($path) {
    if(!file_exists($path)) {
        throw new RecursiveDirectoryException('Directory doesn\'t exist.');
    }

    $directoryIterator = new DirectoryIterator($path);

    foreach($directoryIterator as $fileInfo) {
        $filePath = $fileInfo->getPathname();

        if(!$fileInfo->isDot()) {
            if($fileInfo->isFile()) {
                unlink($filePath);
            }
            else if($fileInfo->isDir()) {
                if($this->emptyDirectory($filePath)) {
                    rmdir($filePath);
                }
                else {
                    $this->delete($filePath);
                    rmdir($filePath);
                }
            }
        }
    }
}
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
thom
  • 41
  • 1
  • 2

4 Answers4

12

Why even recurse in your function?

public function delete($path) {
    $it = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($path),
        RecursiveIteratorIterator::CHILD_FIRST
    );
    foreach ($it as $file) {
        if (in_array($file->getBasename(), array('.', '..'))) {
            continue;
        } elseif ($file->isDir()) {
            rmdir($file->getPathname());
        } elseif ($file->isFile() || $file->isLink()) {
            unlink($file->getPathname());
        }
    }
    rmdir($path);
}

It works, because RII::CHILD_FIRST iterates over the children before the parent element. So by the time it reaches the directory, it should be empty.

But the actual error is due to where you delete your directories. In inner directories you do it in the parent iteration. That means that your root directory will never be deleted. I'd suggest doing it in the local delete iteration:

public function delete($path) {
    if(!file_exists($path)) {
        throw new RecursiveDirectoryException('Directory doesn\'t exist.');
    }

    $directoryIterator = new DirectoryIterator($path);

    foreach($directoryIterator as $fileInfo) {
        $filePath = $fileInfo->getPathname();
        if(!$fileInfo->isDot()) {
            if($fileInfo->isFile()) {
                unlink($filePath);
            } elseif($fileInfo->isDir()) {
                if($this->emptyDirectory($filePath)) {
                    rmdir($filePath);
                } else {
                    $this->delete($filePath);
                }
            }
        }
    }
    rmdir($path);
}

Note the two changes. We're only deleting the empty directories inside of the iteration. Calling $this->delete() on it will handle the deletion for you. The second change is the addition of the final rmdir at the end of the method...

ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • 1
    This works in Linux, but in Windows, I had some "permission denied" errors when deleting the *parent* folder, which was empty (as it should be, because of the recursive deletion of its content). I has to take `rmdir($path)` *out* of the function. `$path = realpath("./test"); delete($path); rmdir($path);` – nevvermind Aug 04 '11 at 09:08
3

You're missing one last rmdir. You could either call it after $this->delete($path) like this:

$this->delete($path);
rmdir($path);

Or you could change the foreach-loop like this:

public function delete($path) {
    //snip

    foreach($directoryIterator as $fileInfo) {
        //snip
                else {
                    $this->delete($filePath);
                }
            }
        }
    }

    rmdir($path);
}

Also, I sure hope you validate what paths you get there, if this is visible to a user (like a "Delete Everything On My Webspace"-function. I mean, you'll have a lot of fun if somebody passes /etc/ into there.

Bobby
  • 11,419
  • 5
  • 44
  • 69
  • 1
    Yes, and then you can delete it after `$this->delete($filePath);`. – Spiny Norman Dec 20 '10 at 14:50
  • No... I don't know why, but the $path (directory) doesn't exist anymore. Thank you. – thom Dec 20 '10 at 14:52
  • @Spiny Norman: I was more thinking along the lines of adding it after the foreach-loop. – Bobby Dec 20 '10 at 14:52
  • Other people seem to have encountered the problem where the path might have disappeared while you were working on it, and seem to solve it with another test for `is_dir($filePath)` right before the final `rmdir`. Or else just suppress errors on that: `@rmdir($filePath)`. – MightyE Dec 20 '10 at 14:55
  • Well, that works for the parent directory. But you'll be double deleting the inner directories (see my answer for why and how to fix that)... – ircmaxell Dec 20 '10 at 14:56
  • 1
    @Bobby Yes, but because of that, his call to `$this->delete($filePath)` will already have `rmdir()`ed the directory itself (which is the point of this question :) ), so the `rmdir($filePath)` in the next line will not be necessary anymore (and generate a warning). – Spiny Norman Dec 20 '10 at 14:57
  • @Spiny Norman: Ohhhh...now I understand. I'll edit my answer, thank you. – Bobby Dec 20 '10 at 15:00
0

try this

unset( $directoryIterator); rmdir($filePath);

user1679267
  • 77
  • 2
  • 8
-1
function delete($path){
    if(!file_exists($path)) {
        throw new RecursiveDirectoryException('Directory doesn\'t exist.');
    }

    $directoryIterator = new DirectoryIterator($path);

    foreach($directoryIterator as $fileInfo) {
        $filePath = $fileInfo->getPathname();

        if(!$fileInfo->isDot()) {
            if($fileInfo->isFile()) {
                unlink($filePath);
            }
            else if($fileInfo->isDir()) {
                if($this->emptyDirectory($filePath)) {
                    rmdir($filePath);
                }
                else {
                    $this->delete($filePath);
                    rmdir($filePath);
                }
            }
        }
    }
    rmdir($path);
}

?

Dejan Marjanović
  • 19,244
  • 7
  • 52
  • 66