Search by

christianjbrown / key-value-store

christianjbrown

A thin, strongly-typed PHP 8.5+ library of interchangeable key-value store implementations (database, Google Secret Manager, Google Firestore, in-memory) behind a single mockable interface.

Package info

github.com/christianjbrown/key-value-store-php

pkg:composer/christianjbrown/key-value-store

Statistics

Installs: 100

Dependents: 4

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-09-28 11:33 UTC

This package is auto-updated.

Last update: 2026-09-28 13:21:35 UTC


README

CI Coverage Packagist License PHP

A small, strongly-typed PHP library of interchangeable key-value stores. Every store hides behind one tiny contract — KeyValueStoreInterface — so you can read, write, and update a single ?string value (with an optional ?int TTL) without caring where it actually lives. It's built for small pieces of state such as configuration flags, cursors, or OAuth refresh tokens.

Four implementations ship today:

  • Database (DatabaseKeyValueStore) — persists via Doctrine ORM to any database it supports (MySQL/MariaDB, PostgreSQL, SQLite, SQL Server, …), keyed by a string id.
  • Google Secret Manager (GoogleSecretKeyValueStore) — reads/writes a Secret Manager secret.
  • Google Firestore (FirestoreKeyValueStore) — reads/writes a single Firestore document; serverless and connectionless, with TTL support via an expiresAt field.
  • In-memory (MemoryKeyValueStore) — a per-process value, handy for tests and defaults.

Because they share one interface, calling code can accept a KeyValueStoreInterface and stay oblivious to the backing store.

✔️ Prerequisites

💡 If you're on MacOS and have Homebrew, PHP and Composer will install with brew install composer.

🏗️ Installation

For your composer-enabled project:

composer require christianjbrown/key-value-store

💻 Usage

Every store exposes the same three methods:

public function getTtl(): ?int;
public function getValue(): ?string;
public function setValue(?string $value, ?int $ttl = null): self;

💾 Database key-value store

Persists each key to a row in a database table through Doctrine ORM — so it works with any platform Doctrine DBAL supports (MySQL/MariaDB, PostgreSQL, SQLite, SQL Server, …). First, define a concrete entity by extending the provided mapped superclass and giving it a table:

use ChristianBrown\KeyValueStore\AbstractDatabaseKeyValueStoreEntity;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'key_value_store')]
class KeyValueStoreEntity extends AbstractDatabaseKeyValueStoreEntity
{
}

Then construct a store with your Doctrine EntityManager, the entity class name, and the key:

use ChristianBrown\KeyValueStore\DatabaseKeyValueStore;

$store = new DatabaseKeyValueStore($entityManager, KeyValueStoreEntity::class, 'refresh-token');

$store->setValue('a-secret-token', 3600); // value + optional TTL (seconds)

$value = $store->getValue(); // 'a-secret-token', or null if the key has never been set
$ttl   = $store->getTtl();   // 3600, or null

Passing an entity class that does not extend AbstractDatabaseKeyValueStoreEntity (i.e. does not implement DatabaseKeyValueStoreEntityInterface) throws InvalidArgumentException.

🔒 Google Secret Manager key-value store

Reads and writes a Google Secret Manager secret. The quickest way to build one is the static create() factory, which constructs a real client from the GOOGLE_APPLICATION_CREDENTIALS environment variable:

use ChristianBrown\KeyValueStore\GoogleSecretKeyValueStore;
use ChristianBrown\KeyValueStore\GoogleSecretKeyValueStoreExceptionInterface;

$store = GoogleSecretKeyValueStore::create('projects/my-project/secrets/my-secret');

try {
    $value = $store->getValue();      // the latest secret version's value, or null
    $store->setValue('new-value');    // adds a new secret version
} catch (GoogleSecretKeyValueStoreExceptionInterface $e) {
    // the secret could not be read or written
    print $e->getMessage();
}

You can also inject a pre-built Google\Cloud\SecretManager\V1\SecretManagerServiceClient directly via the constructor (useful for tests):

$store = new GoogleSecretKeyValueStore($client, 'projects/my-project/secrets/my-secret');

⚠️ Secret Manager has no notion of a TTL, so getTtl() — and any setValue() call that supplies a non-null $ttl — throws a RuntimeException.

🔥 Google Firestore key-value store

Reads and writes a single Google Firestore document. It is serverless and connectionless — no VPC connector or database connection to manage. The value and an integer expiresAt unix timestamp are stored as two fields on the document. The static create() factory resolves the document from a FirestoreClient, a collection name, and a document id:

Optional dependency. google/cloud-firestore is not a hard requirement of this library (it pulls in ext-grpc), so it is only suggested — install it yourself if you use this store: composer require google/cloud-firestore (and enable ext-grpc). The other stores are unaffected.

use ChristianBrown\KeyValueStore\FirestoreKeyValueStore;
use Google\Cloud\Firestore\FirestoreClient;

$firestoreClient = new FirestoreClient();

$store = FirestoreKeyValueStore::create($firestoreClient, 'kv', 'my-key');

$store->setValue('a-secret-token', 3600); // value + optional TTL (seconds)

$value = $store->getValue(); // 'a-secret-token', or null if unset or expired
$ttl   = $store->getTtl();   // remaining seconds, or null when no TTL was set

getValue() returns null when the document does not exist or its expiresAt has passed; getTtl() returns the remaining seconds (expiresAt - time()), or null when no expiry is set. You can also inject a pre-built Google\Cloud\Firestore\DocumentReference directly via the constructor (useful for tests):

$store = new FirestoreKeyValueStore($documentReference);

⚡ In-memory key-value store

A per-process value that lives only for the current request. No configuration required:

use ChristianBrown\KeyValueStore\MemoryKeyValueStore;

$store = new MemoryKeyValueStore();
$store->setValue('hello', 60);

$store->getValue(); // 'hello'
$store->getTtl();   // 60

🚨 Error handling

GoogleSecretKeyValueStore normalizes Secret Manager access failures into a single library exception that implements ChristianBrown\KeyValueStore\GoogleSecretKeyValueStoreExceptionInterface (which extends Throwable), so one catch covers both read and write failures:

use ChristianBrown\KeyValueStore\GoogleSecretKeyValueStoreExceptionInterface;

try {
    $value = $store->getValue();
} catch (GoogleSecretKeyValueStoreExceptionInterface $e) {
    print $e->getMessage();
}

The database store throws InvalidArgumentException if constructed with an entity class that does not implement DatabaseKeyValueStoreEntityInterface.

📄 License

Released under the MIT License.