How do I recursively delete a directory and its entire contents (files + sub dirs) in PHP? [duplicate]
Asked Answered
P

21

164

How do I delete a directory and its entire contents (files and subdirectories) in PHP?

Pettway answered 26/7, 2010 at 19:9 Comment(0)
F
253

The user-contributed section in the manual page of rmdir contains a decent implementation:

 function rrmdir($dir) { 
   if (is_dir($dir)) { 
     $objects = scandir($dir);
     foreach ($objects as $object) { 
       if ($object != "." && $object != "..") { 
         if (is_dir($dir. DIRECTORY_SEPARATOR .$object) && !is_link($dir."/".$object))
           rrmdir($dir. DIRECTORY_SEPARATOR .$object);
         else
           unlink($dir. DIRECTORY_SEPARATOR .$object); 
       } 
     }
     rmdir($dir); 
   } 
 }
Featherweight answered 26/7, 2010 at 19:11 Comment(6)
@The Pixel Developer - I added an answer showing that.Var
check out the solution someone gave me for the same question: glob seems to work nicer: #11267586Stelle
This calls is_dir twice for each recursed directory. If the argument is a symlink, it also follows it instead of deleting the symlink, which might or might not be what you want. In any case, it's not what rm -rf does.Gignac
Instantly and permanently disbelieve anyone who claims they can implement recursive directory deletion in PHP in under 200 lines of code and handle ALL of the dozens corner cases on all operating systems on which PHP can run.Duodenitis
This isn't really good recursion, or rather a good use case; moreover nothing really is.. Recursion is generally bad practice in php as it doesn't support tail call optimization. Most interpreted languages do not. Recursion generally is to help divide large memory problems as well as manage stack frames. In this implementation your waisting stack frames where as examples with RecursiveIteratorIterator will not.Hipbone
Using built-in iterators will utilize php's C foundations. Thus, allowing recursion to be effective. Notably you can write C code alongside your PHP using PHP's newish FFI.Hipbone
V
137

Building on The Pixel Developer's comment, a snippet using the SPL might look like:

$files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
    RecursiveIteratorIterator::CHILD_FIRST
);

foreach ($files as $fileinfo) {
    $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
    $todo($fileinfo->getRealPath());
}

rmdir($dir);

Note: It does no sanity checking and makes use of the SKIP_DOTS flag introduced with the FilesystemIterator in PHP 5.3.0. Of course, the $todo could be an if/else. The important point is that CHILD_FIRST is used to iterate over the children (files) first before their parent (folders).

Var answered 28/7, 2010 at 11:58 Comment(8)
SKIP_DOTS was only introduced in PHP 5.3? Where did you saw that?Respire
Thank you. Also: shouldn't you be using the getPathname() method instead of getRealPath()?Respire
Use whichever does the job for your particular needs, the answer is only a generic example of iterating over the files first then their respective folders.Var
This solution works well, however it deletes everything... except the directory (whether empty or not). There should an rmdir($dir) at the end of the script.Peder
Here is the same function unwrapped, doc-blocked, and made consistent with rmdir() and unlink(), e.g. aborts with E_WARNING and returns true or false indicating success.Gutbucket
FilesystemIterator will basically do the same thing as $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST );Georgeannageorgeanne
@Georgeannageorgeanne no it won't, the FilesystemIterator is not a recursive iterator.Var
unlink(): No such file or directoryCorie
B
20

Deletes all files and folders in the path.

function recurseRmdir($dir) {
  $files = array_diff(scandir($dir), array('.','..'));
  foreach ($files as $file) {
    (is_dir("$dir/$file") && !is_link("$dir/$file")) ? recurseRmdir("$dir/$file") : unlink("$dir/$file");
  }
  return rmdir($dir);
}
Bremsstrahlung answered 16/9, 2013 at 22:9 Comment(4)
rm -rf / == recurseRmdir('/') :)Emancipation
Please note that this is not symlink safe! You need a sanity check after is_dir to also check that it's !is_link, because otherwise you can symlink to an external folder which then is deleted and this may be considered a security hole. So you should change is_dir("$dir/$file") to is_dir("$dir/$file") && !is_link("$dir/$file")Tumer
The real problem with this code is that php doesn't support recursion, aka tail call optimization. Most interpreted languages do notHipbone
updated with @kira-m-backes improvementsBremsstrahlung
F
16

