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

Storage Laravel Package

azure-oss/storage

Azure Blob Storage driver for Laravel. Register Azure as a native filesystem disk and use the familiar Storage facade to upload, retrieve, list, and delete blobs, generate URLs, and manage containers—no custom SDK code required.

View on GitHub
Deep Wiki
Context7

Getting Started

azure-oss/storage is the meta package for the community PHP SDK for Azure Storage. It is a subtree split of the AzureOss\Storage\Meta component in php-oss-for-azure/azure-storage-php, so requiring it pulls in the Blob, Queue and Common components under one version constraint.

Minimal steps

  1. composer require azure-oss/storage
  2. You need a PSR-18 HTTP client and PSR-17 request/response factories — Guzzle 7 and guzzlehttp/psr7 are the path of least resistance; Symfony HttpClient works too. The package is a thin, PSR-friendly layer, not a Guzzle-specific one.
  3. Grab the connection string from the Azure portal (Storage account → Access keys → Connection string) and put it in .env as AZURE_STORAGE_CONNECTION_STRING.
  4. Bind it once in a service provider rather than constructing clients per request:
use AzureOss\Storage\Blob\BlobServiceClient;

$this->app->singleton(BlobServiceClient::class, fn () => BlobServiceClient::fromConnectionString(
    config('services.azure.connection_string')
));

Where to look first

  • AzureOss\Storage\Blob\BlobServiceClient — entry point for everything blob-related.
  • AzureOss\Storage\Queue\QueueServiceClient — entry point for queues.
  • AzureOss\Storage\Common\* — StorageSharedKeyCredential, SAS credentials, StorageException, API version handling. This is where you go when auth or error handling misbehaves.
  • The monorepo README/examples, not the split repo — the split is read-only and its README is a stub.

First use case that pays for itself: moving user uploads out of local disk into a container with a predictable path convention.

$container = $blobServiceClient->getContainerClient('uploads');
$container->createIfNotExists();

$blob = $container->getBlobClient("users/{$userId}/{$filename}");
$blob->upload(fopen($request->file('avatar')->getRealPath(), 'rb'), overwrite: true);

// later
$stream = $blob->downloadStreaming()->getContentAsString();

Implementation Patterns

Wrap, don't sprinkle. Keep container names, path conventions, content types and retry policy inside one AzureStorageService or repository class. Controllers and jobs should ask for storeAvatar(User $u, UploadedFile $f), never build users/{$id}/... strings.

Choose the upload method by payload shape. Strings for small text/JSON, streams or file paths for anything user-generated. Pass an options object for content type, metadata, overwrite and access tier in one place — this is where you set Content-Type and Content-Disposition so browsers preview or download correctly instead of getting application/octet-stream.

Stream downloads for pass-through. downloadStreaming() gives you a PSR-7 stream you can hand to response()->streamDownload() without buffering the whole blob in PHP memory. Reserve downloadToFile() for things you actually need on disk (PDF processing, image resizing), and avoid getContentAsString() on anything you haven't size-checked.

SAS for browser traffic, never the account key. Generate short-lived, narrowly scoped SAS URIs with the builder and return them to the frontend for direct upload/download. The pattern that works well: the browser asks your API for a URI → API checks authorization → API returns a blob-scoped SAS with write/create and a 5–15 minute expiry → browser PUTs straight to Azure. Your PHP workers never touch the bytes.

$sas = $blobClient->generateSasUri(
    BlobSasBuilder::new()
        ->setContainerName('uploads')
        ->setBlobName($path)
        ->setPermissions(BlobSasPermissions::parse('rcw'))
        ->setExpiresOn(new DateTimeImmutable('+10 minutes'))
);

Keep the account-level SAS generator for admin tooling only; it is effectively a password.

Listing with hierarchy. Azure has no real folders, so emulate them with a prefix plus / delimiter and pass the continuation token through to your UI. If your listing method returns a paged result, treat pagination as mandatory, not optional — a container with 100k blobs will happily time out your admin page otherwise.

Queues as the async backbone. createIfNotExists() on deploy, then send a message per unit of work (sendMessage), receive in batches with a visibility timeout, and delete only after the handler succeeds. Track dequeue count for poison messages; Azure redelivers indefinitely otherwise.

Laravel integration — two tiers. For ordinary file operations (put, get, exists, temporaryUrl, delete) register the Flysystem adapter (azure-oss/flysystem-azure-blob-storage) as a disk in config/filesystems.php and use Storage::disk('azure') like any other disk. Drop to the native BlobServiceClient only for what Flysystem doesn't model well: SAS with precise permissions, access tiers, metadata, block-level control, queue operations. Document which tier each feature team should use, or you'll get both idioms in the same controller.

Testing. Put the native client behind your own small interface so feature tests can bind a fake. Keep a separate integration suite that runs against Azurite (Docker) in CI and a dedicated dev storage account nightly — emulator fidelity is good for blobs but not perfect for SAS and tiering.

Gotchas and Tips

  • Pin all azure-oss/* packages to the same version. Because the components are subtree splits of one monorepo, mixing azure-oss/storage with a directly required azure-oss/storage-blob at a different release is the fastest route to a confusing class-signature mismatch. Require the meta package only, then composer update azure-oss/* --with-dependencies.
  • Errors are exceptions, not return values. Catch StorageException and branch on the HTTP status: 404 (missing container/blob), 409 BlobAlreadyExists (forgot overwrite), 412 PreconditionFailed (ETag/If-Match race — the correct signal for optimistic concurrency), 403 (bad key, expired SAS, clock skew). Never match on message text.
  • Overwrite is opt-in. An upload to an existing blob throws by default; that's a feature, but it surprises people migrating from file_put_contents-style APIs.
  • Streams are consumed. Rewind (rewind($stream)) before any retry, and never reuse one stream handle for two uploads. If you're wrapping calls in a retry middleware, make sure the stream is rewindable (fopen on a file is; a PSR-7 stream from a request body may not be).
  • SAS expiry is UTC and unforgiving. Sign with DateTimeImmutable in UTC, leave a couple of minutes of skew, and remember that URL-encoding a SAS into a query string can silently break it. Prefer generateSasUri() over hand-assembling.
  • Metadata keys must be valid C# identifiers (letters, digits, underscore, not starting with a digit) and are stored lowercased. Don't put JSON blobs in metadata; use a sidecar blob or a database row.
  • Azurite needs a different connection string. UseDevelopmentStorage=true plus an explicit http://127.0.0.1:10000 endpoint and path-style URLs. Keep a separate .env.testing — do not try to make one connection string do both.
  • No built-in logging or metrics. You get observability by injecting a PSR-18 client wrapped in a Guzzle logging middleware or a Symfony decorator; that's also the extension point for proxies, timeouts, tracing headers and retry/backoff. Retry only idempotent operations (GET, HEAD, DELETE, PUT with If-Match).
  • Queue payloads are small and base64-encoded. 64 KB is the message ceiling; binary payloads need explicit encoding. Keep messages as references ({type, id}) rather than fat payloads.
  • Maturity risk is real. 38 stars and zero dependents means this is a small community project, not a Microsoft-supported SDK. Mitigate with the wrapper class above, pin versions in composer.json, and read the changelog before minor bumps. The upside is that the surface is thin PSR-18/PSR-7 code, so replacing it with microsoft/azure-storage-blob later is a contained refactor rather than a rewrite.
  • Contributions and bug reports go to the monorepo (php-oss-for-azure/azure-storage-php), not the read-only split. File issues with the component name in the title and expect to supply a minimal Azurite repro.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings