Redis Integration

30 minutes INTERMEDIATE

Master GEMVC's integrated Redis support. Learn how to use RedisManager for caching, data storage, pub/sub messaging, and performance optimization in your API layer.

What You'll Learn

Redis Setup

Install and configure Redis during project initialization

RedisManager API

Learn RedisManager methods and operations

API Layer Caching

Use Redis at the API level for best performance

Video Coming Soon...

What is GEMVC Redis Integration?

GEMVC comes with integrated Redis support through the RedisManager class. Redis is a powerful in-memory data structure store that can be used for caching, session storage, pub/sub messaging, and performance optimization.

🎯 Best Practice:

Use Redis at the API level (in /app/api/ folder classes). This is the recommended approach for caching API responses and optimizing performance.

  • Singleton Pattern - RedisManager uses singleton pattern for connection management
  • Auto-Connection - Automatically connects when needed
  • Configuration via .env - All settings configured through environment variables
  • Full Redis API - Supports all Redis data structures and operations
  • JsonResponse Caching - Special methods for caching API responses
1

Installation & Setup

During Project Initialization

When you run gemvc init, you'll be asked if you want to install Redis support. Make sure to choose "Yes" if you plan to use Redis!

⚠️ Important:

If you want to use Redis in your GEMVC project, don't forget to choose Redis during the gemvc init CLI command! This will install the necessary Redis PHP extension and configure your project.

Terminal
# Initialize project with Redis
gemvc init

# When prompted:
# Install Redis support? (y/n): y
# This will:
# - Install Redis PHP extension
# - Add Redis configuration to .env
# - Set up RedisManager

What Gets Installed:

  • ✓ Redis PHP extension (phpredis)
  • ✓ Redis configuration in .env file
  • ✓ RedisManager class ready to use
2

Configuration

Environment Variables

Configure Redis connection settings in your .env file:

.env - Redis Configuration
# Redis Configuration
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DATABASE=0
REDIS_PREFIX=gemvc:
REDIS_PERSISTENT=false
REDIS_TIMEOUT=0.0
REDIS_READ_TIMEOUT=0.0

Configuration Options:

  • REDIS_HOST - Redis server host (default: 127.0.0.1)
  • REDIS_PORT - Redis server port (default: 6379)
  • REDIS_PASSWORD - Redis password (optional)
  • REDIS_DATABASE - Redis database number (default: 0)
  • REDIS_PREFIX - Key prefix for all keys (default: gemvc:)
  • REDIS_PERSISTENT - Use persistent connection (default: false)
  • REDIS_TIMEOUT - Connection timeout in seconds (default: 0.0)
  • REDIS_READ_TIMEOUT - Read timeout in seconds (default: 0.0)
3

Getting Started with RedisManager

Basic Usage

RedisManager uses the singleton pattern. Get an instance and start using Redis:

Basic RedisManager Usage
<?php
namespace App\Api;

use Gemvc\Core\ApiService;
use Gemvc\Core\RedisManager;
use Gemvc\Http\Request;
use Gemvc\Http\JsonResponse;

class Product extends ApiService
{
    public function list(): JsonResponse
    {
        // Get RedisManager instance (singleton)
        $redis = RedisManager::getInstance();
        
        // Auto-connects if not already connected
        // No need to call connect() manually!
        
        // Use Redis operations
        $redis->set('key', 'value', 3600);  // Set with TTL
        $value = $redis->get('key');        // Get value
        
        // ...
    }
}

Key Points:

  • getInstance() returns the singleton instance
  • ✓ Auto-connects when you call any method (no manual connect() needed)
  • ✓ Connection is reused across requests (efficient!)
  • ✓ All methods return safe defaults (null, false, empty array) if Redis is unavailable
4

Connection Management

Manual Connection Control

While RedisManager auto-connects, you can also manually control the connection:

Connection Management
<?php
use Gemvc\Core\RedisManager;

$redis = RedisManager::getInstance();

// Manually connect (optional - auto-connects anyway)
$connected = $redis->connect();
if (!$connected) {
    $error = $redis->getError();
    // Handle connection error
}

// Check connection status
if ($redis->isConnected()) {
    // Redis is connected
}

// Get raw Redis instance (advanced)
$redisInstance = $redis->getRedis();

// Disconnect (usually not needed)
$redis->disconnect();

