Weave 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 Failover Bundle Laravel Package

webalternatif/flysystem-failover-bundle

Symfony bundle that adds a failover adapter to Flysystem, automatically switching to a backup storage backend when the primary one fails. Configure multiple adapters with priorities so reads and writes keep working during outages or misconfigurations.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a Symfony bundle (Webalternatif FlysystemFailoverBundle) that wires Flysystem's failover storage into a Symfony service container. Its job is narrow: given an ordered list of child filesystems, route writes to the first healthy one and fall back to the next when the active one throws.

For a Laravel developer the practical order of operations is:

  1. Read CHANGELOG.md first — this release (v0.8.0) only ships a changelog link, and the project is still on a 0.x line, so treat every minor as potentially breaking and pin an exact version ("webalternatif/flysystem-failover-bundle": "0.8.0").
  2. Look at the bundle's DI extension and config tree (DependencyInjection/Configuration.php and the service definitions), not the bundle class. That is where the adapter list shape, aliases, and option names live — and it is the part you will re-implement by hand in Laravel.
  3. Then look at the underlying Flysystem failover adapter in vendor/league/flysystem*. That is where the real semantics live (operation routing, exception handling, metadata behaviour). The bundle is mostly glue.
  4. First use case: an upload path that must not break when S3 has a bad afternoon. Primary s3, secondary local (or a second bucket/region), so an ingest job keeps succeeding and you get a reconciliation backlog instead of a 500.

Minimal Laravel wiring (no bundle, just the concept):

// config/filesystems.php
'disks' => [
    'uploads' => [
        'driver' => 'failover',
        'primary' => 's3',
        'fallback' => 'local',
    ],
],

// AppServiceProvider::boot()
Storage::extend('failover', function ($app, $config) {
    return new Filesystem(new FailoverAdapter([
        Storage::disk($config['primary'])->getAdapter(),
        Storage::disk($config['fallback'])->getAdapter(),
    ]));
});

Implementation Patterns

Compose, don't replace. Keep the child disks as normal Laravel disks (s3, local, s3-eu) with their own credentials and env vars in config/filesystems.php. The failover disk should only reference them by name. This keeps Storage::disk('s3') usable for health checks, backfills, and direct downloads.

Ordering is the whole policy. The child list is priority-ordered and the first success wins. Put the fast/authoritative store first; put the durable-but-slow store (local disk, secondary region) last. Document the order in a comment next to the config, because it is the only thing that determines behaviour.

Write-through with read fallback, not replication. Expect writes to land on exactly one child. Plan a reconciliation job: a scheduled command that lists the primary, diffs against the fallback, and copies stragglers up. This is the piece the package does not give you and every production user ends up writing.

Health canary. A small scheduled command that writes and deletes a _health/{date}.txt on each child, logging latency and exceptions per child to a dedicated log channel. Failover decisions made from inside an exception handler are invisible otherwise.

Queue-friendly ingest. Wrap uploads in a job with $tries/backoff and attach the resolved disk name to the job payload. When a failover happens mid-run, the job log tells you which child took the file, which is what you need for the reconciliation diff.

Testing. Use two InMemoryFilesystemAdapter instances and assert the file exists on both/one as intended; simulate an outage with a tiny throwing adapter implementing the same interface. Assert on Storage::disk('uploads')->exists(...) and on the exception being swallowed rather than propagated. Add a test that a read falls through when the first child returns UnableToReadFile.

Integration tips. Storage::disk('uploads')->temporaryUrl() and other Laravel-level conveniences may not survive composition — check per method. Attach Flysystem plugins to the composite, not a child, unless you deliberately want per-child behaviour. For anything metadata-heavy, call through to a specific child disk rather than the failover disk.

Gotchas and Tips

  • It is not a Laravel package. There is no service provider and no auto-discovery. You will almost certainly install only the underlying Flysystem adapter and hand-wire it via Storage::extend, as above. Do not add the bundle to composer.json expecting Laravel to boot it.
  • Pre-1.0 means BC breaks are legal in minors. v0.8.0 shipped with only a changelog link in the release notes; there is no prose summary of what changed. Read the diff, not the tag message, and pin the version.
  • Failover hides outages. A broken credential or expired token stops being a loud 500 and becomes silent degradation. Always log and alert on the caught exception — the failover path is the only signal you get.
  • No automatic backfill. After a failover window, the primary and fallback have diverged. Nothing in the package reconciles them; deleting or overwriting on one side can destroy the only copy of a file.
  • Delete and list are ambiguous on a composite. Verify per-operation behaviour against the adapter source before relying on it. Deleting "the file" may hit only the first child, and listContents may or may not be merged/deduplicated depending on version.
  • Visibility and permission drift. public on local and public on S3 are different ACLs. Pass visibility explicitly on every write rather than relying on per-child defaults.
  • Don't cache the "active child" decision in Redis or a static for long. It defeats the point; recover within one request.
  • Streaming and memory. Large uploads through two adapters are not free. Prefer streamed resources and check whether a failed primary write leaves a partial multipart upload behind on S3.
  • Debugging: enable Flysystem logging (League\Flysystem\Config / a PSR-3 logger) at the adapter boundary, and inspect the child adapter exception, not the wrapper's. php artisan storage:link is irrelevant here — a missing symlink will never be the cause.
  • Extension points: a decorating FilesystemOperator for metrics/alerting, a custom adapter implementing the same interface for canary and diff commands, and Storage::extend for driver registration. Those three cover nearly every adaptation you will need.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums