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

zing/flysystem-oss

Flysystem adapter for Alibaba Cloud OSS (Aliyun) that lets Laravel and PHP applications read, write, and manage files on OSS through a unified filesystem API. Suitable for cloud storage, uploads, backups, and CDN-backed assets.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Flysystem 3 adapter, not a full framework integration. It exposes Alibaba Cloud OSS through League\Flysystem\Filesystem, so everything you already know about read/write/listContents/delete applies — you're just swapping the storage backend. For Laravel you'll usually pair it with the author's companion zing/laravel-flysystem-oss package rather than wiring the adapter by hand.

Minimal path:

  1. Provision the bucket in the Aliyun console and create a RAM user with oss:PutObject, oss:GetObject, oss:ListObjects, oss:DeleteObject (avoid the root account AccessKey).
  2. Install:
    composer require zing/flysystem-oss
    # Laravel only:
    composer require zing/laravel-flysystem-oss
    
  3. Wire up the adapter directly:
    use League\Flysystem\Filesystem;
    use Zing\Flysystem\Oss\OssAdapter;
    
    $filesystem = new Filesystem(new OssAdapter([
        'access_key_id'     => env('OSS_ACCESS_KEY_ID'),
        'access_key_secret' => env('OSS_ACCESS_KEY_SECRET'),
        'endpoint'          => 'oss-cn-hangzhou.aliyuncs.com',
        'bucket'            => 'my-bucket',
        'is_cname'          => false,
        'prefix'            => 'app/uploads',
    ]));
    
  4. Or in config/filesystems.php with the Laravel bridge:
    'oss' => [
        'driver'            => 'oss',
        'access_key_id'     => env('OSS_ACCESS_KEY_ID'),
        'access_key_secret' => env('OSS_ACCESS_KEY_SECRET'),
        'endpoint'          => env('OSS_ENDPOINT'),
        'bucket'            => env('OSS_BUCKET'),
        'is_cname'          => env('OSS_IS_CNAME', false),
        'prefix'            => env('OSS_PREFIX', ''),
    ],
    

First use case: upload a user avatar and return a URL.

Storage::disk('oss')->put('avatars/'.$user->id.'.jpg', $fileContents);
$url = Storage::disk('oss')->url('avatars/'.$user->id.'.jpg'); // only works if bucket/object is public-read

If the bucket is private (the sane default), use temporaryUrl() instead — see the Gotchas section, this is the #1 stalling point.

Implementation Patterns

Environment isolation via prefix. OSS buckets are cheap; prefixes are cheaper. Set prefix to staging/ and production/ in different .env files and the same bucket serves both. All paths you pass to Storage are relative to the prefix.

Private-by-default with signed URLs for delivery. Keep the bucket ACL private and hand out temporaryUrl links for downloads. This is the pattern that scales to a real product:

$url = Storage::disk('oss')->temporaryUrl(
    "invoices/{$invoice->id}.pdf",
    now()->addMinutes(30)
);

Stream large files, don't get() them. For anything over a few MB, use readStream/writeStream so memory stays flat. The underlying OSS SDK's uploadFile does multipart automatically when you pass a local path, so prefer that over loading into a string when the source is on disk.

Treat the disk as a repository, not a filesystem. Listing with listContents('', true) against a flat bucket with millions of keys is slow and paginated 1000-at-a-time under the hood. Keep an index table (path, size, checksum, mime, uploaded_at) in your DB and query that instead. Use Flysystem only for the actual byte transfer.

Queue long uploads. put() calls block on the network; a 5 MB image over a slow uplink will stall the request thread. Push the upload into a queued job and store the returned path.

Use Storage::fake() in tests. Laravel's fake swaps the disk to a local in-memory one, so you get fast unit tests without touching OSS. For the adapter itself, swap in League\Flysystem\InMemory\InMemoryFilesystemAdapter — no network, deterministic.

Visibility per write when you must mix. put($path, $contents, ['visibility' => 'public']) overrides the default for one object. Useful for public thumbnails sharing a bucket with private documents, but it's easy to lose track — audit periodically.