Connection Features:

  • Persistent Connections - Configure via REDIS_PERSISTENT
  • Auto-Reconnect - Automatically reconnects if connection is lost
  • Error Handling - Use getError() to check for errors
  • Prefix Support - All keys automatically prefixed with REDIS_PREFIX
5

Basic Key-Value Operations

Set, Get, Delete

Basic Redis operations for storing and retrieving data:

Basic Operations
<?php
use Gemvc\Core\RedisManager;

$redis = RedisManager::getInstance();

// Set value with TTL (expires in 1 hour)
$redis->set('user:1', 'John Doe', 3600);

// Set value without expiration
$redis->set('config:app_name', 'My App');

// Get value
$user = $redis->get('user:1');
$appName = $redis->get('config:app_name');

// Check if key exists
if ($redis->exists('user:1')) {
    // Key exists
}

// Get TTL (time to live)
$ttl = $redis->ttl('user:1');  // Returns seconds until expiration

// Delete key
$redis->delete('user:1');

// Delete multiple keys
$redis->delete('key1', 'key2', 'key3');

// Flush entire database (use with caution!)
$redis->flush();

TTL (Time To Live):

Use TTL to automatically expire keys. This is perfect for caching! Set a TTL in seconds, and Redis will automatically delete the key when it expires.

6

API Layer Caching (Best Practice)

Cache API Responses

Best Practice: Use Redis at the API level to cache API responses. This dramatically improves performance by avoiding database queries and business logic execution.

🎯 Recommended Approach:

Always use Redis caching in /app/api/ folder classes. This is the best practice for performance optimization.

app/api/Product.php - Caching Example
<?php
namespace App\Api;

use App\Controller\ProductController;
use Gemvc\Core\ApiService;
use Gemvc\Core\RedisManager;
use Gemvc\Http\Request;
use Gemvc\Http\JsonResponse;

class Product extends ApiService
{
    public function list(): JsonResponse
    {
        // Get RedisManager instance
        $redis = RedisManager::getInstance();
        
        // Create cache key from request URL
        $cacheKey = 'product:list:' . md5($this->request->requestedUrl . serialize($this->request->get));
        
        // Try to get cached response
        $cachedResponse = $redis->getJsonResponse($cacheKey);
        
        if ($cachedResponse !== null) {
            // Cache hit - return cached response
            return $cachedResponse;
        }
        
        // Cache miss - execute business logic
        $response = (new ProductController($this->request))->list();
        
        // Cache the response for 5 minutes (300 seconds)
        $redis->setJsonResponse($cacheKey, $response, 300);
        
        return $response;
    }
    
    public function read(): JsonResponse
    {
        $id = $this->request->intValueGet('id');
        if (!$id) {
            return $this->request->returnResponse();
        }
        
        $redis = RedisManager::getInstance();
        $cacheKey = 'product:read:' . $id;
        
        // Try cache first
        $cachedResponse = $redis->getJsonResponse($cacheKey);
        if ($cachedResponse !== null) {
            return $cachedResponse;
        }
        
        // Get from database
        $this->request->post['id'] = $id;
        $response = (new ProductController($this->request))->read();
        
        // Cache for 10 minutes
        $redis->setJsonResponse($cacheKey, $response, 600);
        
        return $response;
    }
    
    public function create(): JsonResponse
    {
        // ... validation ...
        
        $response = (new ProductController($this->request))->create();
        
        // Invalidate cache after create
        $redis = RedisManager::getInstance();
        $redis->delete('product:list:*');  // Clear list cache
        
        return $response;
    }
}

Key Benefits:

  • Faster Responses - Cached responses return instantly
  • Reduced Database Load - Fewer queries to database
  • Better Performance - Especially for frequently accessed data
  • Automatic Expiration - Cache expires automatically with TTL
  • Easy Invalidation - Delete cache keys when data changes
7

Hash Operations

Storing Objects as Hashes

Redis hashes are perfect for storing objects with multiple fields:

Hash Operations
<?php
use Gemvc\Core\RedisManager;

$redis = RedisManager::getInstance();

// Store user data as hash
$redis->hSet('user:1', 'name', 'John Doe');
$redis->hSet('user:1', 'email', 'john@example.com');
$redis->hSet('user:1', 'age', '30');

// Get single field
$name = $redis->hGet('user:1', 'name');

