league/flysystem-vfs
Flysystem adapter for the PHP VFS library that lets you mount and use a virtual filesystem with Flysystem. Install via Composer and pass a VfsAdapter to a new Filesystem instance.
Architecture fit. This package is a thin bridge: a single VfsAdapter class that maps Flysystem's adapter contract onto michael-donat/php-vfs, an in-memory filesystem exposed through the vfs:// stream wrapper. It was written against Flysystem 1.x's AdapterInterface. Modern Laravel (9/10/11) ships Flysystem 3.x, whose adapter contract was rewritten (FilesystemAdapter with write/writeStream/read/readStream/listContents/move/copy/fileExists/directoryExists/lastModified/mimeType/fileSize/visibility/setVisibility). The bridge does not implement that interface, so it is architecturally misaligned with any current Laravel stack. Its only legitimate fit is a legacy Laravel 5.x application still pinned to Flysystem 1.x.
Integration feasibility. Mechanically simple — composer require, then either register a custom disk driver via Storage::extend('vfs', ...) in a service provider or instantiate new Filesystem(new VfsAdapter(new Vfs)) directly. The blocker is dependency resolution: Composer will almost certainly refuse to co-install this alongside league/flysystem ^2|^3, and forcing it means downgrading the Flysystem that Laravel's own filesystem layer depends on. Realistic effort is therefore not "integration" but "fork and port" — roughly a small adapter rewrite plus test coverage, not a one-line install.
Technical risk. High and multi-sourced:
php-vfs is similarly dormant and depends on PHP stream-wrapper internals, which are sensitive to PHP 8 type and behavior changes.vfs:// contents across tests produces hard-to-diagnose pollution.local/s3 disks, creating "green in CI, broken in prod" surprise.Key questions.
league/flysystem-memory (maintained) or Laravel's Storage::fake() already satisfies it.Stack fit. Against Laravel 11 / Flysystem 3 there is no out-of-box fit. Recommended posture: do not register a vfs disk driver in any application we intend to keep. For test-time isolation use Storage::fake('disk') (Laravel's built-in FakeFilesystemAdapter) or the flysystem-memory adapter wired to a custom disk. For sandboxed or plugin I/O, prefer a namespaced directory on the local driver with an explicit cleanup step. Only if a true in-memory namespace is a hard requirement should this package's lineage be considered — and then via a fork, not the published artifact.
Migration path. If a legacy app genuinely depends on it:
VirtualFilesystem contract (write/read/list/delete) so the adapter becomes an implementation detail.flysystem-memory or a temp-dir local disk, so swapping is a config change rather than a refactor.Compatibility. Pin Composer constraints explicitly; do not rely on a floating ^1.0. Verify PHP-version compatibility of both the adapter and php-vfs on the target runtime (PHP 8.x is untested territory for this stack). Confirm the stream wrapper is registered and unregistered per test/process lifecycle. Validate whether visibility and metadata operations work or are silent no-ops, and document whichever is true.
Sequencing.
Storage::fake() or flysystem-memory first; most "we need a VFS" asks collapse at this step.php-vfs directly (without Flysystem) to confirm PHP-version viability.League\Flysystem\FilesystemAdapter rather than resurrecting the 2016 class.VirtualFilesystem contract and a service-provider registration.Maintenance. Effectively zero upstream maintenance; adopting this means we own it. Budget for a private fork, composer patches or a mirrored package, and periodic PHP-upgrade audits. The thin adapter class is cheap to port; the embedded php-vfs dependency is the recurring tax, since stream-wrapper behavior drifts across PHP releases. Treat any adoption as an internal library commitment, not a dependency.
Support. No upstream issue triage, no security-response process, and a negligible community knowledge base (19 stars, 0 dependents means few searchable answers). On-call engineers must understand both Flysystem adapter internals and PHP stream wrappers — a narrow skill set. The escalation path is entirely internal, so runbooks for wrapper registration, cleanup, and known metadata gaps must be written before adoption, not after the first incident.
Scaling. Scales with process RAM, not disk. Unsuitable for large files, high-throughput writes, or durable storage; it will OOM under volume. Semantics are single-process with no concurrency guarantees across workers — each PHP-FPM worker gets its own isolated VFS, which is correct for ephemeral CI workloads and wrong for anything requiring shared state. Appropriate ceiling: small-file, test, and sandboxed ephemeral workloads only.
Failure modes. Memory exhaustion on large or accumulating writes; stream-wrapper double-registration or leakage causing cross-test contamination and flaky suites; fatal errors from Flysystem API mismatch after any dependency bump; silent loss of visibility/MIME/last-modified semantics so code paths pass under VFS and fail on local/s3; and orphaned vfs:// contents holding memory after the logical test has ended. Each needs an explicit detection or cleanup step in the test harness.
Ramp-up. Low to learn, high to own safely. The usage surface is small enough that a mid-level engineer can wire it in an afternoon; the risk is concentrated in what they will not know — that the wrapper is process-global, that metadata is approximate, that the package is unmaintained. Plan for a short internal write-up plus a review checkpoint, and gate adoption on one engineer owning the wrapper lifecycle. If that owner does not exist, the honest TPM recommendation is to skip this package entirely and standardize on Storage::fake() / flysystem-memory, which delivers the same testing benefit with no ownership burden.
How can I help you explore Laravel packages today?