twistor/flysystem-stream-wrapper
Installation
composer require twistor/flysystem-stream-wrapper
Register the service provider in config/app.php:
'providers' => [
// ...
Twistor\FlysystemStreamWrapper\FlysystemStreamWrapperServiceProvider::class,
],
Basic Usage
Define a filesystem in config/filesystems.php (e.g., s3):
'disks' => [
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'bucket' => 'my-bucket',
],
],
Register as Stream Wrapper
In a service provider or AppServiceProvider:
use Twistor\FlysystemStreamWrapper\Facades\FlysystemStreamWrapper;
public function boot()
{
FlysystemStreamWrapper::register('s3', 'my-s3-wrapper');
}
Now access files via PHP streams:
$file = fopen('my-s3-wrapper://path/to/file.txt', 'r');
Replace direct filesystem operations (e.g., Storage::disk('s3')->read()) with stream wrappers for:
fopen(), file_get_contents(), or file_put_contents() with the wrapper.League\MimeTypeDetection) now work with remote storage.tmpfile() with a remote wrapper for processing files without local copies.Register Wrappers Dynamically
Use the facade to register wrappers conditionally (e.g., in a boot() method):
if (app()->environment('production')) {
FlysystemStreamWrapper::register('s3', 'prod-s3');
}
Custom Stream Wrapper Logic Extend the wrapper behavior by binding a custom adapter:
use Twistor\FlysystemStreamWrapper\StreamWrapper;
use League\Flysystem\Adapter\Local;
StreamWrapper::extend('custom', function ($path) {
$adapter = new Local(storage_path('app/custom'));
return new StreamWrapper($adapter, $path);
});
Hybrid Local/Remote Workflows Combine local and remote files in a single operation:
// Read from S3, write to local
$remote = fopen('my-s3-wrapper://file.txt', 'r');
$local = fopen(storage_path('app/file.txt'), 'w');
stream_copy_to_stream($remote, $local);
FlysystemStreamWrapper::register('s3', 'my-s3-wrapper', ['cache' => false]);
FlysystemStreamWrapper::register('s3', 'my-s3-wrapper', [
'default_permissions' => 0644,
]);
try {
$contents = file_get_contents('my-s3-wrapper://file.txt');
} catch (\RuntimeException $e) {
Log::error('Stream wrapper error: ' . $e->getMessage());
}
Storage facade or PHP’s stream_wrapper_register():
$this->app->singleton('stream', function () {
stream_wrapper_register('test', new class extends \StreamWrapper {
// Custom logic
});
});
Stream Wrapper Naming Conflicts
php://, zip://).s3-wrapper:// instead of s3://).Case Sensitivity
FlysystemStreamWrapper::register('s3', 'my-s3-wrapper', ['normalize_paths' => true]);
Memory Limits
file_get_contents() may exceed PHP’s memory_limit.fopen() + stream_get_contents() for chunked reading:
$stream = fopen('my-s3-wrapper://large-file.txt', 'r');
$contents = stream_get_contents($stream, -1, 0);
fclose($stream);
Concurrent Access Issues
FlysystemStreamWrapper::register('s3', 'my-s3-wrapper', ['locking' => false]);
Enable Verbose Logging Configure the underlying Flysystem adapter for debug logs:
'filesystems' => [
'disks' => [
's3' => [
'driver' => 's3',
'debug' => env('APP_DEBUG'), // Enable debug logs
],
],
],
Check Stream Wrapper Registration Verify the wrapper is registered:
$wrappers = stream_get_wrappers();
var_dump(in_array('my-s3-wrapper', $wrappers)); // Should return true
Handle Missing Files Gracefully
Use file_exists() to check for files before operations:
if (!file_exists('my-s3-wrapper://file.txt')) {
Log::warning('File not found in S3 wrapper');
}
Custom Stream Metadata
Override metadata handling (e.g., stat(), is_readable()):
StreamWrapper::macro('customStat', function ($path) {
// Custom logic to fetch metadata
return [
'size' => 1024,
'mtime' => time(),
];
});
Event Listeners Listen for stream events (e.g., file creation/deletion) via Flysystem events:
use League\Flysystem\Event\Created;
event(new Created('my-s3-wrapper://file.txt', $context));
Proxy Wrappers Chain wrappers for layered access (e.g., S3 → local cache):
FlysystemStreamWrapper::register('cache', 'cache-wrapper', [
'adapter' => new \League\Flysystem\Cached\CachedAdapter(
Storage::disk('s3')->getAdapter(),
Storage::disk('local')->getAdapter()
),
]);
Fallback Mechanisms Implement fallback logic for failed operations:
FlysystemStreamWrapper::register('s3', 'my-s3-wrapper', [
'fallback' => 'local', // Fallback to local disk if S3 fails
]);
How can I help you explore Laravel packages today?