Weave Code
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

Flysystem Stream Wrapper Laravel Package

twistor/flysystem-stream-wrapper

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require twistor/flysystem-stream-wrapper
    

    Register the service provider in config/app.php:

    'providers' => [
        // ...
        Twistor\FlysystemStreamWrapper\FlysystemStreamWrapperServiceProvider::class,
    ],
    
  2. 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',
        ],
    ],
    
  3. 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');
    

First Use Case: Remote File Access

Replace direct filesystem operations (e.g., Storage::disk('s3')->read()) with stream wrappers for:

  • Legacy code: Use fopen(), file_get_contents(), or file_put_contents() with the wrapper.
  • Third-party libraries: Libraries expecting local files (e.g., League\MimeTypeDetection) now work with remote storage.
  • Temporary file handling: Use tmpfile() with a remote wrapper for processing files without local copies.

Implementation Patterns

Workflow: Seamless Integration

  1. 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');
    }
    
  2. 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);
    });
    
  3. 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);
    

Integration Tips

  • Laravel Filesystem Caching: Disable caching for dynamic wrappers:
    FlysystemStreamWrapper::register('s3', 'my-s3-wrapper', ['cache' => false]);
    
  • Permissions: Set default permissions for created files:
    FlysystemStreamWrapper::register('s3', 'my-s3-wrapper', [
        'default_permissions' => 0644,
    ]);
    
  • Error Handling: Wrap stream operations in try-catch blocks to handle remote errors gracefully:
    try {
        $contents = file_get_contents('my-s3-wrapper://file.txt');
    } catch (\RuntimeException $e) {
        Log::error('Stream wrapper error: ' . $e->getMessage());
    }
    
  • Testing: Mock the wrapper in tests using Laravel’s Storage facade or PHP’s stream_wrapper_register():
    $this->app->singleton('stream', function () {
        stream_wrapper_register('test', new class extends \StreamWrapper {
            // Custom logic
        });
    });
    

Gotchas and Tips

Pitfalls

  1. Stream Wrapper Naming Conflicts

    • Avoid naming conflicts with existing wrappers (e.g., php://, zip://).
    • Fix: Use unique prefixes (e.g., s3-wrapper:// instead of s3://).
  2. Case Sensitivity

    • Some filesystems (e.g., S3) are case-sensitive, while others (e.g., local) are not.
    • Fix: Normalize paths when registering:
      FlysystemStreamWrapper::register('s3', 'my-s3-wrapper', ['normalize_paths' => true]);
      
  3. Memory Limits

    • Large files read via file_get_contents() may exceed PHP’s memory_limit.
    • Fix: Use 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);
      
  4. Concurrent Access Issues

    • Stream wrappers may not handle concurrent writes well (e.g., race conditions).
    • Fix: Use filesystem locks or disable concurrent access:
      FlysystemStreamWrapper::register('s3', 'my-s3-wrapper', ['locking' => false]);
      

Debugging

  1. Enable Verbose Logging Configure the underlying Flysystem adapter for debug logs:

    'filesystems' => [
        'disks' => [
            's3' => [
                'driver' => 's3',
                'debug' => env('APP_DEBUG'), // Enable debug logs
            ],
        ],
    ],
    
  2. Check Stream Wrapper Registration Verify the wrapper is registered:

    $wrappers = stream_get_wrappers();
    var_dump(in_array('my-s3-wrapper', $wrappers)); // Should return true
    
  3. 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');
    }
    

Extension Points

  1. 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(),
        ];
    });
    
  2. 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));
    
  3. 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()
        ),
    ]);
    
  4. Fallback Mechanisms Implement fallback logic for failed operations:

    FlysystemStreamWrapper::register('s3', 'my-s3-wrapper', [
        'fallback' => 'local', // Fallback to local disk if S3 fails
    ]);
    
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.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor