rikudou / psr6-dynamo-db
PSR-6 and PSR-16 cache implementation using AWS DynamoDB
Installs: 50 507
Dependents: 1
Suggesters: 0
Security: 0
Stars: 6
Watchers: 2
Forks: 4
Open Issues: 2
Requires
- php: ^8.0
- async-aws/dynamo-db: ^1.0
- psr/cache: ^2.0 | ^3.0
- psr/simple-cache: ^1.0
- rikudou/clock: ^1.0
Requires (Dev)
- ext-json: *
- friendsofphp/php-cs-fixer: ^2.16
- jetbrains/phpstorm-attributes: ^1.0
- phpstan/phpstan: ^1.8
- phpunit/phpunit: ^9.3
Provides
README
Library for storing cache in DynamoDB implementing the PSR-6 and PSR-16 interfaces. See also Symfony bundle of this library.
Since version 2 this library uses the lightweight async-aws/dynamo-db instead of the full AWS SDK.
Installation
composer require rikudou/psr6-dynamo-db
Usage
The usage is pretty straight-forward, you just define the details in constructor and then use it as any other PSR-6 or PSR-16 implementation:
<?php use Rikudou\DynamoDbCache\DynamoDbCache; use Rikudou\DynamoDbCache\DynamoDbCacheBuilder; use AsyncAws\DynamoDb\DynamoDbClient; $cache = new DynamoDbCache('dynamoTableName', new DynamoDbClient([])); // with custom field names $cache = new DynamoDbCache('dynamoTableName', new DynamoDbClient([]), 'customPrimaryKeyField', 'customTtlField', 'customValueField'); // using builder $cache = DynamoDbCacheBuilder::create('dynamoTableName', new DynamoDbClient([])) ->withPrimaryField('customPrimaryKeyField') ->withTtlField('customTtlField') ->withValueField('customValueField') ->build();
It's recommended to use the builder for creating new instances. The builder is immutable and every method returns a new instance.
The default values for fields are:
- primary key -
id
(string) - ttl field -
ttl
(number) - value field -
value
(string)
You must create the DynamoDB table before using this library.
Basic example:
<?php use AsyncAws\DynamoDb\DynamoDbClient; use Rikudou\DynamoDbCache\DynamoDbCache; function get(string $key): string { $dynamoDbClient = new DynamoDbClient([ 'region' => 'eu-central-1', ]); $cache = new DynamoDbCache('cache', $dynamoDbClient); // the default field names are used - id, ttl and value $item = $cache->getItem($key); if ($item->isHit()) { return $item->get(); } // do something to fetch the item $result = '...'; $item->set($result); $item->expiresAfter(3600); // expire after one hour if (!$cache->save($item)) { throw new RuntimeException('Could not save cache'); } return $result; }
Example using the PSR-16 interface:
<?php use AsyncAws\DynamoDb\DynamoDbClient; use Rikudou\DynamoDbCache\DynamoDbCache; function get(string $key): string { $dynamoDbClient = new DynamoDbClient([ 'region' => 'eu-central-1', ]); $cache = new DynamoDbCache('cache', $dynamoDbClient); // the default field names are used - id, ttl and value $value = $cache->get($key); if ($value !== null) { return $value; } // do something to fetch the item $result = '...'; if (!$cache->set($key, $result, 3600)) { throw new RuntimeException('Could not save cache'); } return $result; }
Prefixing
You can automatically prefix all keys in DynamoDB by using the prefix configuration like this:
<?php use Rikudou\DynamoDbCache\DynamoDbCacheBuilder; use AsyncAws\DynamoDb\DynamoDbClient; $cache = DynamoDbCacheBuilder::create('myTable', new DynamoDbClient([])) ->withPrefix('myCustomPrefix#') ->build(); $item = $cache->getItem('key1'); // fetches an item with key myCustomPrefix#key1 $key = $item->getKey(); // $key holds the full key including prefix, myCustomPrefix#key1
Converters
This implementation supports all instances of \Psr\Cache\CacheItemInterface
with the use of converters which
convert the object to \Rikudou\DynamoDbCache\DynamoCacheItem
. Note that some information may be lost in the
conversion, notably expiration date.
You can write your own converter for your specific class which includes support for expiration date like this:
<?php use Rikudou\DynamoDbCache\Converter\CacheItemConverterInterface; use Psr\Cache\CacheItemInterface; use Rikudou\DynamoDbCache\DynamoCacheItem; use Rikudou\Clock\Clock; use Rikudou\DynamoDbCache\Encoder\SerializeItemEncoder; class MyCacheItemConverter implements CacheItemConverterInterface { /** * If this methods returns true, the converter will be used */ public function supports(CacheItemInterface $cacheItem): bool { return $cacheItem instanceof MyCacheItem; } public function convert(CacheItemInterface $cacheItem): DynamoCacheItem { assert($cacheItem instanceof MyCacheItem); return new DynamoCacheItem( $cacheItem->getKey(), $cacheItem->isHit(), $cacheItem->get(), $cacheItem->getExpirationDate(), // this is a custom method from the hypothetical MyCacheItem new Clock(), new SerializeItemEncoder() ); } }
You then need to register it in the converter and assign the converter to the cache:
<?php use Rikudou\DynamoDbCache\Converter\CacheItemConverterRegistry; use Rikudou\DynamoDbCache\DynamoDbCache; use AsyncAws\DynamoDb\DynamoDbClient; use Rikudou\DynamoDbCache\DynamoDbCacheBuilder; // you don't need to add the default one as well, it will be added automatically if it's missing $converter = new CacheItemConverterRegistry(new MyCacheItemConverter()); $dynamoClient = new DynamoDbClient([]); $cache = DynamoDbCacheBuilder::create('myTable', $dynamoClient) ->withConverterRegistry($converter) ->build(); $myOldCache = new MyCacheImplementation(); $cacheItem = $myOldCache->getItem('test'); // this is now an instance of MyCacheItem // your custom converter will get used to convert it to DynamoCacheItem // if you didn't supply your own converter, the \Rikudou\DynamoDbCache\Converter\DefaultCacheItemConverter // would be used and the information about expiration date would be lost $cache->save($cacheItem);
Encoders
By default the values are serialized using php serializer. If you want to share the cache with apps
in other languages (or different php app that doesn't have the same classes), you can either use the
\Rikudou\DynamoDbCache\Encoder\JsonItemEncoder
or write your own.
Note: The JsonItemEncoder is lossy when it comes to objects, if you need to store object information this encoder might not be for you. If you on the other hand only store scalar data and/or arrays the JsonItemEncoder is enough.
Example using JsonItemEncoder
<?php use Rikudou\DynamoDbCache\DynamoDbCache; use Rikudou\DynamoDbCache\DynamoDbCacheBuilder; use AsyncAws\DynamoDb\DynamoDbClient; use Rikudou\DynamoDbCache\Encoder\JsonItemEncoder; $encoder = new JsonItemEncoder(); // with default flags and depth $encoder = new JsonItemEncoder(JSON_PRETTY_PRINT, JSON_THROW_ON_ERROR, 100); // with custom encode and decode flags and depth $cache = DynamoDbCacheBuilder::create('myTable', new DynamoDbClient([])) ->withEncoder($encoder) ->build();
Your values will now be saved json encoded in DynamoDB.
Writing your own encoder is easy, you just need to implement the
\Rikudou\DynamoDbCache\Encoder\CacheItemEncoderInterface
interface:
<?php use Rikudou\DynamoDbCache\Encoder\CacheItemEncoderInterface; class MyEncoder implements CacheItemEncoderInterface { /** * @param mixed $input * @return string */ public function encode($input) : string { // TODO: Implement encode() method. } /** * @param string $input * @return mixed */ public function decode(string $input) { // TODO: Implement decode() method. } }