Skip to content

omnidrive/azure

Omnidrive Azure: a container of Azure Blob Storage as an omnidrive drive, through azure-oss/storage-blob-flysystem (the adapter The PHP League names in place of its own, abandoned) - a connection string, a prefix, public and signed (SAS) addresses.

Floor
Provider · Omnidrive
Version
1.x
Conformance suite
runs, CI green
Verified for real
Verified in part
Licence
MIT
composer require omnidrive/azure:1.x-dev

A container of Azure Blob Storage as an omnisense/omnidrive drive, through azure-oss/storage-blob-flysystem (AzureBlobStorageAdapter over Azure-OSS's BlobServiceClient) - the adapter The PHP League names in place of its own league/flysystem-azure-blob-storage, abandoned with Microsoft's retired PHP SDK. The Blob service's address is the connection string's BlobEndpoint, else https://<account>.blob.core.windows.net.

Installation

composer require omnisense/omnidrive omnidrive/azure

Requires PHP 8.2, omnisense/omnidrive (1.x) and azure-oss/storage-blob-flysystem (^2.2).

Credentials

The storage account's connection string (the portal's "Access keys"; a shared key, or a SAS), or its name and key (account_name, account_key). Temporary addresses are signed with the account key. Entra ID credentials are not mapped. Azurite, Microsoft's emulator, has a documented development account (devstoreaccount1), which the harness uses.

Options

new AzureDriveFactory(); factory name azure.

Option Type Default Required
container string - yes
connection_string string none one of the two the portal's
account_name, account_key string none one of the two instead of a connection string
endpoint string https://<account>.blob.core.windows.net no with an account name: another cloud, Azurite
prefix string '' no every path under it
public_container bool false no the container's blobs readable anonymously: a blob's own address is its public one
public_url string or list none no the prefix files are served from, or several (sharded)
visibility public/private ignored no a write's visibility when it does not say
directory_visibility public/private ignored no the same for directories
retain_visibility bool true no whether a copy keeps its source's visibility

Capabilities

Capability
local no
publicUrls with public_container or public_url only
temporaryUrls yes: a read-only SAS (sp=r, sr=b) signed with the account key
visibility per file no: reading or changing one is a DriveException
checksum any algorithm hash() knows (the blob downloaded and hashed)

Behaviour

The adapter's own public address, for a private container, is a SAS valid a thousand years: this drive does not hand it out as "public" (the factory passes $adapterPublicUrls: false), and gives a public address only with public_container (the blob's plain address) or public_url. The namespace is flat: createDirectory() makes nothing, a directory exists while it holds a blob. A stream written may be closed by the client once sent. The connection string is parsed when the drive is built; nothing is sent until the first operation.

Example

<?php

require __DIR__.'/vendor/autoload.php';

use AzureOss\Storage\Blob\BlobServiceClient;
use Omnidrive\Azure\AzureDriveFactory;
use Omnidrive\Registry;

// Azurite's documented development account; on Azure, the portal's connection string.
$azurite = 'DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1';
$drives = new Registry([new AzureDriveFactory()], [
    'private' => ['factory' => 'azure', 'options' => ['container' => 'documents', 'connection_string' => $azurite, 'prefix' => 'site']],
    'behind-cdn' => ['factory' => 'azure', 'options' => ['container' => 'documents', 'connection_string' => $azurite, 'public_url' => 'https://cdn.example.org/documents']],
]);
BlobServiceClient::fromConnectionString($azurite)->getContainerClient('documents')->createIfNotExists();   // Azurite starts empty

$documents = $drives->get('private');
$documents->write('contracts/2026/C-12.pdf', '%PDF-1.4 ...');
echo json_encode($documents->capabilities()->toArray()), ' ', json_encode($drives->get('behind-cdn')->capabilities()->toArray()), "\n";
$url = $documents->temporaryUrl('contracts/2026/C-12.pdf', new DateTimeImmutable('+10 minutes'));
parse_str((string) parse_url($url, PHP_URL_QUERY), $sas);
echo strtok($url, '?'), ' sp=', $sas['sp'], ' sr=', $sas['sr'], "\n";
echo file_get_contents($url), "\n";
$documents->createDirectory('empty');
var_dump($documents->directoryExists('empty'), $documents->directoryExists('contracts'));
$documents->deleteDirectory('contracts');
{"local":false,"publicUrls":false,"temporaryUrls":true} {"local":false,"publicUrls":true,"temporaryUrls":true}
http://azurite:10000/devstoreaccount1/documents/site/contracts/2026/C-12.pdf sp=r sr=b
%PDF-1.4 ...
bool(false)
bool(true)

(run on 2026-10-25 in omnidrive's harness against Azurite: the SAS address served the blob; the directory made empty does not exist, the one holding a blob does)

Errors

The service's refusals are DriveExceptions; a missing blob read is a FileNotFoundException. A missing container, or neither a connection string nor an account name and key, is an InvalidConfigException (The "azure" drive needs connection_string, or account_name and account_key.).

Verified, and not

Verified (2026-10-24 and 2026-10-25, omnidrive's harness, PHP 8.4): the contract's tests against Azurite, its directories as a flat namespace has them; a SAS address signed and fetched, the blob's plain address refused; in plain PHP (bare --live); offline: the SAS's address, permission and resource, the public address of a public container and behind a CDN, no public address otherwise. Not verified: Azure Blob Storage itself (Azurite only); an account with a hierarchical namespace (Data Lake Gen2); a connection string with a SAS; Entra ID credentials.

Responsibility

An independent, unofficial client, not affiliated with Microsoft, Azure-OSS or The PHP League. Whoever runs it is responsible for the data it stores and the access it gives (a public or temporary address serves the file to whoever holds it); see the core's Responsibility section (omnisense/omnidrive).

Propose a change docs/index.md