graham-campbell/flysystem
Laravel Flysystem is a Flysystem bridge for Laravel by Graham Campbell, built on Laravel Manager. It provides a unified filesystem manager with support for local, S3, Dropbox, and other storage adapters, requiring PHP 7.4-8.1 and Laravel 8.
What this package is. graham-campbell/flysystem is a thin Laravel service-provider/manager wrapper around league/flysystem v1.x. It registers a flysystem container binding, exposes a Flysystem facade, adds a connection()/setDefaultConnection() manager API (via graham-campbell/laravel-manager), and ships a publishable config/flysystem.php with per-driver connection stubs plus an optional cache layer (Illuminate or adapter-backed).
Architecture fit — this is the central finding. Laravel has shipped a first-party Flysystem integration since 5.0: the Storage facade and Illuminate\Filesystem\FilesystemManager already provide a manager, named disks, disk() switching, and a default-disk concept. This package materially duplicates that surface. The functional delta versus native Laravel is narrow:
league/flysystem-cached-adapter).connection() vs disk()), which is not a benefit — it is a second vocabulary for the same concept.For any greenfield or Laravel 8+ codebase, adopting this package means adding a competing filesystem abstraction alongside Storage, with no meaningful capability gain unless a specific non-native adapter is required.
Integration feasibility. Mechanically simple: composer require graham-campbell/flysystem:^8.0, publish config, optionally alias the facade. Auto-discovery handles the provider. No schema changes, no middleware, no queue/worker implications. The only real friction is dependency fan-out — each adapter pulls its own transitive tree (league/flysystem-aws-s3-v3 ^1.0, spatie/flysystem-dropbox ^1.0, superbalist/flysystem-google-storage ^7.2, alcaeus/mongo-php-adapter ^1.1 for GridFS, etc.). GridFS and Azure in particular drag in heavy, aging SDKs.
Technical risk — high, and concentrated in version ceiling. The package is pinned to a specific, closing window:
FilesystemInterface was removed/reshaped, adapters rewritten). This is not a bump-the-constraint migration; it is a rewrite of every call site.config/flysystem.php against the package's config between releases, meaning config drift is a known, recurring operational hazard.Key questions to resolve before any commitment.
Storage covers it and this package should be rejected outright.Storage anywhere in the codebase? If yes, we would run two filesystem abstractions concurrently, doubling the mental model and the test surface.league/flysystem v1 has effectively stopped.flysystem container binding collide with anything? Laravel's container uses filesystem/filesystem.disk — the flysystem key is distinct, so collision risk is low, but confirm no internal packages resolve that key.Stack fit — conditionally acceptable, narrowly scoped. There is exactly one defensible adoption pattern: a Laravel 8 application that needs a driver Laravel does not ship natively (Azure Blob, Dropbox, Google Cloud Storage, GridFS, WebDAV, Zip) and cannot justify writing a custom FilesystemAdapter. Even then, I would first evaluate whether a modern, Flysystem 3-compatible driver package exists for that backend — that path avoids the L8 ceiling entirely. Outside that narrow case, the recommendation is reject and standardize on native Storage.
Recommended sequencing (if we proceed).
composer audit).Flysystem:: facade calls leak into controllers, jobs, or domain services. Define a small internal port (e.g., ObjectStore with put/get/delete/url/exists) and implement it once with this package. This is the single highest-leverage decision in the whole engagement: it converts a package swap into a single-class change later.default deliberately, and keep local for anything that touches the request lifecycle. Add the config file to code review ownership so README-mandated diffs between releases are actually caught.Storage call sites onto the new port only if the driver genuinely must serve them; otherwise leave Storage untouched for local/S3/SFTP and let this package cover the single exotic backend. Two abstractions with a hard, documented boundary is manageable; two abstractions with overlapping scope is not.Migration path off the package. Because the interface layer in step 3 is the only call site set, the exit is: implement ObjectStore a second time against Flysystem 3 (native Storage when Laravel 9+'s adapter story covers the backend; a modern driver package otherwise), swap the container binding, run the port's contract tests, delete the dependency. Budget roughly 1–2 engineer-weeks if step 3 is honored, 4–6 weeks if facade calls leak into application code — that delta is the entire argument for step 3.
Compatibility guardrails. Pin graham-campbell/flysystem exactly (8.0.x), not ^8.0, and commit composer.lock. Add a CI job that fails on composer update attempts touching this subtree. Explicitly forbid PHP 8.2+ images for this app, since the package declares 8.1 as its ceiling and no patch will raise it.
Maintenance. Low day-to-day, but with a hard expiry date. The code surface is small and inert; there is nothing to tune. The recurring cost is tracking: config diffs between releases (which the README mandates) and a quarterly re-evaluation of the Flysystem 1.x EOL posture. Realistically, the only maintenance event that matters is the eventual forced removal, and its cost is set entirely by how well the abstraction boundary was enforced. The package itself receives minimal upstream activity — recent releases are version-ceiling adjustments (add L8, drop L5, drop PHP < 7.4), not feature work.
Support. Single-maintainer, MIT-licensed OSS with no vendor SLA. There is a Tidelift commercial subscription path, which is the one lever available if enterprise support is a procurement requirement — worth pricing if the driver is on a revenue path, though note Tidelift guarantees maintenance, not a Laravel 9 port. Security triage routes to [email protected], which is a reasonable process but not a response-time commitment. Internal ownership must be assigned by name; there is no external party to escalate to.
Scaling. Adequately horizontal — each PHP process resolves connections from config, and the manager caches the resolved connection per request. The scaling concerns are all backend-side and shared with native Laravel: S3/Azure/GCS throughput and per-object request cost, SFTP connection limits (a real ceiling under concurrency — SFTP adapters do not pool well), and GridFS performance under load. The package's cache layer deserves scrutiny at scale: adapter-level caching can serve stale listings after writes from other nodes, and Illuminate-cache-backed caching introduces a shared cache dependency on the read path — a cache outage becomes a filesystem outage. Prefer no cache for correctness-critical reads; if caching is enabled, define and test the invalidation story explicitly.
Failure modes to design for.
exists/list against each configured connection and run it post-deploy.alcaeus/mongo-php-adapter) and Azure (league/flysystem-azure-blob-storage) pull aging SDKs; a transitive security advisory can block a release with no upstream fix available. Monitor composer audit in CI and pre-agree a fallback (native adapter or direct SDK usage).composer update. This should be captured as a known dependency-blocking item on the platform upgrade roadmap on day one, not discovered during the upgrade.Ramp-up. Low for any engineer familiar with Laravel, because the underlying Filesystem API is the standard Flysystem v1 interface and the README's examples are copy-pasteable. The genuine ramp cost is conceptual: new hires will reasonably ask why the codebase has both Storage::disk() and Flysystem::connection() doing the same job. That question has no good answer, which is itself a signal. Mitigate with a one-page ADR recording the decision, the specific driver justification, the L8 pin, and the exit criteria — and link it from the interface's docblock. Onboarding then costs minutes instead of a recurring architectural debate.
How can I help you explore Laravel packages today?