Move/copy are server-side but stay in-bucket. move() and copy() map to OSS's server-side operations and avoid a round trip through your app, but only within the same bucket. Cross-bucket "moves" require download-then-upload.

Wrap the adapter in a small service. Inject League\Flysystem\Filesystem (or Storage disk) into a MediaStorage class that owns path-building, MIME validation, and URL signing. Keeps the rest of the app agnostic and makes swapping storage providers a one-file change if this package ever disappears.

Gotchas and Tips

Temporary URLs are the classic trap. Storage::url() builds https://{bucket}.{endpoint}/{key} and returns a link regardless of whether the object is public. If the bucket is private (default), that URL 403s and you'll waste an hour blaming the adapter. Either set visibility => 'public-read' per object or use temporaryUrl().

is_cname trips up custom domains. If you've mapped a custom domain to the bucket, set is_cname => true. If you're using the default bucket.oss-region.aliyuncs.com endpoint, leave it false. Getting this wrong yields signature errors, not helpful messages.

Endpoint must match the bucket's region. A bucket in oss-cn-beijing hit via oss-cn-hangzhou.aliyuncs.com returns AccessDenied or a redirect depending on config. Store the endpoint per environment and never hardcode it.

SignatureDoesNotMatch is almost always clock skew or a typo'd secret. Enable NTP on the server, re-check the secret (they get truncated when copy-pasted), and verify endpoint doesn't include https:// — the SDK expects a hostname.

No real directories. createDirectory() creates a zero-byte object with a trailing slash. listContents returns those as directory entries. Empty "directories" won't exist unless you create them explicitly, and deleting the last object in a folder doesn't delete the folder marker.

Pagination is invisible but real. OSS returns 1000 keys per ListObjectsV2 call. Flysystem wraps this with a continuation token, so listContents('', true) "just works" — but it's making many HTTP calls on a large prefix. Scope with listContents('prefix/', true).

MIME detection is unreliable. Flysystem guesses from the extension; the OSS SDK guesses from the stream. For .webp, office docs, and extensionless keys, pass ['mimetype' => 'image/webp'] explicitly. Wrong MIME means browsers download instead of render.

Flysystem 3 only. This adapter targets league/flysystem:^3.0; it will not work with the 1.x/2.x APIs (getAdapter(), readAndDelete(), etc.). Don't copy snippets from older Laravel docs.

Error mapping is coarse. The adapter translates SDK exceptions into Flysystem's UnableToReadFile / UnableToWriteFile / UnableToListContents. You'll lose OSS error codes (e.g. NoSuchBucket vs AccessDenied) in the exception message. For real diagnostics, drop to the SDK client directly — the adapter usually exposes it via a getClient() method, or you can instantiate OSS\OssClient yourself with the same credentials just for debugging.

Response headers are not exposed. Things like Content-Disposition: attachment or custom Cache-Control on a download need SDK-level $ossClient->signUrl() with options; the adapter's temporaryUrl signature doesn't pass them through. If you need forced downloads or CDN cache headers, sign URLs via the SDK.

Copy/move within prefix only, not across prefixes on the same bucket — actually the prefix is transparent to Flysystem paths, so move('a', 'b') works anywhere in the bucket, but the source and destination must be in the same bucket. Plan your bucket layout before you have data.

Extension points are thin. There's no driver-manager or custom-option hook beyond config. If you need multipart tuning, custom headers, or storage-class (IA/Archive) selection, fork the adapter or wrap it: extend OssAdapter, override write(), and call the SDK client directly for the special cases.

Adoption risk is real. Three stars, zero dependents, a single maintainer, and a January 2025 release. Pin the exact version in composer.json, subscribe to the repo's releases, and keep your MediaStorage abstraction thin enough that dropping in aliyuncs/oss-sdk-php (or a competitor adapter like iidestiny/flysystem-oss) is a day's work, not a sprint.

Config values with empty strings break signing. prefix => '' is fine; prefix => '/' is not. access_key_id => null produces a confusing InvalidArgument rather than a clear "missing credentials" error — validate config at boot with a FilesystemManager extension or a service provider's register/boot.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib