Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Cache implementation for Key Value Store #9

Merged
merged 3 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 13 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
Anydataset NoSQL standardize the access to non-relational databases/repositories and treat them as Key/Value.
The implementation can work with:

- S3-Like Storage
- MongoDB
- Cloudflare KV
- S3
- DynamoDB

Anydataset is an agnostic data source abstraction layer in PHP. See more about Anydataset [here](https://opensource.byjg.com/php/anydataset).
Expand All @@ -30,15 +30,20 @@ See below the current implemented drivers:

| Datasource | Connection String |
|---------------------------------------------|----------------------------------------------------------|
| [MongoDB](MongoDB.md) | mongodb://username:password@hostname:port/database |
| [Cloudflare KV](CloudFlareKV.md) | kv://username:password@accountid/namespaceid |
| [S3](AwsS3KeyValue.md) | s3://accesskey:secretkey@region/bucket?params |
| [AWS DynamoDB](AwsDynamoDbKeyValue.md) | dynamodb://accesskey:secretkey@hostname/tablename?params |
| [MongoDB](docs/MongoDB.md) | mongodb://username:password@hostname:port/database |
| [S3](docs/AwsS3KeyValue.md) | s3://accesskey:secretkey@region/bucket?params |
| [Cloudflare KV](docs/CloudFlareKV.md) | kv://username:password@accountid/namespaceid |
| [AWS DynamoDB](docs/AwsDynamoDbKeyValue.md) | dynamodb://accesskey:secretkey@hostname/tablename?params |


## Examples
## Topics

Check implementation examples on [https://opensource.byjg.com/php/anydataset-nosql](https://opensource.byjg.com/php/anydataset-nosql)
- [S3-Like Storage](docs/AwsS3KeyValue.md)
- [MongoDB](docs/MongoDB.md)
- [Cloudflare KV](docs/CloudFlareKV.md)
- [AWS DynamoDB](docs/AwsDynamoDbKeyValue.md)
- [Cache Store](docs/cache.md)
- [Running Tests](docs/tests.md)

## Install

Expand All @@ -48,42 +53,6 @@ Just type:
composer require "byjg/anydataset-nosql"
```

## Running Unit tests

```bash
docker-compose up -d
export MONGODB_CONNECTION="mongodb://127.0.0.1/test"
export S3_CONNECTION="s3://aaa:12345678@us-east-1/mybucket?create=true&endpoint=http://127.0.0.1:4566"
export DYNAMODB_CONNECTION="dynamodb://accesskey:secretkey@us-east-1/tablename?endpoint=http://127.0.0.1:8000"
vendor/bin/phpunit
```


### Setup MongoDB for the unit test

Set the environment variable:

- MONGODB_CONNECTION = "mongodb://127.0.0.1/test"

### Setup AWS DynamoDb for the unit test

Set the environment variable:

- DYNAMODB_CONNECTION = "dynamodb://accesskey:secretkey@region/tablename"

### Setup AWS S3 for the unit test

Set the environment variable:

- S3_CONNECTION = "s3://accesskey:secretkey@region/bucketname"


### Cloudflare KV

Set the environment variable:

- CLOUDFLAREKV_CONNECTION = "kv://email:authkey@accountid/namespaceid"

## Dependencies

```mermaid
Expand All @@ -94,6 +63,7 @@ flowchart TD
byjg/anydataset-nosql --> byjg/anydataset-array
byjg/anydataset-nosql --> byjg/serializer
byjg/anydataset-nosql --> byjg/webrequest
byjg/anydataset-nosql --> byjg/cache-engine
byjg/anydataset-nosql --> ext-json
```

Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"php": ">=8.1 <8.4",
"ext-curl": "*",
"aws/aws-sdk-php": "3.*",
"byjg/cache-engine": "^5.0",
"byjg/anydataset": "^5.0",
"byjg/anydataset-array": "^5.0",
"byjg/serializer": "^5.0",
Expand Down
24 changes: 24 additions & 0 deletions docs/cache.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Cache Interface

The class `KeyValueCacheEngine` adds a cache layer on top of the KeyValueStore.

It allows you to cache the results locally and avoid unnecessary calls to the KeyValueStore.

This package provides a PSR-16 cache implementation for the Key-Value store.

To use as a cache store you just need to:

```php
<?php
use ByJG\AnyDataset\NoSql\Cache\KeyValueCacheEngine;
use ByJG\AnyDataset\NoSql\Factory;

// Create the KeyValueStore
Factory::registerDriver(\ByJG\AnyDataset\NoSql\AwsS3Driver::class);
$keyValueStore = Factory::getInstance('s3://...');

// Create the Cache Object
$cache = new KeyValueCacheEngine($keyValueStore);
$cache->set('key', 'value');
echo $cache->get('key');
```
19 changes: 19 additions & 0 deletions docs/tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Running Unit tests

```bash
docker-compose up -d
export MONGODB_CONNECTION="mongodb://127.0.0.1/test"
export S3_CONNECTION="s3://aaa:12345678@us-east-1/mybucket?create=true&endpoint=http://127.0.0.1:4566"
export DYNAMODB_CONNECTION="dynamodb://accesskey:secretkey@us-east-1/tablename?endpoint=http://127.0.0.1:8000"
vendor/bin/phpunit
```

## Setup the environment variables

| Variable | Description | Example |
|-------------------------|-------------------------------------|-------------------------------------------------|
| MONGODB_CONNECTION | Connection string for MongoDB | mongodb://127.0.0.1/test |
| S3_CONNECTION | Connection string for S3 | s3://accesskey:secretkey@region/bucketname |
| DYNAMODB_CONNECTION | Connection string for DynamoDB | dynamodb://accesskey:secretkey@region/tablename |
| CLOUDFLAREKV_CONNECTION | Connection string for Cloudflare KV | kv://email:authkey@accountid/namespaceid |

40 changes: 20 additions & 20 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,32 @@ To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->

<!-- see http://www.phpunit.de/wiki/Documentation -->
<phpunit bootstrap="./vendor/autoload.php"
colors="true"
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
bootstrap="./vendor/autoload.php" colors="true"
testdox="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
convertDeprecationsToExceptions="true"
stopOnFailure="false">
stopOnFailure="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">

<php>
<ini name="display_errors" value="On" />
<ini name="display_startup_errors" value="On" />
<ini name="error_reporting" value="E_ALL" />
</php>
<php>
<ini name="display_errors" value="On"/>
<ini name="display_startup_errors" value="On"/>
<ini name="error_reporting" value="E_ALL"/>
</php>

<filter>
<whitelist>
<directory>./src</directory>
</whitelist>
</filter>
<coverage>
<include>
<directory>./src</directory>
</include>
</coverage>

<testsuites>
<testsuite name="Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
</phpunit>
<testsuites>
<testsuite name="Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
</phpunit>
128 changes: 128 additions & 0 deletions src/Cache/KeyValueCacheEngine.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

namespace ByJG\AnyDataset\NoSql\Cache;

use ByJG\AnyDataset\NoSql\KeyValueInterface;
use ByJG\Cache\Exception\InvalidArgumentException;
use ByJG\Cache\Psr16\BaseCacheEngine;
use DateInterval;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

class KeyValueCacheEngine extends BaseCacheEngine
{
protected KeyValueInterface $keyValue;

protected LoggerInterface|null $logger = null;

public function __construct(KeyValueInterface $keyValue, LoggerInterface|null $logger = null)
{
$this->keyValue = $keyValue;
$this->logger = $logger;
if (is_null($logger)) {
$this->logger = new NullLogger();
}
}

/**
* Determines whether an item is present in the cache.
* NOTE: It is recommended that has() is only to be used for cache warming type purposes
* and not to be used within your live applications operations for get/set, as this method
* is subject to a race condition where your has() will return true and immediately after,
* another script can remove it making the state of your app out of date.
*
* @param string $key The cache item key.
* @return bool
* @throws InvalidArgumentException
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function has(string $key): bool
{
$key = $this->getKeyFromContainer($key);
if ($this->keyValue->has($key)) {
if ($this->keyValue->has("$key.ttl") && time() >= $this->keyValue->get("$key.ttl")) {
$this->delete($key);
return false;
}

return true;
}

return false;
}

/**
* @param string $key The object KEY
* @param mixed $default IGNORED IN MEMCACHED.
* @return mixed Description
* @throws ContainerExceptionInterface
* @throws InvalidArgumentException
* @throws NotFoundExceptionInterface
*/
public function get(string $key, mixed $default = null): mixed
{
if ($this->has($key)) {
$key = $this->getKeyFromContainer($key);
$this->logger->info("[KeyValueInterface] Get '$key' fromCache");
return unserialize($this->keyValue->get($key));
} else {
$this->logger->info("[KeyValueInterface] Not found '$key'");
return $default;
}
}

/**
* Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
*
* @param string $key The key of the item to store.
* @param mixed $value The value of the item to store, must be serializable.
* @param null|int|DateInterval $ttl Optional. The TTL value of this item. If no value is sent and
* the driver supports TTL then the library may set a default value
* for it or let the driver take care of that.
*
* @return bool True on success and false on failure.
*
* MUST be thrown if the $key string is not a legal value.
*/
public function set(string $key, mixed $value, DateInterval|int|null $ttl = null): bool
{
$key = $this->getKeyFromContainer($key);

$this->logger->info("[KeyValueInterface] Set '$key' in Cache");

$this->keyValue->put($key, serialize($value));
if (!empty($ttl)) {
$this->keyValue->put("$key.ttl", $this->addToNow($ttl));
}

return true;
}

public function clear(): bool
{
return false;
}

/**
* Unlock resource
*
* @param string $key
* @return bool
*/
public function delete(string $key): bool
{
$key = $this->getKeyFromContainer($key);

$this->keyValue->remove($key);
$this->keyValue->remove("$key.ttl");
return true;
}

public function isAvailable(): bool
{
return true;
}
}
Loading
Loading