Files
XC_VM/src/Core/Cache/RedisCache.php
T
Divarion_D 758a9cab0b style: apply K&R + tab formatting across the codebase (make cs-fix)
Mechanical, behaviour-preserving reformat produced by 'make cs-fix' under
the new build/phpcs.xml.dist ruleset: K&R braces, tab indentation, and the
other whitespace normalisations. No logic changes.
2026-09-13 14:14:23 +03:00

257 lines
5.6 KiB
PHP

<?php
namespace XcVm\Core\Cache;
use XcVm\Infrastructure\Redis\RedisManager;
/**
* \Redis Cache Driver
*
* \Redis-based cache driver implementing CacheInterface.
* Wraps the phpredis extension with serialization and TTL support.
*
* ServiceContainer Registration:
*
* $container->set('redis', function($c) {
* $settings = $c->get('settings');
* $config = $c->get('config');
* return new RedisCache(
* $config['hostname'],
* 6379,
* $settings['redis_password']
* );
* });
*
* Raw \Redis Access:
*
* During migration, legacy code may need raw \Redis:
* $rawRedis = $redisCache->getConnection();
* $rawRedis->multi();
* $rawRedis->zAdd('LIVE', $timestamp, $uuid);
* $rawRedis->exec();
*
* @see CacheInterface
* @see RedisManager::ensureConnected()
*
* @package XC_VM_Core_Cache
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
class RedisCache implements CacheInterface {
/** @var \Redis|null phpredis connection */
protected $redis = null;
/** @var string \Redis host */
protected $host;
/** @var int \Redis port */
protected $port;
/** @var string|null \Redis password */
protected $password;
/** @var bool Whether connection is established */
protected $connected = false;
/** @var string Key prefix to avoid collisions */
protected $prefix = '';
/**
* @param string $host \Redis host
* @param int $port \Redis port
* @param string|null $password \Redis AUTH password
* @param string $prefix Optional key prefix
*/
public function __construct(string $host = '127.0.0.1', int $port = 6379, ?string $password = null, string $prefix = '') {
$this->host = $host;
$this->port = $port;
$this->password = $password;
$this->prefix = $prefix;
}
/**
* Establish \Redis connection (lazy — called on first operation)
*
* @return bool
*/
public function connect() {
if ($this->connected && $this->redis !== null) {
return true;
}
try {
$this->redis = new \Redis();
$this->redis->connect($this->host, $this->port);
if ($this->password) {
$this->redis->auth($this->password);
}
// Use igbinary serializer if available for consistency with FileCache
if (defined('Redis::SERIALIZER_IGBINARY')) {
$this->redis->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_IGBINARY);
}
$this->connected = true;
return true;
} catch (\Exception $e) {
$this->redis = null;
$this->connected = false;
return false;
}
}
/**
* {@inheritdoc}
*/
public function get($key, $maxAge = null) {
if (!$this->ensureConnected()) {
return false;
}
$prefixedKey = $this->prefix . $key;
$data = $this->redis->get($prefixedKey);
if ($data === false) {
return false;
}
// maxAge is handled by \Redis TTL, not by us
// But if caller wants to check age, we can't — \Redis doesn't store creation time
// For file-based TTL compat, we ignore maxAge here (\Redis uses its own TTL)
return $data;
}
/**
* {@inheritdoc}
*/
public function set($key, $data, $ttl = 0) {
if (!$this->ensureConnected()) {
return false;
}
$prefixedKey = $this->prefix . $key;
if ($ttl > 0) {
return $this->redis->setex($prefixedKey, $ttl, $data);
}
return $this->redis->set($prefixedKey, $data);
}
/**
* {@inheritdoc}
*/
public function delete($key) {
if (!$this->ensureConnected()) {
return false;
}
$prefixedKey = $this->prefix . $key;
$this->redis->del($prefixedKey);
return true;
}
/**
* {@inheritdoc}
*/
public function has($key, $maxAge = null) {
if (!$this->ensureConnected()) {
return false;
}
$prefixedKey = $this->prefix . $key;
return (bool) $this->redis->exists($prefixedKey);
}
/**
* {@inheritdoc}
*
* WARNING: Flushes the ENTIRE \Redis database. Use with caution.
*/
public function flush() {
if (!$this->ensureConnected()) {
return false;
}
return $this->redis->flushDB();
}
// ───────────────────────────────────────────────────────────
// Raw \Redis Access (for migration period)
// ───────────────────────────────────────────────────────────
/**
* Get the raw phpredis connection
*
* Allows legacy code to use \Redis-specific operations (sorted sets,
* pipelines, pub/sub, etc.) that don't fit the CacheInterface.
*
* @return \Redis|null
*/
public function getConnection() {
$this->ensureConnected();
return $this->redis;
}
/**
* Check if connection is alive
*
* @return bool
*/
public function isConnected() {
if (!$this->connected || !$this->redis) {
return false;
}
try {
return $this->redis->ping() !== false;
} catch (\Exception $e) {
$this->connected = false;
return false;
}
}
/**
* Close connection
*/
public function disconnect() {
if ($this->redis) {
try {
$this->redis->close();
} catch (\Exception $e) {
// ignore
}
}
$this->redis = null;
$this->connected = false;
}
/**
* Ensure connection is active, reconnect if needed
*
* @return bool
*/
protected function ensureConnected() {
if ($this->connected && $this->redis !== null) {
return true;
}
return $this->connect();
}
/**
* Disconnect from \Redis when the instance is destroyed.
*/
public function __destruct() {
$this->disconnect();
}
}