talleu/php-redis-om

A PHP object mapper library for Redis

Maintainers

Package info

github.com/clementtalleu/php-redis-om

pkg:composer/talleu/php-redis-om

Statistics

Installs: 13 385

Dependents: 1

Suggesters: 0

Stars: 199

Open Issues: 12

v1.1.2 2026-05-07 08:19 UTC

README

Build Status PHPStan Packagist Version GitHub codecov.io Code Coverage

php-redis-om ๐Ÿ—„๏ธ

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.

Features ๐Ÿ› ๏ธ

  • 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)

Requirements โš™๏ธ

  • 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

Supported types โœ…

  • 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

Installation ๐Ÿ“

Install the library via Composer:

composer require talleu/php-redis-om

Depending on your configuration, use phpredis or Predis

Symfony bundle ๐ŸŽต

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 ! ๐Ÿš€

API Platform support ๐Ÿ•ท๏ธ

For API Platform users, a basic implementation is provided here: API Platfom X Redis

Basic Usage ๐ŸŽฏ

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);

Enum Support ๐Ÿท๏ธ

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']);

Range Queries ๐Ÿ”ข

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'])].

Pagination ๐Ÿ“„

$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) {
    // ...
}

Partial Updates (Merge) โšก

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().

Batch Reads (Pipeline) ๐Ÿš€

Load multiple objects by ID in a single Redis pipeline call:

$users = $repository->findMultiple([1, 2, 3, 4, 5]);

GEO Queries ๐ŸŒ

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');

Unique Constraints ๐Ÿ”’

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)
}

Bulk Operations โšก

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.

Streaming Large Collections ๐ŸŒŠ

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
}

Advanced documentation ๐Ÿ“š