// Get all fields
$userData = $redis->hGetAll('user:1');
// Returns: ['name' => 'John Doe', 'email' => 'john@example.com', 'age' => '30']

// Use in API layer
class User extends ApiService
{
    public function read(): JsonResponse
    {
        $id = $this->request->intValueGet('id');
        
        $redis = RedisManager::getInstance();
        $cacheKey = 'user:' . $id;
        
        // Try to get from hash cache
        $userData = $redis->hGetAll($cacheKey);
        
        if (empty($userData)) {
            // Cache miss - get from database
            $this->request->post['id'] = $id;
            $response = (new UserController($this->request))->read();
            
            // Store in hash cache
            if ($response->data) {
                $redis->hSet($cacheKey, 'id', $response->data->id);
                $redis->hSet($cacheKey, 'name', $response->data->name);
                $redis->hSet($cacheKey, 'email', $response->data->email);
                // Set TTL on the hash key
                $redis->getRedis()?->expire($cacheKey, 3600);
            }
            
            return $response;
        }
        
        // Return cached data
        return Response::success($userData, 1, 'User retrieved from cache');
    }
}
8

List Operations

Queues and Lists

Redis lists are perfect for queues, logs, and ordered data:

List Operations
<?php
use Gemvc\Core\RedisManager;

$redis = RedisManager::getInstance();

// Push to left (beginning of list)
$redis->lPush('queue', 'task1');
$redis->lPush('queue', 'task2');

// Push to right (end of list)
$redis->rPush('queue', 'task3');

// Pop from left (FIFO - First In First Out)
$task = $redis->lPop('queue');  // Returns 'task2'

// Pop from right (LIFO - Last In First Out)
$task = $redis->rPop('queue');  // Returns 'task3'

// Use for activity logs
class Activity extends ApiService
{
    public function logActivity(string $action): void
    {
        $redis = RedisManager::getInstance();
        $logEntry = json_encode([
            'action' => $action,
            'timestamp' => time(),
            'user_id' => $this->request->auth() ? $this->request->auth()->id : null
        ]);
        
        // Add to activity log (keep last 1000 entries)
        $redis->lPush('activity:log', $logEntry);
        $redis->getRedis()?->lTrim('activity:log', 0, 999);
    }
}
9

Set Operations

Unique Collections

Redis sets store unique values - perfect for tags, categories, and unique collections:

Set Operations
<?php
use Gemvc\Core\RedisManager;

$redis = RedisManager::getInstance();

// Add to set (duplicates are ignored)
$redis->sAdd('tags', 'php');
$redis->sAdd('tags', 'redis');
$redis->sAdd('tags', 'php');  // Ignored - already exists

// Get all members
$tags = $redis->sMembers('tags');
// Returns: ['php', 'redis']

// Check membership
if ($redis->sIsMember('tags', 'php')) {
    // 'php' is in the set
}

// Use for tracking unique visitors
class Analytics extends ApiService
{
    public function trackVisit(int $userId): void
    {
        $redis = RedisManager::getInstance();
        $date = date('Y-m-d');
        
        // Add to daily unique visitors set
        $redis->sAdd('visitors:' . $date, $userId);
        
        // Get unique visitor count for today
        $uniqueVisitors = count($redis->sMembers('visitors:' . $date));
    }
}
10

Sorted Sets

Ordered Collections with Scores

Sorted sets maintain order by score - perfect for leaderboards, rankings, and time-series data:

Sorted Set Operations
<?php
use Gemvc\Core\RedisManager;

$redis = RedisManager::getInstance();

// Add with score
$redis->zAdd('leaderboard', 100, 'player1');
$redis->zAdd('leaderboard', 200, 'player2');
$redis->zAdd('leaderboard', 150, 'player3');

// Get range (sorted by score)
$topPlayers = $redis->zRange('leaderboard', 0, -1, true);
// Returns: ['player1' => 100, 'player3' => 150, 'player2' => 200]

// Get top 10
$top10 = $redis->zRange('leaderboard', 0, 9, true);

// Use for product rankings
class Product extends ApiService
{
    public function updateRanking(int $productId, float $score): void
    {
        $redis = RedisManager::getInstance();
        
        // Update product ranking
        $redis->zAdd('product:rankings', $score, $productId);
        
        // Get top 10 products
        $topProducts = $redis->zRange('product:rankings', 0, 9, true);
    }
}
11

