rikudou/psr6-dynamo-db-bundle
A Symfony bundle that provides a PSR-6 cache adapter backed by AWS DynamoDB, letting applications use a DynamoDB table for scalable cache storage and integrate the cache pool with Symfony's configuration and services.
This is a small, low-adoption bundle (20 stars, no dependents), so treat the source as the documentation. Read, in order:
vendor/rikudou/psr6-dynamo-db-bundle/README.md — usually the only prose available.src/DependencyInjection/Configuration.php — the authoritative list of config keys and defaults.src/DependencyInjection/*Extension.php and src/Resources/config/services.yaml — the service IDs you'll actually inject.rikudou/psr6-dynamo-db, now >= 3.2) — the table schema, key attribute name, TTL handling and serialization live there, not in the bundle.Minimal setup
composer require rikudou/psr6-dynamo-db-bundle:^4.2
Pin the constraint explicitly. v4.2.0 is the first release with Symfony 8 coverage and a raised floor on the base library (>= 3.2); if you leave the constraint loose you can silently pull a base-library minor that changes TTL/consistency behaviour under you.
Register the bundle in config/bundles.php unless the Flex recipe does it:
Rikudou\Psr6DynamoDbBundle\RikudouPsr6DynamoDbBundle::class => ['all' => true],
Create the DynamoDB table before the app runs. The primary key name must match what the library expects — confirm it in the pool source, but it is typically a string hash key:
aws dynamodb create-table \
--table-name app_cache \
--attribute-definitions AttributeName=key,AttributeType=S \
--key-schema AttributeName=key,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
Configure the bundle (key names vary by version — check Configuration.php):
# config/packages/rikudou_psr6_dynamo_db.yaml
rikudou_psr6_dynamo_db:
table_name: '%env(DYNAMODB_CACHE_TABLE)%'
region: '%env(AWS_REGION)%'
# endpoint: 'http://localhost:8000' # DynamoDB Local / LocalStack
Then find and alias the pool:
bin/console debug:container --show-private | grep -i dynamo
services:
Psr\Cache\CacheItemPoolInterface: '@rikudou_psr6_dynamo_db.cache' # confirm ID
Psr\SimpleCache\CacheInterface: '@rikudou_psr6_dynamo_db.simple_cache'
First use case: replacing cache.app for a workload on ephemeral, multi-instance infrastructure (Lambda, ECS, Kubernetes) where the filesystem cache is per-container and useless. A shared DynamoDB-backed cache for third-party API responses or computed aggregates is the sweet spot.
Read-through with PSR-6 (best when you need isHit(), deferred writes, or item metadata):
final class ExchangeRateProvider
{
public function __construct(private CacheItemPoolInterface $cache) {}
public function rates(string $base): array
{
$item = $this->cache->getItem('rates_' . strtolower($base));
if (!$item->isHit()) {
$item->set($this->fetchFromApi($base));
$item->expiresAfter(300);
$this->cache->save($item);
}
return $item->get();
}
}
Batching to cut round trips. Every save() is a DynamoDB write. When populating several keys at once, use deferred saves and a single commit() — this is the main lever you have on both latency and cost:
foreach ($rows as $id => $row) {
$item = $this->cache->getItem("row_$id");
$item->set($row)->expiresAfter(600);
$this->cache->saveDeferred($item);
}
$this->cache->commit();
PSR-16 for simple callers. For scalar values, inject CacheInterface and use get/set/delete with has() guards. Use PSR-6 in libraries that may be consumed elsewhere; PSR-16 is friendlier in controllers and small services.
Key namespacing. One table, many consumers. Prefix every key with the app and environment (sprintf('%s:%s:rates_%s', $app, $env, $base)) either via a thin decorating wrapper or a bundle option if one exists. This makes clearing/segregating environments possible without a second table.
Local development. Point endpoint at DynamoDB Local or LocalStack, and create the table as part of the container startup. Gate this on %kernel.environment% so production never picks up the local endpoint. Keep a separate DYNAMODB_CACHE_TABLE value per env in Symfony Secrets/env vars.
Integration tip — layered cache. DynamoDB round trips are milliseconds, not nanoseconds. For hot keys, put a request-scoped array cache or a Symfony array adapter in front of the DynamoDB pool and memoize within the request; only the misses go to the network.
Warming. If you have known hot keys (config, tax rates, feature flags), add a small console command that computes and writes them on deploy. This avoids a cold-start thundering herd hitting the origin.
getItem is a read request; every save a write. Caching infrequently-read values in DynamoDB is more expensive than not caching at all. Use it for shared state with a real hit rate, not for in-request memoization.ResourceNotFoundException), not at container compile time. Add table creation/validation to your deploy pipeline or a health check.rikudou/psr6-dynamo-db after upgrading rather than trusting notes you took on an older version.hash('sha256', $key)) before storing, but keep a readable prefix for debugging by hand in the AWS console.serialize preserves types; JSON loses DateTime and object identity) and add an explicit serializer in your values if needed.ConsistentRead.get → set → save sequence is read-modify-write and loses updates under concurrency. For counters or mutual exclusion, talk to DynamoDB conditional writes directly.try {
$item = $this->cache->getItem($key);
} catch (\Psr\Cache\CacheException $e) {
$this->logger->warning('DynamoDB cache unavailable', ['exception' => $e]);
return $fallback->get($key);
}
Aws\DynamoDb\DynamoDbClient service defined, reference it from the bundle config instead of letting the bundle build one.debug:container for service IDs, debug:autowiring cache to verify the alias, AWS SDK logging (Aws\Middleware / Monolog handler) for the actual request parameters, and CloudWatch ThrottledRequests / ConsumedReadCapacityUnits when latency spikes. Compare the key the app writes with what you see in the DynamoDB console — mismatches are almost always prefix or hashing issues.cache:pool:clear only works if the pool is wired into Symfony's cache framework; otherwise expiry and prefixes are your only tools. Ship a small console command that scans and batch-deletes by prefix for the environments where you need it..gitattributes chore, and a base-library floor of >= 3.2. Practically that means: composer update will likely move rikudou/psr6-dynamo-db forward too, Symfony 8 is now supported alongside the older majors, and there is no migration step from 4.0.1. Still run your test suite against a real (or Local) table after bumping, because the base-library jump is where behavioural drift would hide. Also confirm the bundle's own symfony/* constraints in composer.json match your framework version before assuming the CI matrix makes it safe.^4.2) and skim the changelog and src/ before each upgrade — the surface area is small enough that reading it is a ten-minute job.How can I help you explore Laravel packages today?