liz/flysystem-qiniu
liz/flysystem-qiniu is a Flysystem adapter for Qiniu cloud storage. It lets Laravel and PHP applications use Qiniu as a filesystem disk, so you can upload, read, and delete files through Flysystem's unified API with minimal configuration.
Before installing, verify the Flysystem major version. This is still the single most important step with this package. The v1.23 release note ("添加php8.0使用" — add PHP 8.0 support) addresses the PHP runtime, not the Flysystem API, so it does not resolve the version question. Check composer.json on the repo:
composer show liz/flysystem-qiniu --all | grep -i flysystem
^1.0 → usable only on Laravel ≤ 8 (Laravel 9+ requires Flysystem 3).^2.0 → usable on Laravel 8/9 era with the Flysystem 2 bridge.composer.json between the previous tag and v1.23 before assuming Laravel 9+ works.Check the PHP constraint, not just the README claim. "PHP 8.0 support" can mean either "the require was widened to ^7.4|^8.0" or "the minimum was raised to ^8.0". The second silently drops Laravel 8 projects still on PHP 7.4. Diff the require block after pulling v1.23.
Install and configure a disk. The adapter is not auto-discovered as a Laravel driver — you register it yourself. In config/filesystems.php:
'qiniu' => [
'driver' => 'qiniu',
'access_key' => env('QINIU_ACCESS_KEY'),
'secret_key' => env('QINIU_SECRET_KEY'),
'bucket' => env('QINIU_BUCKET'),
'domain' => env('QINIU_DOMAIN'), // e.g. https://cdn.example.com
],
Then extend the Storage manager in a service provider (Storage::extend), instantiating the package's adapter with a QiNiu Auth/Storage client from the official qiniu/php-sdk. Read the package's own README or src/ for the exact constructor signature — with a handful of stars and 0 dependents there is no third-party documentation to fall back on.
First use case: uploading user-generated images (avatars, product photos) and serving them through QiNiu's CDN domain. Start with a single Storage::disk('qiniu')->put('avatars/'.$id.'.jpg', $contents) call behind a feature flag, and confirm the returned URL resolves before wiring it into your models.
Standard Flysystem surface. Once registered, the disk behaves like any other. put, putFileAs, get, exists, delete, url, size should all work:
Storage::disk('qiniu')->putFileAs('uploads', $request->file('photo'), $name);
$url = Storage::disk('qiniu')->url('uploads/'.$name);
Keep application code behind the Storage facade rather than calling the adapter directly — that's what lets you swap to s3 or local in tests and staging.
Pin the version you validated. v1.23 is a minor bump, so SemVer says it should be a drop-in, but this package has a thin release history and only one maintainer — pin "liz/flysystem-qiniu": "1.23.*" and treat any future release as something to review rather than something to auto-adopt.
Keep a local staging disk. QiNiu's write path is reasonably fast but its list/delete semantics are prefix-based and paginated. A common pattern is: process the image locally (resize, strip EXIF, validate), then push the finished artifact to QiNiu. This keeps the adapter doing only what it's reliable at — put and get.
Private buckets + temporary URLs. If the bucket is private, route reads through temporaryUrl(). If this package's version predates Flysystem's temporaryUrl support, fall back to the QiNiu SDK directly:
$auth = new \Qiniu\Auth(config('filesystems.disks.qiniu.access_key'), config('filesystems.disks.qiniu.secret_key'));
return $auth->privateDownloadUrl($baseUrl, 3600);
That escape hatch is worth wrapping in a small helper service either way — you'll need it for signed URLs, image processing, and CDN purging regardless of what the adapter exposes.
Image transforms belong in URL builders, not the adapter. QiNiu applies transformations via query string (?imageView2/1/w/300/h/300). Wrap this in an accessor or a cdnUrl($path, $transform) helper rather than expecting the disk to do it.
Direct browser uploads are a separate path. For large files or high upload volume, generate an upload token server-side with the QiNiu SDK and let the client POST to QiNiu's upload endpoint. The adapter only covers server-side streams.
Testing. Storage::fake('qiniu') works at the facade layer and will exercise your application code without hitting the network — this is the main reason to stay on the Storage facade. The PHP 8.0 support in v1.23 also removes a common upgrade blocker for CI images, so you can run the suite on 8.x without shimming.
v1.23 is a pulse, not a heartbeat. One commit adding PHP 8.0 compatibility after ~3 years of silence is a good sign but not a maintenance guarantee. Still read the src/ directory before adopting — it is a few hundred lines of glue at most. overtrue/flysystem-qiniu remains the better-maintained alternative; compare both before committing, and note it already targets newer Flysystem majors if you're on Laravel 10/11.
Update qiniu/php-sdk in lockstep. The PHP 8.0 fix in the adapter is only half the story — older qiniu/php-sdk releases throw deprecation notices or hard errors on PHP 8.1+ (dynamic properties, strlen(null)). Bump the SDK to its latest 7.x and run your upload integration test on the exact PHP version you deploy.
Version drift is the most likely failure mode. A composer require that resolves but produces a class-not-found or an interface mismatch (Flysystem 1's AdapterInterface vs. 3's FilesystemAdapter) usually means the package's lock constraint was satisfied by an unrelated transitive version. Pin Flysystem explicitly in your root composer.json and run composer why league/flysystem when things break.
Directories are a fiction. Object storage has no folders. createDir is typically a no-op and deleteDir requires listing and deleting keys one by one; QiNiu's list API caps at 1000 entries per page, so naive directory wipes silently miss files. Recursive deletes need explicit pagination and should be queued for anything large.
Visibility is bucket-level, not file-level. QiNiu buckets are public or private as a whole. setVisibility() will either be unimplemented or lie to you — do not build ACL logic on top of it. Use separate buckets for public and private content.
Domain, scheme, and CDN behavior. url() output depends entirely on the domain config. Missing https:// produces protocol-relative URLs that break in JSON APIs and mobile clients. Also note the CDN caches aggressively — if you overwrite a key with the same name, consumers may see stale bytes until the CDN purges. Prefer content-hashed keys (avatars/abc123.jpg) over overwrites.
Region mismatch. QiNiu storage regions (z0/z1/z2/na0/as0) affect upload host selection. If uploads fail with opaque 4xx errors while reads work fine, check whether the client's default region matches the bucket.
MIME and metadata. Flysystem may sniff content types locally; mismatches produce files served as application/octet-stream, which browsers download instead of render. Set content type explicitly on upload when it matters, or rely on the CDN's extension-based inference.
Error handling. Wrap disk operations in try/catch around League\Flysystem\FilesystemException (Flysystem 2/3) or the \Exception returned by Flysystem 1's boolean-returning methods. The QiNiu SDK throws its own exception types underneath; log the underlying SDK message since the adapter's wrapper often loses detail.
Extension point. If you fork, the clean seam is a single adapter class implementing the Flysystem interface plus a ServiceProvider calling Storage::extend('qiniu', ...). Keep the QiNiu Auth/Storage client injectable so you can mock it in unit tests without the facade fake.
Config caching. As with any custom driver, php artisan config:cache will freeze your .env credentials — clear it after changing keys, or you'll spend a while debugging 401s that aren't real.
How can I help you explore Laravel packages today?