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 Table Laravel Package

microsoft/azure-storage-table

PHP client library for Microsoft Azure Table storage. Create, read, update, and delete entities; run queries and batch operations; authenticate with account keys or SAS tokens. A straightforward SDK for NoSQL table data in Azure.

View on GitHub
Deep Wiki
Context7

Getting Started

Read the parent repo, not this one. microsoft/azure-storage-table is a read-only split of azure-storage-php; the archived stub repo exists only so Composer can resolve microsoft/azure-storage-table. All docs, samples, and issue history live upstream. Treat this as a legacy SDK you are adopting knowingly: last release 2022-09-02, archived, zero dependents — you will be the one finding the bugs.

Install and bind once.

composer require microsoft/azure-storage-table

There is no service provider, facade, or config file — it is a plain Guzzle-backed SDK. Wire it yourself:

// config/services.php
'azure_table' => [
    'connection_string' => env('AZURE_TABLE_CONNECTION_STRING'),
    'table' => env('AZURE_TABLE_NAME', 'appdata'),
],
// app/Providers/AppServiceProvider.php
use MicrosoftAzure\Storage\Table\TableRestProxy;

$this->app->singleton(TableRestProxy::class, fn () => TableRestProxy::createTableService(
    config('services.azure_table.connection_string')
));

Connection string form: DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net. For local dev, Azurite supports the Table service — UseDevelopmentStorage=true and run azurite --tableHost 127.0.0.1.

First use case: append-only audit/event log. Table Storage is a schemaless, cheap, key-addressed store, which makes it a natural fit for "write a lot, query by tenant + time" data you do not want in a relational table. It is a poor fit for anything needing joins, aggregation, or ad-hoc filtering.

use MicrosoftAzure\Storage\Table\TableRestProxy;

$proxy = app(TableRestProxy::class);
$proxy->createTable('auditlog');           // 409 TableAlreadyExists is fine; swallow it

$proxy->insertEntity('auditlog', [
    'PartitionKey'          => 'tenant-42',
    'RowKey'                => (string) Str::uuid(),
    'Event@type'            => 'Edm.String',
    'Event'                 => 'user.login',
    'OccurredAt@type'       => 'Edm.DateTime',
    'OccurredAt'            => now()->utc()->format('Y-m-d\TH:i:s\Z'),
    'ActorId@type'          => 'Edm.Int64',
    'ActorId'               => (string) $user->id,   // Int64 must be a string
]);

Implementation Patterns

Wrap it in a repository, never call TableRestProxy from controllers. The SDK's arrays-in/objects-out API leaks into everything; hide it behind one class so swapping to Cosmos DB or a real DB later is a single-file change.

namespace App\Storage;

use MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;
use MicrosoftAzure\Storage\Table\TableRestProxy;

class TableStore
{
    public function __construct(
        private TableRestProxy $proxy,
        private string $defaultTable,
    ) {}

    public function put(string $table, array $entity): void
    {
        $this->proxy->insertOrMergeEntity($table, $entity);
    }

    public function forPartition(string $table, string $pk, int $top = 100, ?string $token = null): array
    {
        $options = new QueryEntitiesOptions();
        $options->setFilter(Filter::applyEq(
            Filter::applyPropertyName('PartitionKey'),
            Filter::applyConstant($pk, 'Edm.String')
        ));
        $options->setTop($top);
        if ($token) {
            $options->setContinuationToken($token);
        }

        return $this->proxy->queryEntities($table, $options)->getEntities();
    }
}

Filters are typed object graphs, not raw OData strings — build them with the Filter::apply* helpers (applyEq, applyGe, applyAnd, applyOr, applyNot, applyPropertyName, applyConstant). The escape hatch is in vendor/microsoft/azure-storage-table/src/Table/Models/Filters/ if you need something the helpers don't cover.

Shape your keys for the query you will actually run. There are no secondary indexes; anything not on PartitionKey/RowKey is a scan. Two patterns carry most of the weight:

  • PartitionKey = "tenant-{id}", RowKey = "{reverse-ticks}-{uuid}" so newest-first reads are a single partition range scan.
  • PartitionKey = "tenant-{id}:{type}" to co-locate heterogeneous records and keep batches single-partition.

Batch writes are your transaction unit. Up to 100 operations, all in the same table and same PartitionKey, atomic and no duplicate keys inside one batch:

use MicrosoftAzure\Storage\Table\Models\BatchOperations;

$batch = new BatchOperations();
foreach ($rows as $row) {
    $batch->addInsertOrMergeEntity('auditlog', $row);
}
$results = $this->proxy->batch($batch);   // per-op status, not all-or-nothing at the HTTP level

Optimistic concurrency via ETag. getEntity returns an entity with getEtag(); pass it to UpdateEntityOptions::setIfMatch($etag) and catch the 412 conflict. This is the only locking primitive you get.

Queue it. Writes to Table Storage are network-bound and occasionally throttled (503 with Retry-After). Push audit writes onto a Laravel queue job rather than blocking a request, and let the SDK/common layer's retry middleware handle transient 5xx.

Pagination is explicit. A query returns at most 1000 entities and a continuation token in getContinuationToken(). Feed that back through setContinuationToken() in a loop — do not pretend setTop() gives you a complete result set.

Gotchas and Tips

  • Archived = frozen. No PHP 8.x / Guzzle conflict fixes incoming. Pin the exact version in composer.json and check composer why guzzlehttp/guzzle after install; a Laravel upgrade that bumps Guzzle is your most likely breakage.
  • Type annotations are mandatory for non-strings. Every non-Edm.String property needs a sibling "Name@type" key. Edm.Int64 must be a string; Edm.DateTime must be ISO-8601 UTC (Y-m-d\TH:i:s\Z, millisecond precision only — sub-ms precision silently truncates); Edm.Binary is base64.
  • Timestamp is system-managed. Do not write it, do not filter on it for ordering — use your own OccurredAt column.
  • Key constraints. PartitionKey/RowKey max 1 KB and cannot contain / \ # ? or control characters. UUIDs are fine; raw user input is not — hash or base64 it.
  • No TTL, no auto-expiry. There is no DynamoDB-style TTL. Retention means a scheduled php artisan command that deletes by partition, or an ExpiresAt column plus a sweeper. Plan for it on day one.
  • Entity limits: 1 MB per entity, 255 properties, 64 KB per string property. Large JSON blobs belong in Blob Storage with the table holding the pointer.
  • Query cost is the whole game. A point read (PartitionKey + RowKey) is one cheap transaction; a filter on a non-key property is a full partition scan billed per entity examined. Never expose a user-supplied filter to this API.
  • String comparisons in filters are case-sensitive and there is no contains/like. Prefix matching requires ge/lt range pairs on the key.
  • insertEntity throws on duplicate keys (409) — use insertOrMergeEntity / insertOrReplaceEntity for idempotent writes, which matters for retried queue jobs.
  • Merge semantics are shallow and destructive-by-omission. mergeEntity updates only supplied properties; updateEntity (replace) wipes everything you omit. Pick deliberately.
  • Swallow TableAlreadyExists at boot, never in a request path — createTable on every page load is wasted RU and a latency tax.
  • Extension point: if you need Cosmos DB Table API instead of Azure Table, the wire protocol is compatible — swap the connection string/endpoint and watch for Cosmos's stricter limits (no Top > 1000, different Timestamp behavior, RU throttling surfaced as 429 rather than 503).
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