sociallydev/spaces-api
Laravel integration for DigitalOcean Spaces object storage. Provides a simple, configuration-driven API for uploading, retrieving, and managing files in S3-compatible Spaces buckets, making cloud file storage easy in PHP applications.
composer require sociallydev/spaces-api. Add credentials to config/services.php (or a dedicated config/spaces.php): key, secret, region (nyc3, ams3, sgp1, fra1, …), bucket, and the endpoint (https://{region}.digitaloceanspaces.com). Keep them in .env — never inline them.Content-Type/ACL, and whether it returns URLs or raw responses.$this->app->singleton(Spaces::class, fn () => new Spaces(
config('services.spaces.key'),
config('services.spaces.secret'),
config('services.spaces.region'),
));
$request->file('avatar'), generate a collision-proof key (avatars/{uuid}.{ext}), upload the file contents, and store the returned public URL on the user record. This exercises the three things you'll do every day — write an object, derive a URL, delete the old one.StorageService (or a Filesystem-like interface) that takes a key + contents + visibility and returns a URL. When you eventually migrate to the native Flysystem S3 adapter — or to R2/B2 — you change one class, not forty call sites.public-read around ad hoc.fopen($path, 'r') instead of file_get_contents(). Loading a 200 MB export into memory is the most common way this package shows up in an OOM trace.Storage::put() in the request cycle is fine for a 50 KB avatar; for anything larger, dispatch a job, store the key immediately, and let the UI poll or sign a temporary URL. Keeps p95 request latency flat.https://{bucket}.{region}.digitaloceanspaces.com/{key} yourself (and optionally prefix a CDN hostname). This decouples your templates from the package's URL method and lets you flip to the CDN edge without touching the wrapper.{type}/{tenant-or-user}/{ulid}.{ext} gives you prefix listing, easy bulk deletes per tenant, and no collisions. Never trust the client-supplied filename.delete per key for one-offs; list-by-prefix + batch delete for "delete this user's data" GDPR flows. Wrap that in a transaction-adjacent service, and log the keys before deleting.StorageService interface in the test container. Don't hit Spaces from the test suite; if you must, gate it behind a @group integration test with real credentials in CI secrets.nyc3 credentials against ams3 endpoints fail with opaque 403s, not a clear "wrong region." Validate region against the endpoint hostname at boot.Content-Type is not inferred for you. If you upload image bytes without it, browsers will download the file instead of rendering it. Set it explicitly from $file->getMimeType() or mime_content_type().public-read is the reliable combination; don't assume every canned ACL or bucket policy works. Test the actual object URL in an incognito window after every upload.Cache-Control on upload, versioned keys (avatar/{uuid}) are your only cache-busting lever. Prefer immutable, UUID-named keys over overwriting the same key.league/flysystem-aws-s3-v3 with a custom endpoint gives you the same bucket with multipart uploads, presigned URLs, and Laravel's Storage facade — worth knowing before you build deep.config:cache. Read all credentials through config(), never env() at runtime, or you'll break after php artisan config:cache in production.Content-Disposition, Cache-Control, and x-amz-acl. If the package doesn't expose them, you'll be forking or dropping to the S3 client — decide early which side of that line you're on.How can I help you explore Laravel packages today?