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

Azure Storage File Laravel Package

microsoft/azure-storage-file

Official Microsoft Azure Storage File SDK for PHP, offering a client library to create and manage Azure file shares, directories, and files, upload and download content, set metadata, and authenticate against Azure Storage accounts.

View on GitHub
Deep Wiki
Context7

Getting Started

microsoft/azure-storage-file is the Azure Files (SMB file share) data-plane SDK — not Blob, not a Laravel filesystem driver. It is a plain PHP SDK that wraps the REST API for shares, directories and files. Treat it as an infrastructure client you wrap yourself, not as something Laravel wires up for you.

1. Install and pin

composer require microsoft/azure-storage-file:^1.5

The repo is release-only and last shipped in Oct 2021; issues belong in microsoft/azure-storage-php. Pin the minor version and add it to your dependency review list. It pulls in microsoft/azure-storage-common and Guzzle — check composer why guzzlehttp/guzzle after install if you are on a recent Laravel.

2. Configure

// config/services.php
'azure_files' => [
    'connection_string' => env('AZURE_STORAGE_CONNECTION_STRING'),
    'share'             => env('AZURE_FILES_SHARE', 'appdata'),
],

Use the full connection string (DefaultEndpointsProtocol=...;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net) or, better for production, a SAS-bearing connection string so the app never holds the account key. Never commit it; config:cache means you must read from env() only inside config files.

3. Bind the client once

// app/Providers/AzureFilesServiceProvider.php
use MicrosoftAzure\Storage\Common\ServicesBuilder;
use MicrosoftAzure\Storage\File\FileRestProxy;

public function register(): void
{
    $this->app->singleton(FileRestProxy::class, fn () => ServicesBuilder::getInstance()
        ->createFileService(config('services.azure_files.connection_string')));
}

Constructing the proxy per request is cheap-ish but not free; the singleton also lets Guzzle reuse connections across a queue worker's jobs.

4. First use case

The canonical first job for a Laravel dev is "write a generated artifact somewhere all app servers can read it" — e.g. a nightly CSV export that a separate reporting box or a legacy Windows service picks up over SMB:

$files->createShare($share);                                   // ignore ShareAlreadyExists
$files->createDirectory($share, 'exports');
$files->createFileFromContent($share, 'exports/report.csv', $csv, $options);

Look first at FileRestProxy (the whole API surface), then MicrosoftAzure\Storage\File\Models\* for options/result objects, then FileSharedAccessSignatureHelper if you need to hand URLs to browsers.

Implementation Patterns

Wrap it in a repository, not in controllers. A thin AzureFileRepository with put(), get(), exists(), delete(), list() keeps the SDK's exception types and options objects out of your domain code and gives you one place to add logging and retries. Inject FileRestProxy into it.

Always ensure the directory chain exists. Azure Files has no implicit parent creation. createFileFromContent($share, 'a/b/c.txt', ...) fails with ParentNotFound if a/b is missing. Cache created paths in Redis per share, or create them idempotently and swallow ResourceAlreadyExists:

private function ensureDirectory(string $path): void
{
    try {
        $this->files->createDirectory($this->share, $path);
    } catch (ServiceException $e) {
        if ($e->getCode() !== 'ResourceAlreadyExists') {
            throw $e;
        }
    }
}

Chunk large uploads yourself. A single Put Range is capped at 4 MiB. createFileFromContent is fine for small payloads; for anything user-uploaded assume it can be big:

$size  = filesize($local);
$chunk = 4 * 1024 * 1024;
$files->createFile($share, $remote, $size);
$fh = fopen($local, 'rb');
for ($offset = 0; $offset < $size; $offset += $chunk) {
    $data = fread($fh, $chunk);
    if ($data === '' || $data === false) break;
    $files->putRange($share, $remote, $offset, $offset + strlen($data) - 1, $data);
}
fclose($fh);

putRange's end offset is inclusive — off-by-one here corrupts data silently in the middle of large files, which is brutal to debug.

Stream downloads, don't buffer them. getFile() returns a result whose getContentStream() is a PSR-7 stream. Use stream_copy_to_stream() into php://output or a temp file for anything over a few MB; getContent() will happily eat your memory limit on a 2 GB file.

Do the work in a queued job with a timeout you set yourself. Azure round-trips from PHP are 50–300 ms each; a loop over 500 files inside a web request will time out. Give the job $timeout, use tries = 3 with backoff, and make operations idempotent (overwrite by default — Azure Files createFile replaces existing files unless you pass an If-None-Match: * condition).

