cpliakas/dynamo-db-odm
A lightweight PHP object-document mapper for Amazon DynamoDB. Map PHP classes to DynamoDB tables with annotations, then persist, load, and query records through repositories built on the AWS SDK for PHP.
This package is archived, so treat the first step as due diligence rather than a normal composer require. Open composer.json and src/ before writing application code: confirm the supported PHP version, whether it depends on AWS SDK for PHP v2 or v3, and what the actual entity/ODM API looks like. The README and tests/ are usually the fastest source of truth for mapping annotations and repository names.
Minimal steps:
Install and pin the package, plus a compatible AWS SDK version:
composer require cpliakas/dynamo-db-odm --with-all-dependencies
If Composer cannot resolve it because of abandoned dependencies, add a VCS repository or fork it into your own namespace and pin the fork.
Create a DynamoDB client. For local development, point it at DynamoDB Local; for production, rely on the normal AWS credential chain:
use Aws\DynamoDb\DynamoDbClient;
$client = DynamoDbClient::factory([
'region' => getenv('AWS_DEFAULT_REGION'),
'version' => '2012-08-10',
// Local only:
// 'endpoint' => 'http://127.0.0.1:8000',
// 'credentials' => ['key' => 'local', 'secret' => 'local'],
]);
Initialize the ODM layer. The package is lightweight, so expect a small bootstrap object rather than a full Doctrine-style container. Check the source for the exact class name; typically it is an Odm or entity-manager object wrapping the client.
Map your first entity. The package uses class/property metadata for table, hash key, range key, and attribute mapping. Verify the exact annotation names under src/Annotation or equivalent before copying examples:
/**
* @DynamoDbTable(name="users")
*/
class User
{
/** @DynamoDbHashKey */
private $id;
/** @DynamoDbAttribute */
private $email;
public function getId(): string { return $this->id; }
public function setId(string $id): void { $this->id = $id; }
public function getEmail(): string { return $this->email; }
public function setEmail(string $email): void { $this->email = $email; }
}
Use the first use case: persist and retrieve a single item by hash key.
$odm = new Odm($client);
$users = $odm->getRepository(User::class);
$user = new User();
$user->setId('user-1');
$user->setEmail('[email protected]');
$users->save($user);
$found = $users->find('user-1');
If the repository API differs, the underlying pattern is still the same: wrap the client, map an entity to a table, and expose CRUD/query methods around that entity.
Repository per entity. Keep DynamoDB access behind a repository or service class so controllers and jobs do not depend directly on the ODM. This also gives you one place to adapt if you later migrate to the official AWS SDK directly.
final class UserRepository
{
public function __construct(private Odm $odm) {}
public function find(string $id): ?User
{
return $this->odm->getRepository(User::class)->find($id);
}
public function save(User $user): void
{
$this->odm->getRepository(User::class)->save($user);
}
}
Laravel service container integration. Register the client and ODM as singletons. DynamoDB clients are safe to reuse and creating them per request is wasteful.
// app/Providers/DynamoDbServiceProvider.php
public function register(): void
{
$this->app->singleton(DynamoDbClient::class, function () {
return DynamoDbClient::factory([
'region' => config('services.dynamodb.region'),
'version' => '2012-08-10',
'endpoint' => config('services.dynamodb.endpoint') ?: null,
]);
});
$this->app->singleton(Odm::class, function ($app) {
return new Odm($app->make(DynamoDbClient::class));
});
}
Then inject UserRepository or Odm into actions, jobs, and console commands.
Hydration and entity design. Keep entity properties simple scalars: strings, ints, bools, arrays/maps, and string sets. Add explicit getters/setters instead of relying on dynamic properties. This matters more on PHP 8.2+ and makes the mapping predictable.
Queries and pagination. Use query operations when you have the hash key; use scan only for admin or low-volume paths. Always handle pagination explicitly. DynamoDB returns a LastEvaluatedKey; the ODM may expose it through a result object or iterator. If not, drop down to the raw client for that operation.
// Prefer query for access patterns backed by the hash key.
$result = $client->query([
'TableName' => 'users',
'KeyConditionExpression' => 'id = :id',
'ExpressionAttributeValues' => [':id' => ['S' => 'user-1']],
]);
Batch and transactional workflows. For imports, batch writes in groups of 25 and implement retries for unprocessed items. Do not loop single save() calls over thousands of records; it will burn throughput and be slow.
Testing. Run DynamoDB Local in CI and tests. Create tables from the same schema definitions used in production, then assert against repository methods rather than raw item arrays.
docker run -p 8000:8000 amazon/dynamodb-local -jar DynamoDBLocal.jar -sharedDb
$client = DynamoDbClient::factory([
'region' => 'us-east-1',
'version' => '2012-08-10',
'endpoint' => 'http://127.0.0.1:8000',
'credentials' => ['key' => 'local', 'secret' => 'local'],
]);
Table provisioning. This package is an ODM, not a migration tool. Keep table creation in infrastructure-as-code (CloudFormation, Terraform) or a small Artisan command that calls CreateTable. Do not rely on implicit table creation in production.
Archived status is the largest risk. There will be no security or compatibility patches. Fork it, pin the fork, and audit its AWS SDK dependency. If the dependency is AWS SDK v2, plan an exit path because v2 is end-of-life and incompatible with modern Guzzle/PHP versions.
Do not mix AWS SDK major versions. If your application already uses AWS SDK v3, this package may pull in v2 or conflict with it. Check composer.lock and run composer why aws/aws-sdk-php before adoption.
Annotation parsing is expensive if uncached. If the package uses Doctrine annotations, cache annotation metadata in production. Parsing entity classes on every request is a common hidden performance cost. Also note that Doctrine annotations do not read PHP 8 attributes; you cannot migrate mappings to attributes without changing the package.
Exact key names and types matter. DynamoDB will not match a hash key named id if the table defines Id. Confirm table schema, attribute names, and scalar types against the entity mapping. A mismatch often appears as an empty result or a validation exception, not a clear mapping error.
Query requires the full hash key. You cannot query by an arbitrary non-key attribute. Design access patterns around hash and range keys, or add indexes. Scanning in a web request is a throughput and latency trap.
Pagination is mandatory. A single request returns at most 1 MB. If the ODM hides this, verify whether it auto-paginates. For reports and exports, use the raw client with ExclusiveStartKey and log LastEvaluatedKey.
DynamoDB Local hides IAM and throughput problems. Local does not enforce IAM permissions or provisioned capacity. Test credential handling and retry/backoff behavior against a real AWS account before relying on it.
Watch number and set types. DynamoDB numbers are transmitted as strings and can lose precision if converted to floats. Empty strings and nulls have specific handling rules. Validate entity values before persistence rather than assuming the ODM will normalize them.
Debug at the client boundary. When an ODM operation behaves unexpectedly, log the underlying AWS request and response. If the package does not expose this, wrap the DynamoDbClient or enable AWS SDK debug logging. Most “ODM bugs” are mapping or key-schema mismatches.
Extension points are limited. For conditional writes, transactions, streams, or complex indexes, drop down to the raw DynamoDbClient. Keep those raw operations in the same repository layer so the rest of the application stays isolated from both the ODM and the SDK.
Plan for replacement. Because the package is archived and low-usage, keep the ODM behind a narrow interface (find, save, delete, query). That makes it feasible to replace with direct AWS SDK calls or a maintained ODM later without rewriting domain code.
How can I help you explore Laravel packages today?