Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filesystem Laravel Package

cakephp/filesystem

CakePHP Filesystem provides utilities for working with files and directories, including the Folder and File classes for creating, reading, writing, and managing paths. A lightweight standalone library usable in any PHP project.

Deep Wiki
Context7

Getting Started

cakephp/filesystem ships the two workhorse utility classes from CakePHP, Cake\Filesystem\Folder and Cake\Filesystem\File. There is no plugin, no service provider, no config file, and no bootstrapping — they are plain objects wrapping mkdir, scandir, fopen, rename, etc.

composer require cakephp/filesystem

If your CakePHP version already pulls it in transitively, skip that step. Then look at exactly two files in vendor/cakephp/filesystem/src: Folder.php and File.php. That is the entire public surface — skim the method list once and you have read the docs.

Your first real use case is almost always "look at what is in a directory":

use Cake\Filesystem\Folder;

$uploads = new Folder(WWW_ROOT . 'uploads');

// read($sort = true, $exceptions = false, $fullPath = false)
[$dirs, $files] = $uploads->read(true, true, true);

foreach ($files as $absolutePath) {
    // ...
}

Two things to internalise immediately:

  • read() returns a tuple of two arrays [directories, files], not one flat list.
  • Passing true as the second argument makes it throw instead of quietly appending to $folder->errors. In new code, always pass true.

Writing is just as direct:

use Cake\Filesystem\File;

$dir = new Folder(TMP . 'reports', true, 0755);   // create = true, recursive mkdir
$file = new File($dir->pwd() . 'daily.csv', true, 0644);
$file->write($csv);                                // returns bytes written, or false
$file->close();

Note that new File($path, true) touches the file on construction. Do not pass true in read-only code paths.

Implementation Patterns

Recursive audit / cleanup job. tree() gives you a nested structure, findRecursive() gives you a flat list of matches:

$tmp = new Folder(TMP . 'cache');

// Flat list of every stale .tmp file, recursively
$stale = $tmp->findRecursive('\.tmp$');

foreach ($stale as $path) {
    if (filemtime($path) < strtotime('-7 days')) {
        (new File($path))->delete();
    }
}

findRecursive()/find() take a regex fragment without delimiters — the class wraps it in preg_match('/' . $pattern . '/', …).

Guarded bulk copy/move. copy() and move() take an options array, and the scheme flag controls collision behaviour, which is the part most people miss:

$src = new Folder(Configure::read('App.uploadDir'));

$ok = $src->copy([
    'to'        => WWW_ROOT . 'archive' . DS,
    'from'      => $src->pwd(),
    'recursive' => true,
    'scheme'    => Folder::MERGE,   // SKIP | MERGE | OVERWRITE | CALLBACK
]);

Use Folder::SKIP for idempotent archive/deploy steps, Folder::OVERWRITE when you genuinely mean "replace", and Folder::CALLBACK when you need to decide per conflict.

Path safety before destructive calls. delete() is recursive, permanent, and does not care about ../. Always fence it:

$base = new Folder(WWW_ROOT . 'uploads');
$target = new Folder($base->pwd() . $userSlug);

if (!$base->inPath($target->pwd())) {
    throw new RuntimeException('Refusing to delete outside upload root');
}
$target->delete();

Validate and canonicalise any user-supplied segment (Folder::normalizePath() / Folder::slashTerm(), or realpath()) before it reaches cd(), delete(), or chmod().

Write-then-rename for anything generated. File::write() truncates in place, so a crash mid-write leaves a corrupt file:

$tmp = new File($dest . '.part', true);
$tmp->write($payload);
$tmp->close();
rename($dest . '.part', $dest);

Wrap it in a service. These classes have no DI hooks, no events, and no interface — which means they are painful to fake in unit tests. Isolate them behind one small class of your own (App\Filesystem\Storage) that takes a base path from Configure, and mock that in application code. You get testability and a one-file migration path to Flysystem later.

Console commands are the natural home. Cache clearing, log rotation, report generation, migration of legacy uploads — anything that says "walk this tree and do something" is a three-line Folder::tree() loop in a Command class.

Upload hygiene. File::safe($clientName) strips directory components and illegal characters from a client-supplied filename — a cheap first line of defence before you assemble a destination path yourself.

Gotchas and Tips

read() returns relative names by default. You get bare basenames unless you pass $fullPath = true. Most "my loop can't find the file" bugs are this.

$folder->errors is the silent failure mode. Every method has a $exceptions argument on read/tree/find/chmod, but create(), delete(), copy(), and move() just return false and push onto Folder::$errors. Check the return value and dump $errors when debugging.

Trailing DS is baked into Folder::$path. pwd() returns a path ending in DS, so naive concatenation produces //. Use $folder->pwd() . 'file.txt' (no separator) or Folder::addPathElement(). When comparing paths from two sources, normalise both first — otherwise /var/www/uploads and /var/www/uploads/ look different.

write() returns an int. fwrite() semantics: a 0-byte write yields 0, which is falsy. Test with !== false, not if ($file->write($x)).

Permissions are requested, not guaranteed. The $mode you pass to new Folder($p, true, 0775) is masked by the process umask. On deploy scripts, follow up with $folder->chmod($path, 0775, true) (recursive) or fix the umask explicitly. Similarly, chown()/chgrp() need privileges you probably don't have in a web request — do that work in a CLI task.

No symlink protection. tree(), dirsize(), and findRecursive() follow the filesystem as-is; a symlink loop can make dirsize() spin. If you accept user-created content, filter results with is_link() before recursing.

dirsize() and File::md5() are both I/O-bound and uncached. File::md5() chunks the read, so it is safe on large files (unlike a file_get_contents() + md5()), but it still touches the whole file. Cache the result in your own table if you call it often.

File holds an open handle. open() twice without close(), or forgetting close() on a long-running worker, leaks descriptors. The destructor closes it, but don't rely on that inside a loop over thousands of files — call close() or use read()/write() which open and close for you.

delete() on Folder removes the directory itself, not just its contents. There is no trash, no dry run, and no confirmation. Print the resolved pwd() in verbose mode before the first real run of any cleanup command.

Locking only matters on write. File::write($data, 'w', true) takes an exclusive lock by default; read() does not. If you have concurrent writers, write-then-rename (above) is more reliable than flock on network filesystems, where flock is frequently a no-op.

This is not a storage abstraction. Everything is local, synchronous, and blocking. There is no S3, no streams beyond local handles, and no adapter interface. The moment you need remote storage, signed URLs, or visibility rules, that is Flysystem (league/flysystem), not this package. Treat cakephp/filesystem as a convenient wrapper for local temp/cache/report/upload chores, and keep it behind your own service boundary so swapping it out is a single class.

Extension points are informal. There are no events or plugin hooks; the only real customisation is subclassing Folder/File to change the default mode or to override read() filtering, or supplying a CALLBACK scheme on copy. Everything else you build around it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource