dreamfactory/azure-documentdb-php-sdk
PHP SDK for Azure DocumentDB (Cosmos DB) from DreamFactory. Provides a client for working with databases, collections, and documents via the DocumentDB REST API, including CRUD and query operations. Small, low-adoption package; check maintenance status before production use.
Reality check first. This package is a DreamFactory fork of Microsoft's original Azure DocumentDB PHP SDK (DocumentDB is now Cosmos DB's SQL API). It has zero stars, zero dependents, and no Laravel service provider, facade, or config file. Treat it as vendored code you now own — not as a maintained dependency. Before writing a line of application code, do two things:
vendor/dreamfactory/azure-documentdb-php-sdk/composer.json and check the require block and autoload section. Old Azure SDKs often pin PHP 5.6/7.0, Guzzle 5/6, and use classmap autoloading rather than PSR-4. If it won't install on your PHP version, stop and pivot to Cosmos DB's MongoDB-compatible endpoint with mongodb/laravel-mongodb instead.src/Client.php (or DocumentClient.php) top to bottom. It's a thin HMAC-SHA256 signer over the Cosmos DB REST API — every public method maps 1:1 to a REST verb + resource link. The SDK is the documentation for the REST API, and vice versa.Install and bind. There's no auto-discovery, so register it yourself:
// composer require dreamfactory/azure-documentdb-php-sdk
// config/services.php
'cosmos' => [
'host' => env('COSMOS_HOST'), // https://acct.documents.azure.com:443/
'key' => env('COSMOS_KEY'), // primary master key
'db' => env('COSMOS_DATABASE'),
],
// app/Providers/CosmosServiceProvider.php
public function register(): void
{
$this->app->singleton(Client::class, fn () => new Client(
config('services.cosmos.host'),
config('services.cosmos.key'),
));
}
First use case. Don't reach for this for data that belongs in MySQL. The canonical fit is schema-drift documents: vendor/product payloads from external feeds, event envelopes, rendered content blocks, audit records with per-tenant shapes. Start with a single collection and one read/write path — a ProductDocumentRepository with find(string $id): ?array and upsert(array $doc): array — before wiring it into anything else.
$collLink = "dbs/{$db}/colls/{$collection}"; // note: no leading slash, ids URL-encoded
$doc = new Document();
$doc->setBody(['id' => $sku, 'type' => 'product', 'title' => $title, 'attrs' => $attrs]);
$response = $client->createDocument($collLink, $doc);
$created = $response->getBody(); // ['id' => ..., '_etag' => ..., '_ts' => ...]
Wrap, never call Client from controllers. The SDK hands you raw arrays with _rid/_self/_etag/_ts metadata and throws HTTP-shaped exceptions. Put a repository behind an interface per collection so the rest of the app sees plain arrays and a small exception vocabulary:
interface DocumentRepository {
public function find(string $id): ?array;
public function query(string $sql, array $params = [], int $max = 100): array;
public function upsert(array $doc, ?string $etag = null): array;
public function delete(string $id, ?string $etag = null): void;
}
Parameterize every query. The SDK exposes a Query object with SQL parameters — use them. String-concatenating a tenant id into SELECT * FROM c WHERE c.tenantId = '...' is both an injection vector and a cache-defeating disaster on Cosmos.
$query = new Query('SELECT c.id, c.title FROM c WHERE c.tenantId = @t AND c.type = "product"');
$query->addParameter('@t', $tenantId);
$opts = new FeedOptions();
$opts->setMaxItemCount($max);
$feed = $client->queryDocuments($collLink, $query, $opts);
$rows = $feed->getBody()['Documents'] ?? [];
Paginate with continuation tokens, not offsets. Cosmos has no OFFSET. For exports or backfills, loop on the feed's continuation token and hand the token to a queued job so a long scan doesn't blow the request timeout. Persist the token in the job payload — it's opaque and account-scoped.
Track request charge like a budget line. Every response carries x-ms-request-charge (RUs). Log it in a middleware or a Log::channel('cosmos') call in your repository so you can see which endpoints are eating your provisioned throughput before the invoice tells you.
Optimistic concurrency via ETag. Read the doc, keep _etag, and send it back as an access condition on replace. Two workers editing the same document will then get a 412 instead of silently clobbering. Without this, the SDK is last-write-wins and you will absolutely lose data in a queue-heavy app.
Testing. Because the SDK has no test double, define the interface above and bind a FakeDocumentRepository (in-memory array) in tests via $this->app->instance(...). Only the thin SDK adapter class — 30 lines — is untested, and that's acceptable. Add one integration test against a real Cosmos account in CI gated by an env flag.
When to reach for something else. Relational joins, transactions, WHERE ... OR spanning many fields, anything needing COUNT(*) at scale — put it in Postgres. Consider Postgres jsonb as a drop-in for most of what you'd use DocumentDB for, with none of the operational risk. Use Cosmos when you specifically need multi-region writes or global low-latency reads.
x-ms-documentdb-partitionkey support. If your container is partitioned, point reads and cross-partition queries may fail or silently scan. Verify against a partitioned container on day one — this is the single most likely reason the package is dead. If it breaks, the fork-in-repo-and-patch path is the only path.id is a string, case-sensitive, cannot contain /, \, ?, #, or be longer than 255 chars. Use a sanitizer when deriving ids from user input (URL slugs, emails) or you'll get cryptic 400s.dbs/mydb/colls/mycol/docs/abc — no leading slash, no host, and every segment must be URL-encoded. Build them in exactly one helper method; ad-hoc string building is where most bugs hide.x-ms-retry-after-ms, and prefer Queue::release() on a rate-limited job over sleeping in the request.config/, and use a read-only key where possible.catch (\Exception) here will hide a malformed id as "service unavailable."x-ms-request-charge — the SDK gives you no logging. Also dd()-dump $response->getBody() before assuming your query logic is wrong; the body envelope (Documents, _count, continuation) is easy to misread.jsonb, or a current Cosmos SDK is a one-class change. Given the package's health (0 stars, 0 dependents, no releases), plan the swap before you need it.src/ into app/Support/Cosmos/ rather than composer.json-patching a dead dependency — future composer update runs will otherwise overwrite your fix or fail entirely.How can I help you explore Laravel packages today?