For *nix you can use a shell_exec for rm -R or DEL /S folder_name for Windows.

Feeler answered 26/7, 2010 at 19:35 Comment(3)
how about DEL /S folder_name for WindowsFeeler
@Gordon RMDIR /S /Q folder_name is what worked for meLorianne
@WiR3D so long as the exec command contains no user input, you should be good. Ex: exec('rm -rf ' . __DIR__ . '/output/*.log');Suggestible
K
5

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

CFileHelper::removeDirectory($my_directory);
Knar answered 5/1, 2014 at 2:42 Comment(0)
I
5
<?php

use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;

# https://mcmap.net/q/149365/-how-do-i-recursively-delete-a-directory-and-its-entire-contents-files-sub-dirs-in-php-duplicate
# https://gist.github.com/XzaR90/48c6b615be12fa765898

# Forked from https://gist.github.com/mindplay-dk/a4aad91f5a4f1283a5e2

/**
 * Recursively delete a directory and all of it's contents - e.g.the equivalent of `rm -r` on the command-line.
 * Consistent with `rmdir()` and `unlink()`, an E_WARNING level error will be generated on failure.
 *
 * @param string $source absolute path to directory or file to delete.
 * @param bool   $removeOnlyChildren set to true will only remove content inside directory.
 *
 * @return bool true on success; false on failure
 */
function rrmdir($source, $removeOnlyChildren = false)
{
    if(empty($source) || file_exists($source) === false)
    {
        return false;
    }

    if(is_file($source) || is_link($source))
    {
        return unlink($source);
    }

    $files = new RecursiveIteratorIterator
    (
        new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST
    );

    //$fileinfo as SplFileInfo
    foreach($files as $fileinfo)
    {
        if($fileinfo->isDir())
        {
            if(rrmdir($fileinfo->getRealPath()) === false)
            {
                return false;
            }
        }
        else
        {
            if(unlink($fileinfo->getRealPath()) === false)
            {
                return false;
            }
        }
    }

    if($removeOnlyChildren === false)
    {
        return rmdir($source);
    }

    return true;
}
Indigenous answered 22/4, 2015 at 13:15 Comment(4)
Quite complex suggestion ;-)Belvia
@Belvia yeah I guess. Well I made a fork out of gist.github.com/mindplay-dk/a4aad91f5a4f1283a5e2 because I did not get it to work so I just thought I may also share it.Indigenous
There is a problem. It doesn't delete empty folders after all files are removed. Posting slightly modified version as an answer below.Fitly
@Vladislav Rastrusny really? It works for me. Maybe you had a folder with read-only or something.Indigenous
F
3

The 100% working solution

public static function rmdir_recursive($directory, $delete_parent = null)
  {
    $files = glob($directory . '/{,.}[!.,!..]*',GLOB_MARK|GLOB_BRACE);
    foreach ($files as $file) {
      if (is_dir($file)) {
        self::rmdir_recursive($file, 1);
      } else {
        unlink($file);
      }
    }
    if ($delete_parent) {
      rmdir($directory);
    }
  }
Firestone answered 15/6, 2018 at 14:39 Comment(1)
$directory is used as part of the glob without any escaping, that's dangerous; also, the glob at the end won't remove anything that starts with two dots.Carboxylase
M
2

Example with glob() function. It will delete all files and folders recursively, including files that starts with dot.

delete_all( 'folder' );

function delete_all( $item ) {
    if ( is_dir( $item ) ) {
        array_map( 'delete_all', array_diff( glob( "$item/{,.}*", GLOB_BRACE ), array( "$item/.", "$item/.." ) ) );
        rmdir( $item );
    } else {
        unlink( $item );
    }
};
Multifold answered 24/3, 2015 at 20:55 Comment(1)
I went with system('rm -fr folder')Pettway
S
2

Enhanced @Artefacto 's solution - corrected typos and simplified code, working for both - empty && non-empty directories .

  function recursive_rmdir($dir) { 
    if( is_dir($dir) ) { 
      $objects = array_diff( scandir($dir), array('..', '.') );
      foreach ($objects as $object) { 
        $objectPath = $dir."/".$object;
        if( is_dir($objectPath) )
          recursive_rmdir($objectPath);
        else
          unlink($objectPath); 
      } 
      rmdir($dir); 
    } 
  }
Shier answered 15/10, 2015 at 8:55 Comment(0)
B
2

