johnlui/aliyun-oss
Laravel integration for Alibaba Cloud OSS that plugs into the Storage facade, letting you upload, read, delete, and generate URLs for files on Aliyun OSS using familiar filesystem APIs. Simple configuration and Composer install make it easy to add cloud object storage to Laravel apps.
Install the wrapper — it pulls in Aliyun's official aliyuncs/oss-sdk-php transitively, so anything the wrapper does not expose you can still reach through the SDK directly:
composer require johnlui/aliyun-oss
Console prerequisites (do these before writing code): create an OSS bucket, note the region/endpoint (e.g. https://oss-cn-hangzhou.aliyuncs.com), and create a RAM sub-user with an AccessKey scoped to object-level permissions on that bucket only. Never use the root account key.
Laravel wiring. On Laravel 5.5+ auto-discovery registers JohnLui\AliyunOSS\AliyunOSSServiceProvider and the AliyunOSS facade alias; on older versions add both to config/app.php manually. Publish and populate the config:
// config/aliyunoss.php
return [
'endpoint' => env('OSS_ENDPOINT'), // https://oss-cn-hangzhou.aliyuncs.com
'accessKeyId' => env('OSS_KEY_ID'),
'accessKeySecret' => env('OSS_KEY_SECRET'),
'bucket' => env('OSS_BUCKET'),
];
Where to look first. The package is tiny — open src/AliyunOSS.php and read the whole file in five minutes. You will see boot(), setBucket(), uploadFile(), uploadContent(), getFile(), deleteFile(), listFiles(), getUrl(). That is essentially the entire surface area. Then skim the official aliyuncs/oss-sdk-php README for what the wrapper omits (signed URLs, multipart uploads, ACLs, options). The description promises a native Laravel oss filesystem driver and Storage facade compatibility, but verify in your installed version — not every release registers an oss disk driver out of the box.
First use case — an avatar upload. The boot sequence is the atomic unit of this package:
use JohnLui\AliyunOSS\AliyunOSS;
$oss = AliyunOSS::boot(
config('aliyunoss.endpoint'),
config('aliyunoss.accessKeyId'),
config('aliyunoss.accessKeySecret')
)->setBucket(config('aliyunoss.bucket'));
$key = 'avatars/'.auth()->id().'/'.Str::uuid().'.'.$file->extension();
$oss->uploadFile($file->getRealPath(), $key);
return $oss->getUrl($key);
If that works end-to-end, you have proven credentials, endpoint, bucket ACL, and URL shape. Everything else is a variation on it.
Bind one configured client as a singleton. Calling boot() in every controller re-parses config and creates a new SDK client per request. Register once and inject:
// AppServiceProvider::register()
$this->app->singleton(AliyunOSS::class, function () {
return AliyunOSS::boot(
config('aliyunoss.endpoint'),
config('aliyunoss.accessKeyId'),
config('aliyunoss.accessKeySecret')
)->setBucket(config('aliyunoss.bucket'));
});
Wrap it behind an internal storage interface. Define put, get, url, delete, exists and bind AliyunOSS behind it. This is your escape hatch to the official SDK or another adapter when the wrapper lags. Tests bind a fake or local-disk implementation, avoiding network calls in CI.
Persist object keys, not URLs. Store avatars/42/abc.jpg in the database and build the URL at render time with getUrl(). This lets you switch bucket, region, or front with a CDN without a data migration, and keeps rows small.
Deterministic key naming with explicit prefixes. Always pass the destination key as the second argument to uploadFile() — the default is basename($path), so two users uploading avatar.png collide silently. Adopt a scheme like {type}/{owner_id}/{uuid}.{ext} and centralize the builder so every upload path agrees.
Content uploads without a temp file. For generated HTML, JSON exports, thumbnails, or CSV reports, use uploadContent($content, $key) to avoid writing a temp file to disk. Useful in queue jobs and streamed exports.
Two-tier adapter for advanced operations. Keep basic CRUD on the wrapper; when you need multipart upload, signed PUT URLs, STS temporary credentials, server-side encryption, or lifecycle rules, drop to the underlying SDK through the same wrapper class or a dedicated method. Do not fork the wrapper — subclass it or add a sibling service that holds its own SDK client.
Direct-to-OSS signed uploads for large files. Streaming multi-hundred-MB uploads through PHP ties up FPM workers and risks time/memory limits. Generate a signed URL server-side and let the browser PUT directly to OSS, then record the key via callback or a post-upload verify request. The wrapper itself may not expose this — use the SDK.
Cache metadata, not bodies. For existence checks and URL generation you already have enough; only call getFile() in download/sync flows. Reading object bytes into PHP memory is the classic scaling mistake.
Configuration via a single .env block. Keep endpoint, key ID, key secret, bucket, and (if used) CDN base URL all in one place. Derive CDN URLs in your adapter (cdn_url . '/' . $key), not in views.
setBucket() is mandatory after boot(). Calling uploadFile() before setBucket() throws or produces opaque SDK errors. Make the singleton factory always call it — never rely on callers to remember.
Default key collision. uploadFile($localPath) with no key uses basename($path). In a shared bucket this means user A's report.pdf overwrites user B's. Always supply a key.
boot() signature is positional and untyped. Mixing up endpoint, key ID, and secret yields confusing auth errors rather than a clear type error. Wrap the boot in your singleton factory and use named config, never literals in controllers.
Region/endpoint mismatch is the most common first failure. The endpoint must be the bucket's region (oss-cn-hangzhou, oss-ap-southeast-1, etc.) — an internal endpoint (-internal.aliyuncs.com) works only from ECS in the same region and will time out from local dev. Check this before blaming signatures.
Bucket ACL vs. object URL. If the bucket is private (correct default), getUrl() returns a URL that 403s to the browser. You need signed URLs or a public-read bucket + CDN. Decide bucket policy explicitly, and remember that getUrl() on a public bucket returns the object URL, not a signed URL with expiry.
Credential handling. Do not commit .env; do not log the access key; do not use the account root key. Rotate RAM user keys on a schedule. If you need per-user or per-tenant isolation, use STS temporary credentials via the SDK rather than long-lived keys.
Large file handling. Stream on the PHP side and prefer multipart for anything above a few tens of MB; the wrapper may not expose multipart directly. For high concurrency, offload uploads from PHP using signed PUTs.
Content-Type and Content-Disposition. uploadFile() often infers the type from the file extension or defaults to application/octet-stream. That breaks inline display of images and PDFs in browsers. If the wrapper does not let you set headers, patch through the SDK or set metadata via the SDK call directly. Unicode filenames also des
How can I help you explore Laravel packages today?