162

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

tshepang
  • 12,111
  • 21
  • 91
  • 136
Itay Moav -Malimovka
  • 52,579
  • 61
  • 190
  • 278

21 Answers21

252

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); 
   } 
 }
Mahdyfo
  • 1,155
  • 7
  • 18
Artefacto
  • 96,375
  • 17
  • 202
  • 225
  • 1
    @The Pixel Developer - I added [an answer](http://stackoverflow.com/questions/3338123/how-do-i-recursively-delete-a-directory-and-its-entire-contents-filessub-dirs/3352564#3352564) showing that. – salathe Jul 28 '10 at 11:58
  • 2
    check out the solution someone gave me for the same question: glob seems to work nicer: http://stackoverflow.com/questions/11267086/php-unlink-all-files-withing-a-directory-and-then-deleting-that-directory – rolling_codes Aug 10 '12 at 17:09
  • 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. – Vladimir Panteleev Feb 11 '20 at 07:53
  • 1
    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. – Szczepan Hołyszewski Jun 20 '21 at 18:15
  • 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](https://stackoverflow.com/questions/6171807/does-php-optimize-tail-recursion). 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. – Richard Tyler Miles Mar 04 '22 at 03:22
  • 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](https://www.php.net/manual/en/class.ffi.php). – Richard Tyler Miles Mar 04 '22 at 03:27
135

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).

Community
  • 1
  • 1
salathe
  • 51,324
  • 12
  • 104
  • 132
  • `SKIP_DOTS` was only introduced in PHP 5.3? Where did you saw that? – Alix Axel May 31 '11 at 17:48
  • 1
    Thank you. Also: shouldn't you be using the `getPathname()` method instead of `getRealPath()`? – Alix Axel Jun 01 '11 at 08:19
  • 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. – salathe Jun 01 '11 at 09:26
  • 4
    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. – laurent Jan 10 '14 at 15:22
  • 4
    [Here is the same function](https://gist.github.com/mindplay-dk/a4aad91f5a4f1283a5e2) unwrapped, doc-blocked, and made consistent with `rmdir()` and `unlink()`, e.g. aborts with `E_WARNING` and returns `true` or `false` indicating success. – mindplay.dk Jul 03 '14 at 14:10
  • [FilesystemIterator](http://php.net/manual/de/class.filesystemiterator.php) will basically do the same thing as `$files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST );` – dbf Mar 01 '16 at 10:01
  • 3
    @dbf no it won't, the `FilesystemIterator` is not a recursive iterator. – salathe Mar 01 '16 at 20:09
  • unlink(): No such file or directory – Mostafa Sep 11 '19 at 07:26
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);
}
Liko
  • 2,130
  • 19
  • 20
  • 1
    `rm -rf /` == `recurseRmdir('/')` :) – Aaron Esau Jan 09 '17 at 20:56
  • 9
    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")` – Kira M. Backes Jul 04 '17 at 08:57
  • The real problem with this code is that php doesn't support recursion, aka [tail call optimization](https://stackoverflow.com/questions/6171807/does-php-optimize-tail-recursion). Most interpreted languages do not – Richard Tyler Miles Mar 04 '22 at 02:41
  • updated with @kira-m-backes improvements – Liko Mar 08 '22 at 19:27
16

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

Joshua Moore
  • 24,706
  • 6
  • 50
  • 73
ankitjaininfo
  • 11,961
  • 7
  • 52
  • 75
5

There is another thread with more examples here: A recursive remove directory function for 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
5
<?php

use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;

# http://stackoverflow.com/a/3352564/283851
# 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;
}
XzaR
  • 610
  • 1
  • 7
  • 17
  • 1
    Quite complex suggestion ;-) – Philipp May 21 '15 at 19:07
  • @Philipp yeah I guess. Well I made a fork out of https://gist.github.com/mindplay-dk/a4aad91f5a4f1283a5e2 because I did not get it to work so I just thought I may also share it. – XzaR May 24 '15 at 09:13
  • There is a problem. It doesn't delete empty folders after all files are removed. Posting slightly modified version as an answer below. – Vladislav Rastrusny Jun 23 '15 at 16:10
  • @Vladislav Rastrusny really? It works for me. Maybe you had a folder with read-only or something. – XzaR Aug 01 '15 at 10:34
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);
    }
  }
David Pankov
  • 105
  • 1
  • 10
  • `$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. – Fravadona Jun 27 '23 at 23:04
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 );
    }
};
Danijel
  • 12,408
  • 5
  • 38
  • 54
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); 
    } 
  }
jave.web
  • 13,880
  • 12
  • 91
  • 125
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.

jlh
  • 4,349
  • 40
  • 45
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);
}
AvaLanCS
  • 89
  • 3
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);
        }
    }
}
Kerem
  • 11,377
  • 5
  • 59
  • 58
  • 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. – laurent Jan 10 '14 at 15:19
  • 1
    @buggy3 Which specific code are you referring to? The link simply links to this question page. – cgogolin Apr 25 '17 at 13:58
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?

Community
  • 1
  • 1
Programster
  • 12,242
  • 9
  • 49
  • 55
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.

Tofeeq
  • 2,523
  • 1
  • 23
  • 20
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); 
  } 
?>
Hamed
  • 51
  • 1
  • 5
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'));
Chicna
  • 180
  • 1
  • 7
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 );

}
D3F4ULT
  • 926
  • 1
  • 12
  • 20
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'");
        }
    }
}
Vladislav Rastrusny
  • 29,378
  • 23
  • 95
  • 156
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);

?>
Alin Razvan
  • 1,451
  • 13
  • 18
0
<?php

/**
 * code by Nk (nk.have.a@gmail.com)
 */

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');

?>
Nkc
  • 11
  • 2
-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);
     }
}
Ilya Yaremchuk
  • 2,007
  • 2
  • 19
  • 36
  • 1
    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. – Jared Smith Jul 14 '16 at 15:30
  • 1
    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. – vee May 30 '17 at 13:43