It seems that all other answers assume the path given to the function is always a directory. This variant works to remove directories as well as single files:

/**
 * Recursively delete a file or directory.  Use with care!
 *
 * @param string $path
 */
function recursiveRemove(string $path) {
    if (is_dir($path)) {
        foreach (scandir($path) as $entry) {
            if (!in_array($entry, ['.', '..'], true)) {
                recursiveRemove($path . DIRECTORY_SEPARATOR . $entry);
            }
        }
        rmdir($path);
    } else {
        unlink($path);
    }
}

EDIT: If you're picky (and you should be picky) you may want to add code to check for scandir(), rmdir() and unlink() returning an error and throw an exception if so.

Barringer answered 23/1, 2019 at 16:59 Comment(0)
C
2

Using DirectoryIterator and recursion correctly:

function deleteFilesThenSelf($folder) {
    foreach(new DirectoryIterator($folder) as $f) {
        if($f->isDot()) continue; // skip . and ..
        if ($f->isFile()) {
            unlink($f->getPathname());
        } else if($f->isDir()) {
            deleteFilesThenSelf($f->getPathname());
        }
    }
    rmdir($folder);
}
Community answered 25/11, 2019 at 14:46 Comment(0)
T
1

Something like this?

function delete_folder($folder) {
    $glob = glob($folder);
    foreach ($glob as $g) {
        if (!is_dir($g)) {
            unlink($g);
        } else {
            delete_folder("$g/*");
            rmdir($g);
        }
    }
}
Topography answered 30/1, 2013 at 13:10 Comment(2)
I can't explain why but that didn't work for me. It kept trying to delete a folder that was not empty. The second answer above worked fine.Peder
@buggy3 Which specific code are you referring to? The link simply links to this question page.Fayth
H
1

'simple' code that works and can be read by a ten year old:

function deleteNonEmptyDir($dir) 
{
   if (is_dir($dir)) 
   {
        $objects = scandir($dir);

        foreach ($objects as $object) 
        {
            if ($object != "." && $object != "..") 
            {
                if (filetype($dir . "/" . $object) == "dir")
                {
                    deleteNonEmptyDir($dir . "/" . $object); 
                }
                else
                {
                    unlink($dir . "/" . $object);
                }
            }
        }

        reset($objects);
        rmdir($dir);
    }
}

