adlawson/vfs
Virtual file system for PHP using the stream wrapper API. Mount a vfs:// scheme and use built-in functions (fopen, require, file_get_contents) or filesystem libraries like Symfony/Laravel. Emulates real streams, including PHP warnings and edge cases.
composer require adlawson/vfs
use Vfs\FileSystem;
$fs = FileSystem::factory('vfs://');
$fs->mount();
vfs:// as a prefix for all filesystem operations:
file_put_contents('vfs://test.txt', 'Hello, VFS!');
$content = file_get_contents('vfs://test.txt');
Replace disk I/O in Laravel tests with VFS to avoid filesystem pollution:
public function test_file_upload()
{
$fs = FileSystem::factory('vfs://');
$fs->mount();
Storage::fake('local');
Storage::disk('local')->put('uploaded.txt', 'Test content');
// Assert using VFS
$this->assertEquals('Test content', file_get_contents('vfs://uploaded.txt'));
}
Use Vfs\Node\Directory and Vfs\Node\File for programmatic filesystem setup:
$fs = FileSystem::factory('vfs://');
$fs->mount();
// Create a directory with files
$root = $fs->get('/');
$root->addChild(new Directory(['subdir' => new Directory(['file.txt' => new File('Content')])]));
// Access via PHP functions
file_put_contents('vfs://subdir/file.txt', 'Updated content');
Extend Laravel’s Storage facade to use VFS:
Storage::extend('vfs', function ($app, $config) {
$fs = FileSystem::factory('vfs://');
$fs->mount();
return new \Illuminate\Filesystem\FilesystemAdapter(
new \Vfs\Adapter\StreamWrapperAdapter('vfs://'),
'vfs',
$config
);
});
// Usage
Storage::disk('vfs')->put('file.txt', 'Hello');
$this->assertEquals('Hello', file_get_contents('vfs://file.txt'));
Safely require files from VFS (avoids eval security risks):
$fs = FileSystem::factory('vfs://');
$fs->mount();
file_put_contents('vfs://dynamic.php', '<?php echo "Dynamic content";');
$result = include 'vfs://dynamic.php'; // Outputs: Dynamic content
Works with Symfony’s Filesystem or Laravel’s Filesystem:
$symfonyFs = new \Symfony\Component\Filesystem\Filesystem();
$symfonyFs->mkdir('vfs://symfony_dir');
$laravelFs = new \Illuminate\Filesystem\Filesystem();
$this->assertTrue($laravelFs->exists('vfs://symfony_dir'));
Mock Laravel’s Filesystem or Cache services:
public function test_cache_with_vfs()
{
$fs = FileSystem::factory('vfs://');
$fs->mount();
Cache::store('file')->put('key', 'value');
// Assert via VFS
$this->assertEquals('value', file_get_contents('vfs://storage/framework/cache/data/file/key'));
}
Use VFS to mock S3 or other cloud adapters:
public function test_s3_upload()
{
$fs = FileSystem::factory('vfs://');
$fs->mount();
Storage::fake('s3');
Storage::disk('s3')->put('file.txt', 'Cloud content');
// Assert via VFS (adjust path to match S3 adapter's local simulation)
$this->assertEquals('Cloud content', file_get_contents('vfs://s3_file.txt'));
}
Unmount the VFS after each test to avoid leaks:
public function tearDown(): void
{
$fs = FileSystem::getInstance();
if ($fs->isMounted()) {
$fs->unmount();
}
}
// ❌ Bad: Mount once in setUp()
public function setUp(): void { FileSystem::getInstance()->mount(); }
// ✅ Good: Mount/unmount per test
public function test_something()
{
$fs = FileSystem::factory('vfs://');
$fs->mount();
// ... test logic ...
$fs->unmount();
}
vfs://. Check for conflicts:
if (!stream_wrapper_register('vfs', \Vfs\StreamWrapper::class)) {
throw new \RuntimeException('VFS stream wrapper already registered');
}
storage_path() or public_path() won’t resolve to VFS. Use absolute vfs:// paths:
// ❌ Fails: Uses Laravel's resolved path
Storage::disk('local')->put('file.txt', 'Content');
// ✅ Works: Uses VFS directly
file_put_contents('vfs://file.txt', 'Content');
chmod sparingly:
// ❌ May fail silently
chmod('vfs://file.txt', 0644);
// ✅ Stick to VFS methods
$file = new \Vfs\Node\File('Content');
$file->setContent('Content'); // No permission handling
link() with caution (may not work as expected).flock() in tests or use in-memory alternatives.Check if the VFS is mounted:
$fs = FileSystem::getInstance();
if (!$fs->isMounted()) {
$fs->mount();
}
List all files/directories:
$root = FileSystem::getInstance()->get('/');
print_r($root->getChildren());
VFS triggers warnings like real streams. Suppress or log them:
// Suppress warnings
$oldErrorHandler = set_error_handler(function () {});
file_put_contents('vfs://nonexistent', 'Content'); // No warning
restore_error_handler();
// Or log warnings
set_error_handler(function ($errno, $errstr) {
error_log("VFS Warning: $errstr");
});
Use unique paths per test to avoid collisions:
$fs = FileSystem::factory('vfs://unique_' . uniqid());
$fs->mount();
Extend \Vfs\StreamWrapper for custom behavior:
class CustomStreamWrapper extends \Vfs\StreamWrapper
{
public function stream_open($path, $mode, $options, &$opened_path)
{
// Custom logic (e.g., logging)
error_log("Opening $path");
return parent::stream_open($path, $mode, $options, $opened_path);
}
}
// Register
stream_wrapper_register('custom_vfs', CustomStreamWrapper::class);
Create a provider to auto-register VFS for testing:
// app/Providers/VfsServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Vfs\FileSystem;
class VfsServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('vfs', function () {
$fs = FileSystem::factory('vfs://');
$fs->mount();
return $fs;
});
}
}
Reusable VFS setup/teardown:
trait UsesVfs
{
protected function createVfs()
{
$fs = FileSystem::factory('vfs://' . uniqid());
$fs->mount();
return $fs;
}
protected function tearDownVfs($fs)
{
$fs->unmount();
}
}
// Usage in test
public function testSomething()
{
$fs = $this->createVfs();
// ... test logic ...
$this->tear
How can I help you explore Laravel packages today?