Pub/Sub Messaging

Publish/Subscribe Pattern

Redis pub/sub allows real-time messaging between different parts of your application:

Pub/Sub Operations
<?php
use Gemvc\Core\RedisManager;

$redis = RedisManager::getInstance();

// Publish message to channel
$redis->publish('news', 'Breaking news: GEMVC released!');
$redis->publish('notifications', json_encode(['user_id' => 1, 'message' => 'New order']));

// Subscribe to channels (usually in background worker)
$redis->subscribe(['news', 'notifications'], function($redis, $channel, $message) {
    echo "Channel: $channel, Message: $message\n";
    
    if ($channel === 'notifications') {
        $data = json_decode($message, true);
        // Process notification
    }
});

// Use for cache invalidation
class Product extends ApiService
{
    public function create(): JsonResponse
    {
        // ... create product ...
        
        // Publish cache invalidation message
        $redis = RedisManager::getInstance();
        $redis->publish('cache:invalidate', 'product:list');
        
        return $response;
    }
}

Use Cases:

  • Cache Invalidation - Notify all instances to clear cache
  • Real-time Updates - Push updates to connected clients
  • Event Broadcasting - Broadcast events across services
  • Background Jobs - Trigger background processing
12

Pipeline & Transactions

Batch Operations

Use pipelines for batch operations (faster) or transactions for atomic operations:

Pipeline & Transactions
<?php
use Gemvc\Core\RedisManager;

$redis = RedisManager::getInstance();

// Pipeline (faster - no atomicity guarantee)
$pipe = $redis->pipeline();
$pipe->set('key1', 'value1');
$pipe->set('key2', 'value2');
$pipe->set('key3', 'value3');
$pipe->execute();  // All commands sent at once

// Transaction (atomic - all or nothing)
$tx = $redis->transaction();
$tx->set('key1', 'value1');
$tx->set('key2', 'value2');
$tx->set('key3', 'value3');
$tx->exec();  // All commands executed atomically

// Use for batch cache updates
class Product extends ApiService
{
    public function updateMultiple(array $products): void
    {
        $redis = RedisManager::getInstance();
        $pipe = $redis->pipeline();
        
        foreach ($products as $product) {
            $cacheKey = 'product:' . $product['id'];
            $pipe->set($cacheKey, json_encode($product), 3600);
        }
        
        $pipe->execute();  // All updates sent at once
    }
}

Pipeline vs Transaction:

  • Pipeline - Faster, no atomicity guarantee, use for batch operations
  • Transaction - Slower, atomic (all or nothing), use when consistency is critical

Best Practices

🎯 Use Redis at API Level

Always use Redis caching in /app/api/ folder classes. This is the recommended best practice for performance optimization.

💡 Cache Key Strategy

Use descriptive, consistent cache keys:

Good Cache Keys
<?php
// ✅ Good - Descriptive and consistent
'product:list:' . md5($url . serialize($params))
'product:read:' . $id
'user:profile:' . $userId

// ❌ Bad - Unclear and inconsistent
'p1'
'data'
'cache_' . rand()

💡 Set Appropriate TTL

Always set TTL (Time To Live) for cache keys. This prevents stale data and automatically cleans up old cache entries.

💡 Invalidate Cache on Updates

When data changes (create, update, delete), invalidate related cache keys to prevent serving stale data.

💡 Handle Cache Misses Gracefully

Always handle cache misses by falling back to database queries. Redis should enhance performance, not break functionality.

Important Notes

  • Installation: Don't forget to choose Redis during gemvc init CLI command! This installs the Redis PHP extension and configures your project.
  • API Level: Best practice is to use Redis at the API level (/app/api/ folder). This provides the best performance optimization.
  • Auto-Connection: RedisManager auto-connects when you call any method. No need to manually call connect().
  • Safe Defaults: All methods return safe defaults (null, false, empty array) if Redis is unavailable, so your application won't break.
  • Key Prefix: All keys are automatically prefixed with REDIS_PREFIX (default: gemvc:) to avoid conflicts.

⚡ Redis Integration Complete!

Excellent! You've learned how to use GEMVC's integrated Redis support. Remember: use Redis at the API level for best performance, and don't forget to choose Redis during gemvc init!