Please note that all I did was expand/simplify and fix (didn't work for non empty dir) the solution here: In PHP how do I recursively remove all folders that aren't empty?

Hazelhazelnut answered 9/9, 2013 at 17:59 Comment(0)
B
1

unlinkr function recursively deletes all the folders and files in given path by making sure it doesn't delete the script itself.

function unlinkr($dir, $pattern = "*") {
    // find all files and folders matching pattern
    $files = glob($dir . "/$pattern"); 

    //interate thorugh the files and folders
    foreach($files as $file){ 
    //if it is a directory then re-call unlinkr function to delete files inside this directory     
        if (is_dir($file) and !in_array($file, array('..', '.')))  {
            echo "<p>opening directory $file </p>";
            unlinkr($file, $pattern);
            //remove the directory itself
            echo "<p> deleting directory $file </p>";
            rmdir($file);
        } else if(is_file($file) and ($file != __FILE__)) {
            // make sure you don't delete the current script
            echo "<p>deleting file $file </p>";
            unlink($file); 
        }
    }
}

if you want to delete all files and folders where you place this script then call it as following

//get current working directory
$dir = getcwd();
unlinkr($dir);

if you want to just delete just php files then call it as following

unlinkr($dir, "*.php");

you can use any other path to delete the files as well

unlinkr("/home/user/temp");

This will delete all files in home/user/temp directory.

Bedouin answered 20/11, 2015 at 7:40 Comment(0)
H
1

I use this code ...

 function rmDirectory($dir) {
        foreach(glob($dir . '/*') as $file) {
            if(is_dir($file))
                rrmdir($file);
            else
                unlink($file);
        }
        rmdir($dir);
    }

or this one...

<?php 
public static function delTree($dir) { 
   $files = array_diff(scandir($dir), array('.','..')); 
    foreach ($files as $file) { 
      (is_dir("$dir/$file")) ? delTree("$dir/$file") : unlink("$dir/$file"); 
    } 
    return rmdir($dir); 
  } 
?>
Hynda answered 25/3, 2016 at 11:29 Comment(1)
Is this recursive?Pericardium
A
0

I juste made this code, from some StackOverflow discussions. I didn't test on Linux environment yet. It is made in order to delete a file or a directory, completely :

function splRm(SplFileInfo $i)
{
    $path = $i->getRealPath();

    if ($i->isDir()) {
        echo 'D - ' . $path . '<br />';
        rmdir($path);
    } elseif($i->isFile()) {
        echo 'F - ' . $path . '<br />';
        unlink($path);
    }
}

function splRrm(SplFileInfo $j)
{
    $path = $j->getRealPath();

    if ($j->isDir()) {
        $rdi = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
        $rii = new RecursiveIteratorIterator($rdi, RecursiveIteratorIterator::CHILD_FIRST);
        foreach ($rii as $i) {
            splRm($i);
        }
    }
    splRm($j);

}

splRrm(new SplFileInfo(__DIR__.'/../dirOrFileName'));
Adrian answered 2/7, 2014 at 13:51 Comment(0)
O
0
function rmdir_recursive( $dirname ) {

    /**
     * FilesystemIterator and SKIP_DOTS
     */

    if ( class_exists( 'FilesystemIterator' ) && defined( 'FilesystemIterator::SKIP_DOTS' ) ) {

        if ( !is_dir( $dirname ) ) {
            return false;
        }

        foreach( new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dirname, FilesystemIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST ) as $path ) {
            $path->isDir() ? rmdir( $path->getPathname() ) : unlink( $path->getRealPath() );
        }

        return rmdir( $dirname );

    }

    /**
     * RecursiveDirectoryIterator and SKIP_DOTS
     */

    if ( class_exists( 'RecursiveDirectoryIterator' ) && defined( 'RecursiveDirectoryIterator::SKIP_DOTS' ) ) {

        if ( !is_dir( $dirname ) ) {
            return false;
        }

        foreach( new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dirname, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST ) as $path ) {
            $path->isDir() ? rmdir( $path->getPathname() ) : unlink( $path->getRealPath() );
        }

        return rmdir( $dirname );

    }

    /**
     * RecursiveIteratorIterator and RecursiveDirectoryIterator
     */

    if ( class_exists( 'RecursiveIteratorIterator' ) && class_exists( 'RecursiveDirectoryIterator' ) ) {

        if ( !is_dir( $dirname ) ) {
            return false;
        }

        foreach( new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dirname ), RecursiveIteratorIterator::CHILD_FIRST ) as $path ) {
            if ( in_array( $path->getFilename(), array( '.', '..' ) ) ) {
                continue;
            }
            $path->isDir() ? rmdir( $path->getPathname() ) : unlink( $path->getRealPath() );
        }

        return rmdir( $dirname );

    }

    /**
     * Scandir Recursive
     */

    if ( !is_dir( $dirname ) ) {
        return false;
    }

    $objects = scandir( $dirname );

    foreach ( $objects as $object ) {
        if ( $object === '.' || $object === '..' ) {
            continue;
        }
        filetype( $dirname . DIRECTORY_SEPARATOR . $object ) === 'dir' ? rmdir_recursive( $dirname . DIRECTORY_SEPARATOR . $object ) : unlink( $dirname . DIRECTORY_SEPARATOR . $object );
    }

    reset( $objects );
    rmdir( $dirname );

    return !is_dir( $dirname );

}
Orientation answered 4/12, 2014 at 16:58 Comment(0)
F
0

Modified variant of @XzaR solution. It does remove empty folders, when all files are deleted from them and it throws exceptions instead of returning false on errors.

function recursivelyRemoveDirectory($source, $removeOnlyChildren = true)
{
    if (empty($source) || file_exists($source) === false) {
        throw new Exception("File does not exist: '$source'");
    }

    if (is_file($source) || is_link($source)) {
        if (false === unlink($source)) {
            throw new Exception("Cannot delete file '$source'");
        }
    }

    $files = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST
    );

    foreach ($files as $fileInfo) {
        /** @var SplFileInfo $fileInfo */
        if ($fileInfo->isDir()) {
            if ($this->recursivelyRemoveDirectory($fileInfo->getRealPath()) === false) {
                throw new Exception("Failed to remove directory '{$fileInfo->getRealPath()}'");
            }
            if (false === rmdir($fileInfo->getRealPath())) {
                throw new Exception("Failed to remove empty directory '{$fileInfo->getRealPath()}'");
            }
        } else {
            if (unlink($fileInfo->getRealPath()) === false) {
                throw new Exception("Failed to remove file '{$fileInfo->getRealPath()}'");
            }
        }
    }

    if ($removeOnlyChildren === false) {
        if (false === rmdir($source)) {
            throw new Exception("Cannot remove directory '$source'");
        }
    }
}
Fitly answered 23/6, 2015 at 16:14 Comment(0)
H
0

Once you finish running tests , just remove # from the #unlink and #rmdir in the class .

<?php 
class RMRFiles {

        function __construct(){
        }

    public function recScan( $mainDir, $allData = array() )
    {

    // hide files
    $hidefiles = array(
    ".",
    "..") ;

    //start reading directory
    $dirContent = scandir( $mainDir ) ;

        //cycle through
        foreach ( $dirContent as $key => $content )
        {
            $path = $mainDir . '/' . $content ;

            // if is readable / file
            if ( ! in_array( $content, $hidefiles ) )
            {
            if ( is_file( $path ) && is_readable( $path ) )
            {
            #delete files within directory
            #unlink($path);
            $allData['unlink'][] = $path ;
            }

            // if is readable / directory
            else
            if ( is_dir( $path ) && is_readable( $path ) )
            {
            /*recursive*/
            $allData = $this->recScan( $path, $allData ) ;

            #finally remove directory
            $allData['rmdir'][]=$path;
            #rmdir($path);
            }
            }
        }

    return $allData ;

    }

}

header("Content-Type: text/plain");

/* Get absolute path of the running script 
Ex : /home/user/public_html/   */
define('ABPATH', dirname(__file__) . '/'); 

/* The folder where we store cache files 
Ex: /home/user/public_html/var/cache   */
define('STOREDIR','var/cache'); 

$rmrf = new RMRFiles();
#here we delete folder content files & directories
print_r($rmrf->recScan(ABPATH.STOREDIR));
#finally delete scanned directory ? 
#rmdir(ABPATH.STOREDIR);

?>
Honest answered 12/4, 2017 at 2:44 Comment(0)
A
0
<?php

/**
 * code by Nk ([email protected])
 */

class filesystem
{
    public static function remove($path)
    {
        return is_dir($path) ? rmdir($path) : unlink($path);
    }

    public static function normalizePath($path)
    {
        return $path.(is_dir($path) && !preg_match('@/$@', $path) ? '/' : '');      
    }

    public static function rscandir($dir, $sort = SCANDIR_SORT_ASCENDING)
    {
        $results = array();

        if(!is_dir($dir))
        return $results;

        $dir = self::normalizePath($dir);

        $objects = scandir($dir, $sort);

        foreach($objects as $object)
        if($object != '.' && $object != '..')
        {
            if(is_dir($dir.$object))
            $results = array_merge($results, self::rscandir($dir.$object, $sort));
            else
            array_push($results, $dir.$object);
        }

        array_push($results, $dir);

        return $results;
    }

    public static function rrmdir($dir)
    {
        $files = self::rscandir($dir);

        foreach($files as $file)
        self::remove($file);

        return !file_exists($dir);
    }
}

?>

cleanup.php :

<?php

/* include.. */

filesystem::rrmdir('/var/log');
filesystem::rrmdir('./cache');

?>
Adulthood answered 22/2, 2018 at 19:26 Comment(0)
I
-1
function deltree_cat($folder)
{
    if (is_dir($folder))
    {
             $handle = opendir($folder);
             while ($subfile = readdir($handle))
             {
                     if ($subfile == '.' or $subfile == '..') continue;
                     if (is_file($subfile)) unlink("{$folder}/{$subfile}");
                     else deltree_cat("{$folder}/{$subfile}");
             }
             closedir($handle);
             rmdir ($folder);
     }
     else
     {
        unlink($folder);
     }
}
Iiette answered 14/7, 2016 at 15:23 Comment(2)
If you're answering an old question that already has a number of answers including an accepted one, you need to post an explanation of what value your answer adds, not just code. Code-only answers are frowned upon in general, but especially this case.Siddon
I voted up for this answer and accepted answer. This is not bad, from my benchmark check (without unlink, rmdir) the opendir + readdir work faster that scandir and RecursiveDirectoryIterator it is also use less memory than all. To remove folder I have to closedir first, I was stuck at this. Thanks to this answer.Simonnesimonpure

© 2022 - 2024 — McMap. All rights reserved.