doctrine/key-value-store
Doctrine Key Value Store provides a lightweight Doctrine-style mapper for NoSQL key-value backends. Use simple @Entity/@Id annotations, schema-less values mapped to objects, and a stripped-down object manager with events. Drivers include Redis, DynamoDB, MongoDB, CouchDB and more.
Installation
composer require doctrine/key-value-store
Define a Key-Value Entity
Annotate a class with @KeyValue\Entity and mark at least one property as @KeyValue\Id.
use Doctrine\KeyValueStore\Mapping\Annotations as KeyValue;
/**
* @KeyValue\Entity(storageName="user_sessions")
*/
class UserSession
{
/** @KeyValue\Id */
private $sessionId;
private $userId;
private $expiresAt;
}
Configure the EntityManager
Choose a storage backend (e.g., Redis, Doctrine Cache, or DBAL) and initialize the EntityManager.
use Doctrine\KeyValueStore\EntityManager;
use Doctrine\KeyValueStore\Configuration;
use Doctrine\KeyValueStore\Storage\RedisStorage;
use Doctrine\KeyValueStore\Mapping\AnnotationDriver;
use Doctrine\Common\Annotations\AnnotationReader;
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$storage = new RedisStorage($redis);
$reader = new AnnotationReader();
$metadataDriver = new AnnotationDriver($reader);
$config = new Configuration();
$config->setMetadataDriverImpl($metadataDriver);
$entityManager = new EntityManager($storage, $config);
First Use Case: CRUD Operations
// Create
$session = new UserSession();
$session->setSessionId('abc123');
$session->setUserId(1);
$session->setExpiresAt(new \DateTime('+1 hour'));
$entityManager->persist($session);
$entityManager->flush();
// Read
$session = $entityManager->find('UserSession', ['sessionId' => 'abc123']);
// Update
$session->setExpiresAt(new \DateTime('+2 hours'));
$entityManager->flush();
// Delete
$entityManager->remove($session);
$entityManager->flush();
Schema-less Data Handling Use the package to store unstructured data (e.g., caching, session storage, or temporary objects) without defining a rigid schema.
/**
* @KeyValue\Entity(storageName="temp_data")
*/
class TempData
{
/** @KeyValue\Id */
private $key;
private $data; // Can be any serializable value (array, object, etc.)
}
Multi-Key Entities
Leverage composite keys for hierarchical or relational data (e.g., user_id + feature_id for feature flags).
/**
* @KeyValue\Entity(storageName="feature_flags")
*/
class FeatureFlag
{
/** @KeyValue\Id */
private $userId;
/** @KeyValue\Id */
private $featureId;
private $isEnabled;
}
Event-Driven Extensions
Use the KeyValueStoreEventListener to hook into lifecycle events (e.g., log changes or trigger actions on postPersist).
use Doctrine\KeyValueStore\Event\KeyValueStoreEventArgs;
use Doctrine\KeyValueStore\Event\KeyValueStoreEvents;
$entityManager->getEventManager()->addEventListener(
KeyValueStoreEvents::postPersist,
function (KeyValueStoreEventArgs $args) {
// Custom logic (e.g., analytics, notifications)
}
);
Hybrid Storage with Doctrine ORM
Embed key-value entities within ORM entities using @KeyValue\Embedded (if supported by the backend).
use Doctrine\ORM\Mapping as ORM;
use Doctrine\KeyValueStore\Mapping\Annotations as KeyValue;
/**
* @ORM\Entity
*/
class UserProfile
{
/** @ORM\Id */
private $id;
/** @KeyValue\Embedded */
private $preferences; // Instance of a KeyValue entity
}
Backend-Specific Optimizations
$storage = new RedisStorage($redis, 'prefix_');
$storage = new DBALStorage($conn, 'kv_store', 'id', 'data');
$storage = new AzureSdkTableStorage($tableService, 'Entity');
Bulk Operations
Use EntityManager::findBy() for querying multiple entities at once.
$flags = $entityManager->findBy('FeatureFlag', ['userId' => 1]);
Metadata Caching
Improve performance by caching metadata (e.g., with Doctrine\Common\Cache\ApcuCache).
$config->setMetadataCache(new ApcuCache());
Testing
Mock the Storage interface for unit tests:
$mockStorage = $this->createMock(Storage::class);
$entityManager = new EntityManager($mockStorage, $config);
Serialization Limits
Composite Key Quirks
$key = json_encode(['campaign' => '1234', 'recipient' => 'user@example.com']);
Transaction Isolation
flush() judiciously to avoid partial writes.Metadata Driver Conflicts
Deprecated Backends
Enable Logging
Configure the EntityManager to log operations:
$entityManager->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
Inspect Raw Storage
For debugging, bypass the EntityManager and interact directly with the storage (e.g., Redis CLI or DBAL queries).
Validate Annotations
Use the MetadataFactory to check if your entities are correctly mapped:
$metadata = $entityManager->getMetadataFactory()->getMetadataFor('Response');
Handle Serialization Errors
Ensure all properties are serializable. Use __serialize()/__unserialize() for custom logic:
class UserSession implements \Serializable
{
public function serialize()
{
return serialize([
'sessionId' => $this->sessionId,
'userId' => $this->userId,
'expiresAt' => $this->expiresAt->getTimestamp(),
]);
}
public function unserialize($data)
{
$data = unserialize($data);
$this->sessionId = $data['sessionId'];
$this->userId = $data['userId'];
$this->expiresAt = new \DateTime();
$this->expiresAt->setTimestamp($data['expiresAt']);
}
}
Custom Storage Backend
Implement the Doctrine\KeyValueStore\Storage\Storage interface for unsupported backends (e.g., Memcached, Etcd).
class CustomStorage implements Storage
{
public function find($className, $key)
{
// Custom logic to fetch data
}
public function persist($entity)
{
// Custom logic to save data
}
// Implement other required methods...
}
Event Subscribers
Extend functionality with subscribers for events like preFlush or postRemove.
$eventManager->addEventSubscriber(new class implements EventSubscriber {
public function getSubscribedEvents()
{
return [
KeyValueStoreEvents::preFlush => 'onPreFlush',
];
}
public function
How can I help you explore Laravel packages today?