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.
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.
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).
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.Cache::lock() or a DB advisory lock around shared-path writes.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./. '/exports/a.csv' and 'exports/a.csv' are not normalised for you; one throws, the other works.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.getShareProperties()->getQuota() tells you if you're close.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.listDirectoriesAndFiles() returns a continuation token; loop with setMarker() and handle getContinuationToken() returning null, or a large directory will look mysteriously truncated.deleteShare after snapshotting fails until you pass the includeSnapshots option. Same trap applies to a cleanup job that "just deletes everything".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.strlen(null) errors. Confirm composer show microsoft/azure-storage-file resolved 1.5.x before debugging anything else.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.How can I help you explore Laravel packages today?