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

Fs Laravel Package

leafs/fs

leafs/fs is a lightweight filesystem package for Laravel that simplifies working with local and cloud disks. It offers clean helpers for reading, writing, uploading, and managing files while integrating with Laravel's Storage system, making common file operations more expressive and consistent acros...

View on GitHub
Deep Wiki
Context7

Getting Started

leafs/fs is Leaf PHP's filesystem module: a thin, static, local-disk wrapper that gives you a consistent Leaf\FS::… vocabulary for reading, writing, moving, uploading, and downloading files instead of scattering file_get_contents / move_uploaded_file / mkdir calls across your controllers. It is small enough to read in one sitting, which is exactly what you should do first.

Minimal setup

composer require leafs/fs

If you're on a Leaf skeleton (Leaf MVC / Leaf API), the module is already wired in — just call the static methods. On a bare install, reference the class directly:

use Leaf\FS;

FS::createFolder('storage/exports');
FS::writeFile('storage/exports/report.csv', $csv);

Where to look first

  1. vendor/leafs/fs/src/ — the module is effectively a single class. Read it before you write anything non-trivial; you'll learn the exact signatures, which methods return bool vs. throw, and how paths are normalized. Don't rely on the README alone.
  2. The package's composer.json — check the PHP constraint and whether it pulls in any Leaf core dependency you already have pinned.
  3. Your app's writable directory (storage/ or equivalent) — confirm the web user owns it before your first write.

First real use case

The highest-value entry point is user uploads plus generated artifacts, because that's where ad-hoc filesystem code usually rots fastest:

// Store an uploaded avatar
FS::uploadFile($request->file('avatar'), 'storage/uploads/avatars');

// Later, hand a generated file back to the browser
FS::download('storage/exports/report.csv', 'report.csv');

Get those two flows working end-to-end (upload → persist path in DB → download) and you've covered ~80% of what this package is for.

Implementation Patterns

Wrap the static facade in a small storage class. The package is all static calls, which makes it untestable and unswappable if you call it from 40 places. Give each domain its own collaborator:

class AvatarStorage
{
    public function __construct(private string $base) {}

    public function put(UploadedFile $file, int $userId): string
    {
        $dir = "{$this->base}/avatars/{$userId}";
        FS::createFolder($dir);
        FS::uploadFile($file, $dir);
        return "{$dir}/{$file->name}";
    }
}

Now a future move to S3, or a fake in tests, is a one-class change.

Resolve every path against one configured root. Read the base path from leafs/config (or an env var) once, and build absolute paths from it. Never let a relative path leak into a call — see the gotchas below.

Compose with the rest of the Leaf stack. This is where the package earns its keep: it slots into Leaf's request/response flow without adapters.

  • leafs/request → file objects feed straight into uploadFile.
  • leafs/response / leafs/http → use download as the terminal statement of a controller action.
  • leafs/config → single source of truth for storage roots and allowed extensions.
  • leafs/logger → log every write/delete for an audit trail; filesystem mutations are otherwise invisible.

Export / temp-file workflow. A pattern that stays clean:

  1. Build a unique filename (storage/exports/{uuid}.csv) — never reuse a name, never trust user input in it.
  2. Ensure the directory exists (createFolder, tolerating "already exists").
  3. Write, then hand off to download or a queued cleanup job.
  4. Sweep old exports on a schedule; this package has no retention logic of its own.

Check before you act, and check the result after. exists / isFile / isDirectory guards plus a truthiness check on the return value beat letting a failed rename silently corrupt a record that already points at the new path. Order your DB writes so the file operation succeeds first, then persist the path.

Idempotent directory creation. Make a ensureDir() helper that creates and ignores "exists" — you'll call it at the top of nearly every write path.

Testing. Point your storage class at a sys_get_temp_dir() sandbox per test and clean up in teardown. Because the underlying operations are real I/O, integration-style tests are more honest here than mocking statics.

Gotchas and Tips

Relative paths follow the current working directory, not your app root. The same FS::writeFile('storage/x.txt') can land in different places under php-fpm, a CLI command, a queue worker, or PHPUnit. Always pass absolute paths built from a configured base. This is the single most common source of "it works locally but not in the worker" bugs with thin filesystem wrappers.

download() is terminal — it emits headers. Calling it mid-controller breaks JSON responses, post-response middleware, and most test assertions. Treat it as the last line of a dedicated action, and keep it out of anything that also wants to log or redirect.

Return values are likely bare booleans, not exceptions. There's no rich error model in a module this size. Every write/rename/copy needs an explicit check, and you should decide once whether failures throw your own exception or return a result object — don't let a false travel silently into a DB column.

Permissions and PHP limits, not the package, will bite you first. Ensure the web/queue user owns the storage tree; check upload_max_filesize, post_max_size, and max_file_uploads before blaming the library. Test with a file larger than 2 MB — that's where default limits show up.

Writes are not atomic. A crash or concurrent writer mid-write can leave a truncated file, and a reader may see a partial one. For anything that matters, write to {name}.tmp with an exclusive lock, then rename into place (rename is atomic on the same filesystem). Append-style logging is the one case where in-place writes are fine.

Path traversal and extension allow-lists are your job. Never pass a raw client-supplied filename into a path. Generate names server-side, keep the extension from a fixed allow-list, and reject anything containing .. or a separator. This package will happily write outside your storage root if you ask it to.

Local disk only. There is no cloud adapter, no stream abstraction, no driver interface. If S3/GCS is on the roadmap even vaguely, put your own Storage interface in front now — retrofitting it later is the expensive version of the same work.

Maintenance risk is real and should shape your usage. Three stars, zero dependents, a single maintainer, and a release date you should sanity-check against your dependency policy. Mitigations: pin an exact version in composer.json, commit the lockfile, and keep your usage confined to the handful of methods you've personally read the source of. Because the surface is small, "vendoring the behaviour into a 60-line helper" is a genuinely viable fallback if the package ever goes quiet.

Extension points are essentially nil — that's fine. There's no plugin layer worth hunting for. The correct extension point is your wrapper class: put validation, base-path resolution, naming, logging, and atomic-write logic there, and let leafs/fs stay the dumb, readable primitive it was designed to be.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons