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

Storage Bundle Laravel Package

1tomany/storage-bundle

Symfony bundle for uploading files to remote storage (Amazon S3/R2, GCS, Azure) with a simple client-based config. Includes an Amazon S3-compatible client plus a mock client for fast, offline testing, and optional custom URLs for CDN/public buckets.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup for Laravel
1. **Install the package** via Composer:
   ```bash
   composer require 1tomany/storage-bundle

For S3/R2 support:

composer require aws/aws-sdk-php-symfony
  1. Configure the bundle in config/packages/onetomany_storage.yaml:

    onetomany_storage:
        client: "amazon"  # or "mock" for testing
        bucket: "your-bucket-name"
        custom_url: "https://your-cdn.com"  # Optional CDN override
    
  2. Set AWS credentials in .env (use Symfony secrets for production):

    AWS_KEY=%env(AWS_KEY)%  # Encrypted via `php bin/console secrets:generate`
    AWS_SECRET=%env(AWS_SECRET)%
    AWS_ENDPOINT="https://your-account.r2.cloudflarestorage.com"  # For R2
    AWS_MERGE_CONFIG=true
    
  3. First use case: Upload a file in a Laravel controller/service:

    use OneToMany\StorageBundle\Contract\Action\UploadActionInterface;
    use OneToMany\StorageBundle\Request\UploadRequest;
    
    class FileUploader {
        public function __construct(private UploadActionInterface $uploadAction) {}
    
        public function upload(string $localPath, string $storageKey) {
            $response = $this->uploadAction->act(
                new UploadRequest($localPath, 'image/png', $storageKey)
            );
            return $response->getUrl();  // Returns CDN URL if configured
        }
    }
    

Implementation Patterns

Core Workflows

  1. Dependency Injection:

    • Prefer action interfaces (UploadActionInterface, DownloadActionInterface) over the base ClientInterface for clarity and testability.
    • Example service:
      final readonly class FileService {
          public function __construct(
              private UploadActionInterface $upload,
              private DownloadActionInterface $download
          ) {}
      
          public function handleFileUpload(UploadDto $dto) {
              $response = $this->upload->act(
                  new UploadRequest($dto->path, $dto->mimeType, $dto->storageKey)
              );
              return new FileUploaded($response->getUrl());
          }
      }
      
  2. Request/Response Pattern:

    • Use immutable request objects (UploadRequest, DownloadRequest) to encapsulate parameters.
    • Validate responses (e.g., check UploadResponse::isSuccess()) before processing.
  3. Testing:

    • Mock actions in tests (e.g., UploadActionInterface mock) instead of the full client.
    • Use the mock client in onetomany_storage.yaml for integration tests:
      onetomany_storage:
          client: "mock"
          bucket: "test-bucket"
      

Integration Tips

  • Laravel Filesystem: Bridge with Laravel’s Storage facade by extending the bundle’s client:

    use Illuminate\Support\Facades\Storage;
    use OneToMany\StorageBundle\Contract\Client\ClientInterface;
    
    class LaravelStorageAdapter implements ClientInterface {
        public function act(RequestInterface $request) {
            $contents = file_get_contents($request->getPath());
            Storage::disk('s3')->put($request->getKey(), $contents);
            return new UploadResponse(true, Storage::disk('s3')->url($request->getKey()));
        }
    }
    

    Register it in config/packages/onetomany_storage.yaml:

    onetomany_storage:
        client: "laravel_adapter"
    
  • Validation: Use Symfony’s Validator to validate UploadRequest/DownloadRequest before processing:

    use Symfony\Component\Validator\Validator\ValidatorInterface;
    
    $errors = $validator->validate($request);
    if (count($errors) > 0) {
        throw new \InvalidArgumentException((string) $errors);
    }
    
  • Async Uploads: Dispatch a queue job for large files:

    use OneToMany\StorageBundle\Contract\Action\UploadActionInterface;
    use Illuminate\Bus\Queueable;
    use Illuminate\Queue\SerializesModels;
    
    class UploadFileJob implements ShouldQueue {
        use Queueable, SerializesModels;
    
        public function __construct(
            private UploadActionInterface $uploadAction,
            private UploadRequest $request
        ) {}
    
        public function handle() {
            $this->uploadAction->act($this->request);
        }
    }
    

Gotchas and Tips

Pitfalls

  1. AWS SDK Configuration:

    • Issue: Forgetting AWS_MERGE_CONFIG=true causes Symfony to ignore .env overrides.
    • Fix: Add it to .env and clear cache:
      php bin/console cache:clear
      
  2. Custom URL Overrides:

    • Issue: custom_url in config is ignored if the storage service returns a public URL (e.g., S3 bucket with static website hosting).
    • Fix: Set custom_url to null or omit it to use the service’s canonical URL.
  3. Mock Client Limitations:

    • Issue: The mock client doesn’t simulate network delays or partial failures.
    • Fix: Extend the mock client for custom behavior:
      use OneToMany\StorageBundle\Client\MockClient;
      
      class CustomMockClient extends MockClient {
          public function act(RequestInterface $request) {
              if ($request->getKey() === 'fail-test') {
                  throw new \RuntimeException('Simulated failure');
              }
              return parent::act($request);
          }
      }
      
      Register it in services.yaml:
      services:
          OneToMany\StorageBundle\Contract\Client\ClientInterface:
              class: App\Client\CustomMockClient
              tags: ['onetomany.storage.client', { key: 'mock' }]
      
  4. File Permissions:

    • Issue: Uploaded files may not inherit correct permissions (e.g., S3 ACLs).
    • Fix: Configure ACLs in the UploadRequest:
      $request = new UploadRequest($path, $mimeType, $key);
      $request->setAcl('public-read');  // For S3
      

Debugging Tips

  • Enable AWS SDK Debugging: Add to config/packages/aws.yaml:

    aws:
        debug: true
    

    Check logs for SDK requests/responses.

  • Validate Requests: Use Symfony’s Validator to catch malformed requests early:

    $validator = $container->get(ValidatorInterface::class);
    $errors = $validator->validate($request);
    
  • Check Response Codes: Inspect UploadResponse/DownloadResponse for HTTP status codes:

    if (!$response->isSuccess()) {
        throw new \RuntimeException(
            'Upload failed: ' . $response->getStatusCode()
        );
    }
    

Extension Points

  1. Custom Clients: Implement ClientInterface and tag it for DI:

    use OneToMany\StorageBundle\Contract\Client\ClientInterface;
    
    class BackblazeClient implements ClientInterface {
        public function act(RequestInterface $request) {
            // Custom Backblaze logic
        }
    }
    

    Register in services.yaml:

    services:
        App\Client\BackblazeClient:
            tags: ['onetomany.storage.client', { key: 'backblaze' }]
    
  2. Middleware: Add pre/post-processing to actions by decorating the interface:

    use OneToMany\StorageBundle\Contract\Action\UploadActionInterface;
    
    class LoggingUploadAction implements UploadActionInterface {
        public function __construct(private UploadActionInterface $decorated) {}
    
        public function act(RequestInterface $request) {
            \Log::info('Uploading file', ['key' => $request->getKey()]);
            $response = $this->decorated->act($request);
            \Log::info('Upload complete', ['url' => $response->getUrl()]);
            return $response;
        }
    }
    

    Register in services.yaml:

    services:
        OneToMany\StorageBundle\Contract\Action\UploadActionInterface:
            class: App\Action\LoggingUploadAction
            decorates: 'onetomany_storage.upload_action'
    
  3. Event Dispatching: Trigger events before/after actions using Symfony’s EventDispatcher:

    use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
    
    class UploadEventDispatcher {
        public function __construct(
            private EventDispatcherInterface $dispatcher,
            private UploadActionInterface $uploadAction
        ) {}
    
        public function uploadWithEvents(RequestInterface $request) {
            $this->dispatcher->dispatch(new PreUploadEvent($request));
            $response = $this->uploadAction->act($request);
            $this->dispatcher->dispatch(new PostUploadEvent($response));
            return $response;
        }
    
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.
nasirkhan/laravel-sharekit
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony