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

illuminate/filesystem

The Illuminate Filesystem package provides a fluent API for working with local and cloud-based filesystems in Laravel. It offers file operations, storage drivers, and seamless integration with Laravel’s filesystem abstraction.

View on GitHub
Deep Wiki
Context7

Getting Started

In a Laravel app you already have this package—Illuminate\Filesystem ships with the framework and is aliased as the File facade. Standalone, composer require illuminate/filesystem pulls in league/flysystem, symfony/filesystem, illuminate/contracts, illuminate/collections, illuminate/macroable, and illuminate/support (PHP 8.3+ in current releases).

There are two entry points, and picking the right one on day one matters:

Class Use it for Reached via
Illuminate\Filesystem\Filesystem Local, in-process file I/O — reading config, writing cache stubs, path introspection new Filesystem(), File:: facade
Illuminate\Filesystem\FilesystemAdapter Anything disk-backed (local, S3, FTP, …) via Flysystem Storage::disk('s3'), or app('filesystem')->disk()

Start with config/filesystems.php — that's the source of truth for every disk your app can address. Then pick one real use case and migrate it end-to-end. The highest-value first use case is a user upload:

use Illuminate\Support\Facades\Storage;

// Stored as a hashed filename under avatars/, returns the path
$path = Storage::disk('s3')->putFile('avatars', $request->file('avatar'));

$url = Storage::disk('s3')->url($path);

For plain local I/O, the minimal surface is exists(), get(), put(), delete(), copy(), move(), plus ensureDirectoryExists() before writing to a new path:

use Illuminate\Filesystem\Filesystem;

$fs = new Filesystem();

if (! $fs->exists($path = base_path('bootstrap/cache/x.php'))) {
    $fs->ensureDirectoryExists(dirname($path), 0755);
    $fs->put($path, '<?php return [];');
}

Read Filesystem first (small, local, no container), then FilesystemAdapter when you need a disk. Everything below assumes you eventually need both.

Implementation Patterns

One disk per concern, resolved by name. Don't hardcode config('filesystems.default') at call sites. Inject or resolve a named disk (Storage::disk('uploads'), Storage::disk('backups')) so dev/staging (local) and prod (S3) differ only in config. This is the whole point of the package and the cheapest abstraction you'll ever buy.

Uploads: prefer putFile / putFileAs over put. They accept the raw UploadedFile, generate a collision-resistant filename, and return the stored path. putFileAs lets you keep a deterministic name (e.g. "avatars/{$user->id}.png"), but be aware of the visibility argument order (putFileAs($dir, $file, $name, $options)).

Streams for anything big. Both APIs accept resource handles, and FilesystemAdapter exposes readStream() / writeStream(). Passing a stream into put() avoids holding a multi-hundred-MB file in memory — the difference between a working queue job and an OOM kill.

$handle = fopen($localPath, 'r');
Storage::disk('s3')->writeStream('exports/report.csv', $handle);

Locked writes for anything authoritative. For critical config/cache files, use Filesystem::replace() (writes via a temp file with LOCK_EX) rather than bare put(). put($path, $contents, lock: true) is the explicit form. Verify what's actually available in your installed version before designing around a specific "atomic" helper name — replace() is the reliable primitive.

Traversal returns Finder objects, so iterate lazily. files($dir, $hidden = false), allFiles($dir), and directories() are Symfony Finder-backed and yield SplFileInfo. That's a feature: it's lazy and cheap per item.

foreach (File::allFiles(lang_path()) as $file) {
    $locale = $file->getPathInfo()->getFilename(); // not basename()
}

glob(), lines(), json(), getRequire() as convenience wrappers. getRequire() / requireOnce() are the clean way to load PHP config arrays; lines() returns a lazy collection for log or CSV tailing; json() decodes or throws.

Test with Storage::fake(), not with mocks.

Storage::fake('s3');

$this->post('/avatar', ['avatar' => UploadedFile::fake()->image('a.jpg')])
     ->assertOk();

Storage::disk('s3')->assertExists("avatars/{$user->id}.png");

Storage::fake() swaps the disk for a local temp directory and gives you assertExists / assertMissing / assertDirectoryEmpty. It's dramatically faster than hitting real S3 and it exercises the same adapter code path. Call it before anything resolves the disk.

