rmdir

Removes an empty directory from the filesystem.

Signature
rmdir(string $directory, ?resource $context = null): bool
  • Added in PHP 4.0

Parameters

ParameterTypeDescription
$directorystringPath to the directory to remove. It must exist and be empty.
$contextoptional?resource= nullA stream context resource created with stream_context_create(), used by stream wrappers that support directory removal.

Return value

boolReturns true on success or false on failure.

Errors

Emits an E_WARNING on failure, for example when the directory does not exist, is not empty, or permissions forbid the operation.

rmdir() deletes a single directory. It is deliberately narrow: the directory must already be empty and the parent directory must be writable by the process. It does not recurse, and it will not delete a file — that is unlink()'s job.

When to reach for it

  • Cleaning up a per-job working directory once its files have been removed.
  • Removing a lock directory used as a cheap mutex.
  • The final step of a recursive delete routine, after the contents are gone.

Recursive deletion

PHP has no built-in recursive remove. Walk the tree bottom-up and call unlink() on files and rmdir() on directories, or use RecursiveIteratorIterator with RecursiveDirectoryIterator::CHILD_FIRST. Always skip the . and .. entries that scandir() returns.

What trips people up

"Empty" means empty to the operating system, including dotfiles. A stray .DS_Store, .gitkeep or editor swap file will make rmdir() fail with a warning even though the directory looks empty in a file browser. glob('dir/*') will not show those; use scandir() instead.

PHP also caches stat results, so is_dir() may still report true right after a successful rmdir() in the same request. Call clearstatcache() before re-checking.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Delete a temporary upload staging folder after moving its files into permanent storage.
  • Release a lock implemented as mkdir('/path/job.lock') at the end of a cron script.
  • Prune empty date-based directories left behind by a log rotation job.

Frequently asked questions

How do I delete a directory with files in it in PHP?

rmdir() cannot do it alone — delete the contents first. Iterate the directory bottom-up with RecursiveDirectoryIterator::CHILD_FIRST, calling unlink() on files and rmdir() on directories.

Why does rmdir say the directory is not empty when it looks empty?

It almost certainly contains hidden entries such as .DS_Store, .gitkeep or a lock file. List it with scandir() rather than glob(), which skips names starting with a dot.

What is the difference between rmdir and unlink?

rmdir() removes directories and only empty ones; unlink() removes files. On Windows, a symlink pointing at a directory must be removed with rmdir(), not unlink().

More examples

Recursive delete, children first

Directories are removed from the deepest level upward. Calling rmdir('demo_dir') first would fail, because the parent still contains the sub directory.

Example · php
<?php
mkdir('demo_dir/sub', 0777, true);
file_put_contents('demo_dir/sub/f.txt', 'data');

foreach (['demo_dir/sub/f.txt'] as $f) { unlink($f); }
rmdir('demo_dir/sub');
var_dump(rmdir('demo_dir'));

clearstatcache();
var_dump(file_exists('demo_dir'));

Check emptiness including dotfiles

scandir() reports the hidden entry so you know rmdir() would fail, while glob() returns an empty array and would make the directory look ready to delete.

Example · php
<?php
mkdir('demo_dir');
file_put_contents('demo_dir/.hidden', '');

$entries = array_diff(scandir('demo_dir'), ['.', '..']);
var_dump($entries);            // the dotfile is listed
var_dump(glob('demo_dir/*'));  // glob misses it

unlink('demo_dir/.hidden');
rmdir('demo_dir');

Directory as a lock

mkdir() is atomic, so the second call returns false while the lock is held. rmdir() releases it. This pattern needs no lock file cleanup on read.

Example · php
<?php
if (@mkdir('job.lock')) {
    echo "lock acquired\n";
    var_dump(@mkdir('job.lock'));  // second attempt fails
    rmdir('job.lock');
    echo "lock released\n";
}
clearstatcache();
var_dump(is_dir('job.lock'));

Discussion

  • Be the first to comment on this lesson.