Reader Stacks

Laravel File and Folder Operations: Copying, Zipping, and Content Manipulation

Laravel's File and Storage facades cover checking, creating, copying, and deleting folders — for zipping, PHP's built-in ZipArchive class does the actual work.

Laravel File and Folder Operations: Copying, Zipping, and Content Manipulation

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.