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 Qiniu Laravel Package

overtrue/flysystem-qiniu

Flysystem adapter for Qiniu Cloud Storage (七牛云), letting Laravel/PHP apps read, write, delete, and list files on Qiniu buckets through Flysystem’s filesystem API. Configure access keys, bucket, and domain to use Qiniu for uploads and storage.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and confirm the Flysystem major version. overtrue/flysystem-qiniu targets Flysystem 3.x. Check composer show overtrue/flysystem-qiniu league/flysystem after install — the adapter's method signatures and exception behavior differ between Flysystem 2 and 3, and mixing them produces confusing BadMethodCallExceptions.
  2. Collect four credentials from the Qiniu console: Access Key, Secret Key, bucket name, and the bucket's bound CDN domain. The domain is not optional for URL generation — a bucket with no bound domain can still store objects but you cannot produce links for them.
  3. Look first at the adapter constructor, not the README. The signature is roughly new QiniuAdapter($accessKey, $secretKey, $bucket, $domain) and that is the whole configuration surface. There is no YAML, no service provider, no published config file — expect to wire everything yourself.
  4. Minimal non-Laravel use:
    $adapter = new QiniuAdapter($ak, $sk, $bucket, $domain);
    $fs = new \League\Flysystem\MyFilesystem($adapter);
    $fs->write('hello.txt', 'hello');
    echo $fs->publicUrl('hello.txt');
    
  5. First use case to validate end-to-end is upload → URL → fetch in a browser. This exercises the three things most likely to be wrong on day one: the key format, the domain format, and whether the bucket is public or private. If the upload succeeds but the URL 401s, you have a private bucket and need temporary URLs, not the adapter.

Implementation Patterns

Laravel disk registration. The package ships no Laravel auto-discovery provider, so register a custom driver in AppServiceProvider::boot():

Storage::extend('qiniu', function ($app, $config) {
    $adapter = new QiniuAdapter(
        $config['key'], $config['secret'], $config['bucket'], $config['domain']
    );
    return new \League\Flysystem\Filesystem($adapter, [
        'visibility' => 'public',
    ]);
});

Then declare the disk in config/filesystems.php and keep credentials in env(). Because there is no config file to publish, this is the canonical integration path and you should treat it as boilerplate rather than a workaround.

Treat it as a file storage adapter, not a CDN manager. The productive pattern is: upload and delete through the adapter, but do URL construction, cache busting, image processing, and invalidation in your own service class. The adapter hands you a raw getUrl(), which returns whatever domain string you passed in plus the key — no signing, no transformation.

Use streaming for user uploads. writeStream() avoids loading the whole file into memory and composes cleanly with Laravel's UploadedFile:

$stream = fopen($file->getRealPath(), 'r');
Storage::disk('qiniu')->writeStream($path, $stream);

Prefer this over put()/write() anywhere a user controls file size. Qiniu's SDK has its own resumable/chunked uploader, but the adapter does not expose it — for very large objects you should call qiniu/php-sdk directly and skip the adapter.

Separate public and private disks. Qiniu visibility is a bucket-level property, not a per-object one. Model this as two disks (qiniu-public, qiniu-private) pointing at two buckets, and route user-facing assets to the public one. Trying to toggle visibility per file through Flysystem will appear to work and silently not do what you expect.

Directory semantics: use prefixes deliberately. Qiniu keys are flat strings; "directories" are just shared prefixes. That means list operations, deleteDirectory, and prefix-scoped queries are all simulated. A useful pattern is to encode tenant or user scope as the first path segment (avatars/{user_id}/{uuid}.jpg) and list with listContents("avatars/{$id}"). Do not rely on empty directories existing.

Testing. Never let the test suite reach Qiniu — quota, latency, and flakiness will punish you. Swap the disk for Flysystem's InMemoryFilesystemAdapter in the test container, and keep a single opt-in integration test (env-gated) that exercises the real adapter for upload/URL/delete. Adapter-specific behavior — URL format, prefix listing — belongs in that integration test, because the in-memory adapter will not reproduce it.

Gotchas and Tips

  • The domain format is inconsistent and version-dependent. Some releases expect https://cdn.example.com, others cdn.example.com. If your URLs come out as https://cdn.example.com//path or get double-schemed, this is why. Normalize once in your own config layer (trim trailing slash, strip scheme) and pass the exact form your installed version wants — verify by reading the adapter source, it is short.
  • getUrl() on a private bucket returns a link that 403s. There is no error; you just get a URL that does not work. Use getTemporaryUrl($path, $expiry) for private buckets, and be aware Qiniu signs with an expiry timestamp, so caching signed URLs beyond their lifetime defeats the purpose.
  • Visibility is largely a no-op. setVisibility/getVisibility cannot change how Qiniu serves an object. Don't build access control on top of them; build it on bucket choice plus signed URLs.
  • Exceptions are not normalized. The adapter tends to surface Qiniu SDK errors rather than Flysystem's UnableToReadFile / UnableToWriteFile family. If your application catches Flysystem exceptions to render friendly errors, wrap adapter calls in a small service that translates Qiniu\Exception (or the SDK's error arrays) into the Flysystem types.
  • fileExists() costs an API call. Every exists() / stat() is a round trip to Qiniu with non-trivial latency from outside China. Avoid calling it in loops and in request-path hot code; prefer try { readStream() } catch (...).
  • Listing is paginated and rate-limited. Qiniu's list API caps results per call (typically 1000). A recursive listContents(..., true) over a large bucket can be slow and can trip quota. Use narrow prefixes and iterate lazily rather than collecting everything into an array.
  • Region auto-detection adds latency. The Qiniu SDK resolves a bucket's region via an API call on first use. For a long-lived process this is fine; for a cold-start request it can add hundreds of milliseconds. Where the SDK allows an explicit region/zone configuration, set it.
  • Key normalization bites. Leading slashes, backslashes, and .. segments produce keys that look different between your local disk and Qiniu. Normalize keys in one place before handing them to the adapter, and never store the raw user-supplied filename as the key.
  • Deletes are eventually consistent at the CDN edge. Deleting an object does not purge the CDN. If you need immediate disappearance (e.g. GDPR or user avatar replacement), pair deletes with Qiniu's CDN refresh API, which this package does not wrap.
  • Pin the Qiniu SDK. The adapter defers to qiniu/php-sdk; a minor bump there has historically changed exception types and error shapes. Constrain it in composer.json rather than inheriting whatever ^7 resolves to.
  • Maintenance signal. 228 stars and a recent (2025-09) tag but an "unknown" repository is a warning sign: releases exist, but community scrutiny is thin. Read the adapter source on upgrade — it is a few hundred lines — rather than assuming semantic versioning protects you.
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.
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus