Laravel's File and Storage facades wrap PHP's native filesystem functions with a friendlier API — covering checking for a folder's existence, creating nested directories, copying and deleting recursively, and manipulating file contents directly.
Checking if a folder exists, and creating a nested one
use Illuminate\Support\Facades\File;
if (! File::exists(storage_path('app/exports'))) {
File::makeDirectory(storage_path('app/exports'), 0755, true);
}
The third argument to makeDirectory() (true, for "recursive") is what lets it create every missing parent directory in the path at once — without it, creating a/b/c would fail if a/b doesn't already exist.
Copying a folder recursively
File::copyDirectory(storage_path('app/templates'), storage_path('app/exports/templates'));
Moving files or a folder
File::move(storage_path('app/temp/report.pdf'), storage_path('app/reports/report.pdf'));
File::moveDirectory(storage_path('app/old-uploads'), storage_path('app/uploads'));
Deleting a folder recursively, with its files
File::deleteDirectory(storage_path('app/temp'));
This deletes the directory itself along with everything inside it — there's no separate step needed to empty it first.
Creating or replacing a file's contents
File::put(storage_path('app/logs/custom.log'), "Log started\n");
Prepending or appending to a file's contents
File::prepend(storage_path('app/logs/custom.log'), "[".now()."] New session started\n");
File::append(storage_path('app/logs/custom.log'), "[".now()."] Request processed\n");
append() is the common choice for log-style writing; prepend() is less common but useful when the newest entries should show first without needing to reverse the file's contents on read.
Zipping a file or folder
use ZipArchive;
$zip = new ZipArchive();
$zipPath = storage_path('app/exports/report.zip');
if ($zip->open($zipPath, ZipArchive::CREATE) === true) {
$zip->addFile(storage_path('app/reports/report.pdf'), 'report.pdf');
$zip->close();
}
Zipping an entire folder, including nested subfolders
function zipDirectory(string $source, string $destination): void
{
$zip = new ZipArchive();
$zip->open($destination, ZipArchive::CREATE);
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($source),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if (! $file->isDir()) {
$relativePath = substr($file->getPathname(), strlen($source) + 1);
$zip->addFile($file->getPathname(), $relativePath);
}
}
$zip->close();
}
Zipping a whole folder (rather than a single file) needs this recursive directory walk, since ZipArchive itself doesn't have a built-in "add this entire folder" method — RecursiveIteratorIterator is the standard PHP tool for walking every file in a nested directory structure.
Extracting (unzipping) a zip file
$zip = new ZipArchive();
if ($zip->open(storage_path('app/imports/data.zip')) === true) {
$zip->extractTo(storage_path('app/imports/extracted'));
$zip->close();
}
Why File:: methods are preferable to raw PHP functions in a Laravel app
Beyond the slightly friendlier API, using Laravel's File facade (rather than calling mkdir(), copy(), unlink() directly) keeps filesystem operations mockable in tests via File::fake() or a swapped facade implementation — genuinely useful when a test needs to verify a file was written without actually touching disk.