A PHP object mapper for Redis.
An Object Mapper for Redis®, designed to providing an intuitive and familiar interface for PHP developers to interact with Redis.
- Doctrine-like methods and architecture
- Symfony bundle integration
- Easy integration with existing PHP applications
- High performance and scalability with Redis®
- Support for Redis JSON module
- Automatic schema generation
- Search and query capabilities with range filters
- Auto-expiration of your objects
- PHP enum support (backed enums)
- Identity map and dirty tracking with partial updates
- Atomic transactions (MULTI/EXEC)
- Unique constraints (single-field and composite)
- Pagination with total count
- Memory-efficient streaming of large collections
- Bulk delete and update without loading objects into memory
- GEO queries (radius search)
- Pipeline batch reads
- API Platform support (beta)
- PHP 8.2 or higher
- Redis 4.0 or higher
- Redisearch module (available by default with Redis >8 or in redis-stack distribution) (installation)
- php-redis extension OR Predis library
- Redis JSON module (optional, include in redis-stack)
- Composer
- scalar (string, int, float, bool, double)
- PHP backed enums (string and int)
- timestamp
- json
- null
- DateTimeImmutable
- DateTime
- array and nested arrays
- object and nested objects
- stdClass
Install the library via Composer:
composer require talleu/php-redis-omDepending on your configuration, use phpredis or Predis
In a Symfony application, you may need to add this line to config/bundles.php
Talleu\RedisOm\Bundle\TalleuRedisOmBundle::class => ['all' => true],And that's it, your installation is complete ! 🚀
For API Platform users, a basic implementation is provided here: API Platfom X Redis
Add the RedisOm attribute to your class to map it to a Redis schema:
<?php
use Talleu\RedisOm\Om\Mapping as RedisOm;
#[RedisOm\Entity]
class User
{
#[RedisOm\Id]
#[RedisOm\Property]
public int $id;
#[RedisOm\Property(index:true)]
public string $name;
#[RedisOm\Property]
public \DateTimeImmutable $createdAt;
}After add the RedisOm attribute to your class,
you have to run the following command to create the Redis schema for your classes (default path is ./src):
For Symfony users:
bin/console redis-om:migrate For others PHP applications:
vendor/bin/redisMigration <YOUR DIRECTORY PATH>Then you can use the ObjectManager to persist your objects from Redis ! 💪
For Symfony users, just inject the RedisObjectManagerInterface in the constructor:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Talleu\RedisOm\Om\RedisObjectManagerInterface;
use App\Entity\Book;
class MySymfonyController extends AbstractController
{
public function __construct(private RedisObjectManagerInterface $redisObjectManager)
{}
#[Route('/', name: 'app_home')]
public function index(): Response
{
$book = new Book();
$book->name = 'Martin Eden';
$this->redisObjectManager->persist($book);
$this->redisObjectManager->flush();
//..
}
}For others PHP applications:
<?php
use Talleu\RedisOm\Om\RedisObjectManager;
$user = new User()
$user->id = 1;
$user->name = 'John Doe';
// Persist the object in redis
$objectManager = new RedisObjectManager();
$objectManager->persist($user);
$objectManager->flush();🥳 Congratulations, your PHP object is now registered in Redis !
You can now retrieve your user wherever you like using the repository provided by the Object Manager (or the object manager directly):
// Retrieve the object from redis
$user = $this->redisObjectManager->find(User::class, 1);
$user = $this->redisObjectManager->getRepository(User::class)->find(1);
$user = $this->redisObjectManager->getRepository(User::class)->findOneBy(['name' => 'John Doe']);
// Retrieve a collection of objects
$users = $this->redisObjectManager->getRepository(User::class)->findAll();
$users = $this->redisObjectManager->getRepository(User::class)->findBy(['name' => 'John Doe'], ['createdAt' => 'DESC'], 10);PHP backed enums are natively supported:
enum Status: string
{
case ACTIVE = 'active';
case INACTIVE = 'inactive';
}
#[RedisOm\Entity]
class Task
{
#[RedisOm\Id]
#[RedisOm\Property]
public int $id;
#[RedisOm\Property(index: true)]
public Status $status;
}Search by enum value:
$activeTasks = $repository->findBy(['status' => 'active']);Use MongoDB-style operators for numeric range searches:
// Age between 18 and 65
$users = $repository->findBy(['age' => ['$gte' => 18, '$lte' => 65]]);
// Price greater than 100
$products = $repository->findBy(['price' => ['$gt' => 100]]);
// Score less than 50
$results = $repository->findBy(['score' => ['$lt' => 50]]);
// Combine with exact match
$results = $repository->findBy(['name' => 'John', 'age' => ['$gte' => 18]]);Supported operators: $gte (>=), $gt (>), $lte (<=), $lt (<).
Note: Range queries work automatically with HASH format (NUMERIC index is auto-generated for int/float). For JSON format, you must explicitly declare a NUMERIC index:
#[Property(index: ['age' => 'NUMERIC'])].
$paginator = $repository->paginate(
criteria: ['status' => 'active'],
page: 2,
itemsPerPage: 20,
orderBy: ['createdAt' => 'DESC']
);
$paginator->getItems(); // Current page items
$paginator->getTotalItems(); // Total matching count
$paginator->getTotalPages(); // Total number of pages
$paginator->getCurrentPage(); // Current page number
$paginator->hasNextPage(); // bool
$paginator->hasPreviousPage(); // bool
// Iterable
foreach ($paginator as $item) {
// ...
}Instead of re-persisting the entire object, use merge() to only update changed fields:
$user = $objectManager->find(User::class, 1);
$user->name = 'New Name'; // Only this field changed
$objectManager->merge($user); // Detects change, updates only 'name'
$objectManager->flush();For new objects (not loaded via find()), merge() falls back to a full persist().
Load multiple objects by ID in a single Redis pipeline call:
$users = $repository->findMultiple([1, 2, 3, 4, 5]);Search objects within a geographic radius (requires a GEO-indexed property):
#[RedisOm\Property(index: ['location' => 'GEO'])]
public string $location; // Format: "longitude,latitude"
$nearby = $repository->findByGeoRadius('location', 2.3522, 48.8566, 10, 'km');Enforce uniqueness on one or more fields using #[Unique].
Single field:
#[RedisOm\Entity]
class User
{
#[RedisOm\Id]
#[RedisOm\Property]
public int $id;
#[RedisOm\Property(index: true)]
#[RedisOm\Unique]
public string $email;
}Composite (combination of fields must be unique):
#[RedisOm\Entity]
#[RedisOm\Unique(properties: ['username', 'tenantId'])]
class User
{
#[RedisOm\Id]
#[RedisOm\Property]
public int $id;
#[RedisOm\Property]
public string $username;
#[RedisOm\Property]
public int $tenantId;
}flush() throws UniqueConstraintViolationException on conflict. Violations within the same flush() call are detected before hitting Redis. Concurrent writes are protected via Redis WATCH/MULTI/EXEC.
use Talleu\RedisOm\Exception\UniqueConstraintViolationException;
try {
$objectManager->persist($user);
$objectManager->flush();
} catch (UniqueConstraintViolationException $e) {
// $e->getMessage() describes the conflicting field(s) and value(s)
}Delete or update large numbers of objects without loading them into PHP memory.
$repository = $objectManager->getRepository(User::class);
// Delete all inactive users — unique-constraint keys are cleaned up automatically
$deleted = $repository->bulkDelete(['status' => 'inactive']);
// Update a scalar field on many objects at once
$updated = $repository->bulkUpdate(['country' => 'FR'], ['currency' => 'EUR']);bulkUpdate() throws BulkOperationException when $changes targets a #[Unique] field. Use stream() + merge() + flush() instead for those cases.
findAll() loads everything into memory. stream() fetches objects in batches and yields them one by one, keeping memory bounded regardless of collection size.
// Via repository — full control
foreach ($repository->stream(['status' => 'active'], batchSize: 500) as $user) {
// process $user — break works normally
}
// Via object manager — identity map is cleared automatically between batches
foreach ($objectManager->stream(User::class, ['status' => 'active']) as $user) {
// process $user
}