Non-Laravel / standalone projects. Instantiate Filesystem directly and skip the facade. If you need disks outside Laravel, bootstrap a minimal container, register Illuminate\Filesystem\FilesystemServiceProvider, and configure filesystems.disks manually — at that point the dependency weight is real and symfony/filesystem alone may be the better call.

Macros for project sugar, classes for logic. Filesystem::macro('jsonPut', fn ($path, $data) => $this->put($path, json_encode($data, JSON_PRETTY_PRINT))) is fine for a one-liner used everywhere. Anything with branching or validation belongs in a dedicated JsonFileReader/ConfigWriter class.

Path resolution, once. Centralize storage_path() / base_path() calls in one helper or config entry. Ad-hoc relative paths are the single most common environment-specific bug in this area.

Gotchas and Tips

Return types differ between the two classes. Filesystem::put() returns int|false (bytes written), while FilesystemAdapter::put() returns bool. Code like if ($result > 0) silently breaks when you swap File for Storage. Also, Filesystem::delete() returns bool for a single path but returns an array of results for a list.

Exception types are not uniform. A missing read via Filesystem::get() throws Illuminate\Contracts\Filesystem\FileNotFoundException; failures in copy(), move(), and delete() surface as Symfony\Component\Filesystem\Exception\IOException (catch the IOExceptionInterface). Don't write catch (Exception) expecting one type — and don't assume a single exception contract covers the whole class.

exists() conflates files and directories. Use isFile() / isDirectory() (or fileExists() on the adapter) when it matters. isEmptyDirectory() does a full scan and can be slow on large trees.

path() only works on local disks. Storage::disk('s3')->path('x') throws — there is no local path for a remote object. If your code needs path(), that code is local-only; branch on the driver or keep the local disk separate.

url() and temporaryUrl() need configuration and driver support. Storage::url() requires the disk's url key (and, for local, an actual php artisan storage:link. Without it the URL 404s and looks like a code bug). temporaryUrl() is unsupported on the local driver and will throw — use Storage::temporaryUploadUrl() for direct-to-S3 browser uploads.

glob() changed shape. In modern versions it returns a Collection of SplFileInfo objects, not the array of path strings older tutorials show. Code doing str_ends_with($path, '.php') on glob results is now operating on objects.

Hidden files are opt-in. Filesystem::files($dir) skips dotfiles by default; pass $hidden = true if your scanner needs .env-style files. Getting this wrong makes "file exists in dev but not in the deploy" bugs.

No built-in flock() in the disk layer. Flysystem's default local write is not lock-aware. For concurrent writes either use Filesystem::replace(), add Flysystem's lock plugin, or push the work onto a queue with a single worker. Wrapping fopen()/flock() yourself is legitimate but must be paired with a matching read path.

Storage::disk() memoizes. The manager caches resolved drivers per request. If you mutate config at runtime (tests, multi-tenant setups), call Storage::forgetDisk('name') or you'll silently keep the old connection.

RefreshFilesystem / mocks: prefer Storage::fake() for feature tests and a thin wrapper class (constructor-injected Filesystem) for unit tests. Don't Mockery::mock(Filesystem::class) broadly — you'll end up asserting implementation details of basic I/O.

Symlinks are platform-bound. link() / relativeLink() fail on Windows without elevated privileges/developer mode. Guard with is_link() checks and test on the actual target platform.

Directory scanning has cost even when lazy. allFiles() loads metadata per entry. Never point it at /var/log, vendor/, or node_modules inside a request or a long-running worker; scope it or cache the result.

deleteDirectory() follows into symlinked directories in ways that surprise people — on older behavior it could recurse through the link. Be explicit about symlinks in cleanup routines.

Macros leak. Filesystem and FilesystemAdapter are both Macroable, which makes it easy for one package's macro('yaml') to collide with another's. Namespace custom macros ('app.jsonPut') or skip them.

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.
wandesnet/mercadopago-laravel
tsfcorp/email
larafony/core
laravel-enso/currencies
tartan/laravel-online-payment
gam/laravel-sat-catalogs
cloudcreativity/laravel-stripe
abdulelahragih/querybuilder
wedevbr/celcoin-laravel
surazdott/nepali-date
devsquad-cockpit/laravel
coding-socks/laravel-upload-handler
artisanbarista/laravel-shield
shopwwi/laravel-cache
instance-code/repository
flooris/prestashop-models
lab2view/laravel-generator
lenderspender/laravel-wizard
joecampo/laravel-notification-basecamp
square1/laravel-collection-rolling-average