Share SAS URLs for browser downloads instead of proxying. Generate a read-only, short-lived token with FileSharedAccessSignatureHelper and redirect the user to https://{account}.file.core.windows.net/{share}/{path}?{sas}. This keeps PHP workers out of the download path. Scope the token to the single file, not the share, and keep expiry to minutes.

Batch writes and reuse connections. In a queue worker, one FileRestProxy instance handles thousands of operations; the SDK's default retry middleware retries transient 5xx/timeouts, which is why idempotent operations matter more than clever error handling.

Consider mounting the share instead of using the SDK. If your workload is "the app reads and writes ordinary files on a share", mount it (mount -t cifs //account.file.core.windows.net/share /mnt/azure -o ...) on the VM/container host and point a normal Laravel local disk at /mnt/azure. You get Storage::disk(), file_get_contents, rename() and all the standard tooling, and you delete the SDK dependency entirely. The SDK is the right choice only when you cannot mount (App Service, serverless, multi-tenant, or you need per-request SAS auth).

If you must have Storage::disk('azure-files'), write a small League\Flysystem\FilesystemAdapter implementation mapping write/readStream/delete/listContents onto the proxy, and register it in a FilesystemServiceProvider via Storage::extend(). Budget a day: listContents must handle pagination and the non-recursive directory model, and Flysystem's move/copy semantics don't map cleanly (see below).

Gotchas and Tips

  • No Azure Files support in Azurite v3. Azurite emulates Blob, Queue and Table only. There is no local emulator for file shares, so integration tests either hit a real (cheap, throwaway) share or run against an interface you can fake in memory. Design for the latter from day one.
  • No atomic rename. There is no server-side rename; copyFile + deleteFile leaves a window where both or neither exist. Don't build a "write temp then rename into place" pattern on this backend — write directly, or use a marker/status column in your DB to signal completeness.
  • No leases/locks. Blob leases don't exist for Files. Two queue workers writing the same path will interleave. Use Cache::lock() or a DB advisory lock around shared-path writes.
  • Case-insensitive, case-preserving names. Report.CSV and report.csv are the same file. This breaks anything that assumes case-sensitive keys (e.g. hash-based filenames are fine, user-supplied paths are not). Share names are stricter still: lowercase, 3–63 chars, no consecutive hyphens.
  • File paths must not start with /. '/exports/a.csv' and 'exports/a.csv' are not normalised for you; one throws, the other works.
  • Catch ServiceException, read getCode(). Useful codes: ShareAlreadyExists, ResourceAlreadyExists, ParentNotFound, ResourceNotFound, ShareBeingDeleted, AuthorizationFailure, AuthenticationFailed. getError() on the exception gives the raw error body for anything unusual. Do not blanket-catch — a swallowed AuthenticationFailed in a queue worker is a silent data-loss bug.
  • Share quota and size limits. Default share quota is 5 TiB and per-file max is 4 TiB; 100 TiB shares require a support-enabled account setting. getShareProperties()->getQuota() tells you if you're close.
  • Set content type and metadata explicitly. Uploads default to application/octet-stream, which breaks browser previews and anything downstream that sniffs MIME. Pass a CreateFileOptions with setContentType(). Metadata is capped around 8 KB total — don't stuff serialized arrays in there.
  • Listing paginates at ~5000 entries. listDirectoriesAndFiles() returns a continuation token; loop with setMarker() and handle getContinuationToken() returning null, or a large directory will look mysteriously truncated.
  • Deleting a share requires deleting its snapshots first. A deleteShare after snapshotting fails until you pass the includeSnapshots option. Same trap applies to a cleanup job that "just deletes everything".
  • Debugging is painful because there are no logs. Wrap the proxy in a decorator that logs method name, path, duration and outcome, or attach a Guzzle history middleware if your version of ServicesBuilder accepts an HTTP client / middleware options array (check the class for the exact keys — it differs between the split packages). Without this you get an opaque exception and no request ID to give Azure support.
  • PHP 8 compatibility hinges on the 1.5 release. Older 1.x tags will fail on PHP 8.1+ with dynamic-property deprecations and strlen(null) errors. Confirm composer show microsoft/azure-storage-file resolved 1.5.x before debugging anything else.
  • Migration path if the maintenance status bothers you: use microsoft/azure-storage-blob with league/flysystem-azure-blob-storage and get a real Laravel disk, SAS support and active maintenance — but only if you don't actually need SMB semantics. If you do need a real file share, mount it over CIFS or move to the newer Azure SDK for PHP data-plane packages rather than extending the life of this one.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core