refactor(redis): add type hints, PHPDoc, null-safety and health-check ping

This commit is contained in:
Divarion-D
2026-04-11 22:06:04 +03:00
parent 5e0bfe343c
commit b544357172
2 changed files with 334 additions and 57 deletions

View File

@@ -1,7 +1,12 @@
<?php
/**
* ConnectionTracker — connection tracker
* ConnectionTracker — live streaming connection management.
*
* Tracks connection lifecycle (create, update, close), stores state in Redis
* sorted sets (LIVE, LINE#, STREAM#, SERVER#, PROXY#) with MySQL fallback.
* Provides server load calculation, batch connection queries by user/server/stream,
* and closed connection activity logging.
*
* @package XC_VM_Domain_Stream
* @author Divarion_D <https://github.com/Divarion-D>
@@ -11,7 +16,17 @@
*/
class ConnectionTracker {
public static function getCapacity($rProxy = false) {
/**
* Calculate server/proxy load capacity.
*
* Counts active connections via Redis zCard or MySQL COUNT, then computes
* load ratio using the configured strategy: band, maxclients, guar_band, or client count.
* Result is cached to a file.
*
* @param bool $rProxy If true — calculate for proxy servers, otherwise for main servers.
* @return array<int, array{online_clients: int, capacity?: float}> Map of serverID => load data.
*/
public static function getCapacity(bool $rProxy = false): array {
global $rSettings, $rServers, $db;
$rRedis = RedisManager::instance();
$rFile = ($rProxy ? 'proxy_capacity' : 'servers_capacity');
@@ -28,10 +43,13 @@ class ConnectionTracker {
}
}
$rResults = $rMulti->exec();
if (!is_array($rResults)) {
$rResults = [];
}
$i = 0;
foreach (array_keys($rServers) as $rServerID) {
if ($rServers[$rServerID]['server_online']) {
$rRows[$rServerID] = array('online_clients' => ($rResults[$i] ?: 0));
$rRows[$rServerID] = array('online_clients' => ($rResults[$i] ?? 0));
$i++;
}
}
@@ -82,13 +100,24 @@ class ConnectionTracker {
}
}
if (defined('CACHE_TMP_PATH') && is_dir(CACHE_TMP_PATH)) {
if (defined('CACHE_TMP_PATH') && is_dir(CACHE_TMP_PATH) && is_writable(CACHE_TMP_PATH)) {
file_put_contents(CACHE_TMP_PATH . $rFile, json_encode($rRows), LOCK_EX);
}
return $rRows;
}
public static function getConnections($rServerID = null, $rUserID = null, $rStreamID = null) {
/**
* Get connections filtered by server, user, or stream.
*
* In Redis mode returns [keys[], deserialized data[]].
* In MySQL mode performs a JOIN query with lines, streams, streams_servers tables.
*
* @param int|null $rServerID Server ID to filter by.
* @param int|null $rUserID User ID to filter by.
* @param int|null $rStreamID Stream ID to filter by.
* @return array Connections: [keys[], data[]] for Redis or rows for MySQL.
*/
public static function getConnections(?int $rServerID = null, ?int $rUserID = null, ?int $rStreamID = null): array {
global $rSettings, $db;
$rRedis = RedisManager::instance();
if ($rSettings['redis_handler']) {
@@ -127,16 +156,32 @@ class ConnectionTracker {
return $db->get_rows(true, 'user_id', false);
}
public static function getMainID() {
/**
* Get the main server ID.
*
* @return int|null Server ID with is_main flag, or null if not found.
*/
public static function getMainID(): ?int {
global $rServers;
foreach ($rServers as $rServerID => $rServer) {
if ($rServer['is_main']) {
return $rServerID;
}
}
return null;
}
public static function addToQueue($rStreamID, $rAddPID) {
/**
* Add a process PID to the stream processing queue.
*
* Queue is stored on disk (igbinary). Dead PIDs are automatically
* filtered out on each call.
*
* @param int $rStreamID Stream ID.
* @param int $rAddPID Process PID to add.
* @return void
*/
public static function addToQueue(int $rStreamID, int $rAddPID): void {
$rActivePIDs = $rPIDs = array();
if (file_exists(SIGNALS_TMP_PATH . 'queue_' . intval($rStreamID))) {
$rPIDs = igbinary_unserialize(file_get_contents(SIGNALS_TMP_PATH . 'queue_' . intval($rStreamID)));
@@ -152,7 +197,16 @@ class ConnectionTracker {
file_put_contents(SIGNALS_TMP_PATH . 'queue_' . intval($rStreamID), igbinary_serialize($rActivePIDs), LOCK_EX);
}
public static function removeFromQueue($rStreamID, $rPID) {
/**
* Remove a process PID from the stream processing queue.
*
* If the queue becomes empty, the file is deleted.
*
* @param int $rStreamID Stream ID.
* @param int $rPID Process PID to remove.
* @return void
*/
public static function removeFromQueue(int $rStreamID, int $rPID): void {
$rActivePIDs = array();
foreach ((igbinary_unserialize(file_get_contents(SIGNALS_TMP_PATH . 'queue_' . intval($rStreamID))) ?: array()) as $rActivePID) {
if (ProcessManager::isRunning($rActivePID, 'php-fpm') && $rPID != $rActivePID) {
@@ -166,7 +220,19 @@ class ConnectionTracker {
}
}
public static function updateConnection($rData, $rChanges = array(), $rOption = null) {
/**
* Update a connection in Redis with applied changes.
*
* With $rOption='open' — adds UUID to all sorted sets (LIVE, LINE#, STREAM#, SERVER#, etc.).
* With $rOption='close' — removes UUID from sorted sets and marks as ENDED.
* Data is igbinary-serialized and saved atomically via MULTI/EXEC.
*
* @param array $rData Current connection data.
* @param array $rChanges Fields to update (key => value).
* @param string|null $rOption Action: 'open', 'close', or null (data update only).
* @return array|null Updated connection data, or null on exec failure.
*/
public static function updateConnection(array $rData, array $rChanges = [], ?string $rOption = null): ?array {
$rRedis = RedisManager::instance();
$rOrigData = $rData;
foreach ($rChanges as $rKey => $rValue) {
@@ -213,14 +279,36 @@ class ConnectionTracker {
return null;
}
public static function redisSignal($rPID, $rServerID, $rRTMP, $rCustomData = null) {
/**
* Send a signal to a server via Redis.
*
* Creates an entry in SIGNALS#{serverID} set and stores signal data.
* Used for remote termination of RTMP/HLS streams on other servers.
*
* @param int $rPID Process PID to terminate.
* @param int $rServerID Target server ID.
* @param int $rRTMP 1 — RTMP, 0 — regular process.
* @param mixed|null $rCustomData Additional signal data.
* @return array|false MULTI/EXEC result.
*/
public static function redisSignal(int $rPID, int $rServerID, int $rRTMP, $rCustomData = null) {
$rRedis = RedisManager::instance();
$rKey = 'SIGNAL#' . md5($rServerID . '#' . $rPID . '#' . $rRTMP);
$rData = array('pid' => $rPID, 'server_id' => $rServerID, 'rtmp' => $rRTMP, 'time' => time(), 'custom_data' => $rCustomData, 'key' => $rKey);
return $rRedis->multi()->sAdd('SIGNALS#' . $rServerID, $rKey)->set($rKey, igbinary_serialize($rData))->exec();
}
public static function getUserConnections($rUserIDs, $rCount = false, $rKeysOnly = false) {
/**
* Get connections for multiple users (batch).
*
* Uses MULTI pipeline for parallel LINE# sorted set queries.
*
* @param int[] $rUserIDs Array of user IDs.
* @param bool $rCount If true — return only connection count per user.
* @param bool $rKeysOnly If true — return only Redis keys (UUIDs) without deserialization.
* @return array Map of userID => connections[] (or count, or keys).
*/
public static function getUserConnections(array $rUserIDs, bool $rCount = false, bool $rKeysOnly = false): array {
$rRedis = RedisManager::instance();
$rMulti = $rRedis->multi();
foreach ($rUserIDs as $rUserID) {
@@ -228,6 +316,9 @@ class ConnectionTracker {
}
$rGroups = $rMulti->exec();
$rConnectionMap = $rRedisKeys = array();
if (!is_array($rGroups)) {
return ($rKeysOnly ? $rRedisKeys : $rConnectionMap);
}
foreach ($rGroups as $rGroupID => $rKeys) {
if ($rCount) {
$rConnectionMap[$rUserIDs[$rGroupID]] = count($rKeys);
@@ -239,7 +330,7 @@ class ConnectionTracker {
}
$rRedisKeys = array_unique($rRedisKeys);
if (!$rKeysOnly) {
if (!$rCount) {
if (!$rCount && !empty($rRedisKeys)) {
foreach ($rRedis->mGet($rRedisKeys) as $rRow) {
$rRow = igbinary_unserialize($rRow);
$rConnectionMap[$rRow['user_id']][] = $rRow;
@@ -250,7 +341,18 @@ class ConnectionTracker {
return $rRedisKeys;
}
public static function getServerConnections($rServerIDs, $rProxy = false, $rCount = false, $rKeysOnly = false) {
/**
* Get connections for multiple servers/proxies (batch).
*
* Uses MULTI pipeline for parallel SERVER#/PROXY# sorted set queries.
*
* @param int[] $rServerIDs Array of server IDs.
* @param bool $rProxy If true — query PROXY# instead of SERVER#.
* @param bool $rCount If true — return only count.
* @param bool $rKeysOnly If true — return only UUID keys.
* @return array Map of serverID => connections[] (or count, or keys).
*/
public static function getServerConnections(array $rServerIDs, bool $rProxy = false, bool $rCount = false, bool $rKeysOnly = false): array {
$rRedis = RedisManager::instance();
$rMulti = $rRedis->multi();
foreach ($rServerIDs as $rServerID) {
@@ -258,6 +360,9 @@ class ConnectionTracker {
}
$rGroups = $rMulti->exec();
$rConnectionMap = $rRedisKeys = array();
if (!is_array($rGroups)) {
return ($rKeysOnly ? $rRedisKeys : $rConnectionMap);
}
foreach ($rGroups as $rGroupID => $rKeys) {
if ($rCount) {
$rConnectionMap[$rServerIDs[$rGroupID]] = count($rKeys);
@@ -269,7 +374,7 @@ class ConnectionTracker {
}
$rRedisKeys = array_unique($rRedisKeys);
if (!$rKeysOnly) {
if (!$rCount) {
if (!$rCount && !empty($rRedisKeys)) {
foreach ($rRedis->mGet($rRedisKeys) as $rRow) {
$rRow = igbinary_unserialize($rRow);
$rConnectionMap[$rRow['server_id']][] = $rRow;
@@ -280,7 +385,15 @@ class ConnectionTracker {
return $rRedisKeys;
}
public static function getFirstConnection($rUserIDs) {
/**
* Get the most recent connection for each user.
*
* Queries LINE# with LIMIT 0,1 via MULTI pipeline and deserializes results.
*
* @param int[] $rUserIDs Array of user IDs.
* @return array<int, array> Map of userID => connection data.
*/
public static function getFirstConnection(array $rUserIDs): array {
$rRedis = RedisManager::instance();
$rMulti = $rRedis->multi();
foreach ($rUserIDs as $rUserID) {
@@ -288,11 +401,17 @@ class ConnectionTracker {
}
$rGroups = $rMulti->exec();
$rConnectionMap = $rRedisKeys = array();
if (!is_array($rGroups)) {
return $rConnectionMap;
}
foreach ($rGroups as $rKeys) {
if (0 < count($rKeys)) {
$rRedisKeys[] = $rKeys[0];
}
}
if (empty($rRedisKeys)) {
return $rConnectionMap;
}
foreach ($rRedis->mGet(array_unique($rRedisKeys)) as $rRow) {
$rRow = igbinary_unserialize($rRow);
$rConnectionMap[$rRow['user_id']] = $rRow;
@@ -300,7 +419,17 @@ class ConnectionTracker {
return $rConnectionMap;
}
public static function getStreamConnections($rStreamIDs, $rGroup = true, $rCount = false) {
/**
* Get connections for multiple streams (batch).
*
* Uses MULTI pipeline for parallel STREAM# sorted set queries.
*
* @param int[] $rStreamIDs Array of stream IDs.
* @param bool $rGroup If true — group by stream_id, otherwise by stream_id + server_id.
* @param bool $rCount If true — return only connection count per stream.
* @return array Map of streamID => connections[] (or count).
*/
public static function getStreamConnections(array $rStreamIDs, bool $rGroup = true, bool $rCount = false): array {
$rRedis = RedisManager::instance();
$rMulti = $rRedis->multi();
foreach ($rStreamIDs as $rStreamID) {
@@ -308,6 +437,9 @@ class ConnectionTracker {
}
$rGroups = $rMulti->exec();
$rConnectionMap = $rRedisKeys = array();
if (!is_array($rGroups)) {
return $rConnectionMap;
}
foreach ($rGroups as $rGroupID => $rKeys) {
if ($rCount) {
$rConnectionMap[$rStreamIDs[$rGroupID]] = count($rKeys);
@@ -317,7 +449,7 @@ class ConnectionTracker {
}
}
}
if (!$rCount) {
if (!$rCount && !empty($rRedisKeys)) {
foreach ($rRedis->mGet(array_unique($rRedisKeys)) as $rRow) {
$rRow = igbinary_unserialize($rRow);
if ($rGroup) {
@@ -330,7 +462,22 @@ class ConnectionTracker {
return $rConnectionMap;
}
public static function getRedisConnections($rUserID = null, $rServerID = null, $rStreamID = null, $rOpenOnly = false, $rCountOnly = false, $rGroup = true, $rHLSOnly = false) {
/**
* Universal Redis connection query with multiple filters.
*
* Reads from LIVE/LINE#/STREAM#/SERVER# depending on provided filters.
* Supports counting, grouping by user identity, and HLS filtering.
*
* @param int|null $rUserID Filter by user.
* @param int|null $rServerID Filter by server.
* @param int|null $rStreamID Filter by stream.
* @param bool $rOpenOnly Only open connections (hls_end=0).
* @param bool $rCountOnly Return [total, unique_users] instead of data.
* @param bool $rGroup Group by user/identity.
* @param bool $rHLSOnly Exclude HLS connections.
* @return array Connections grouped by identity, or [count, unique].
*/
public static function getRedisConnections(?int $rUserID = null, ?int $rServerID = null, ?int $rStreamID = null, bool $rOpenOnly = false, bool $rCountOnly = false, bool $rGroup = true, bool $rHLSOnly = false): array {
$rRedis = RedisManager::instance();
$rReturn = ($rCountOnly ? array(0, 0) : array());
$rUniqueUsers = array();
@@ -380,7 +527,13 @@ class ConnectionTracker {
return $rReturn;
}
public static function getConnection($rUUID) {
/**
* Get a single connection by UUID.
*
* @param string $rUUID Connection UUID (Redis key).
* @return array|null Deserialized connection data, or null if not found.
*/
public static function getConnection(string $rUUID): ?array {
$rRedis = RedisManager::instance();
if (!$rRedis) {
return null;
@@ -389,7 +542,17 @@ class ConnectionTracker {
return ($raw !== false) ? igbinary_unserialize($raw) : null;
}
public static function createConnection($rData) {
/**
* Create a new connection in Redis.
*
* Atomically (MULTI/EXEC) adds UUID to all sorted sets:
* LINE#, LINE_ALL#, STREAM#, SERVER#, SERVER_LINES#, PROXY#,
* CONNECTIONS, LIVE, and stores igbinary-serialized data.
*
* @param array $rData Connection data (uuid, identity, stream_id, server_id, etc.).
* @return array|false MULTI/EXEC result.
*/
public static function createConnection(array $rData) {
$rRedis = RedisManager::instance();
$rMulti = $rRedis->multi();
$rMulti->zAdd('LINE#' . $rData['identity'], $rData['date_start'], $rData['uuid']);
@@ -408,7 +571,15 @@ class ConnectionTracker {
return $rMulti->exec();
}
public static function getLineConnections($rUserID, $rActive = false, $rKeys = false) {
/**
* Get connections for a specific user/line.
*
* @param int $rUserID User ID.
* @param bool $rActive If true — only active (LINE#), otherwise all (LINE_ALL#).
* @param bool $rKeys Unused (overwritten internally).
* @return array UUID keys or deserialized connection data.
*/
public static function getLineConnections(int $rUserID, bool $rActive = false, bool $rKeys = false): array {
$rRedis = RedisManager::instance();
$rKeys = $rRedis->zRangeByScore((($rActive ? 'LINE#' : 'LINE_ALL#')) . $rUserID, '-inf', '+inf');
if ($rKeys) {
@@ -420,7 +591,14 @@ class ConnectionTracker {
return array_map('igbinary_unserialize', $rRedis->mGet($rKeys));
}
public static function getEnded() {
/**
* Get all ended (ENDED) connections.
*
* Reads members from ENDED set and deserializes data via mGet.
*
* @return array Array of deserialized ended connection data.
*/
public static function getEnded(): array {
$rRedis = RedisManager::instance();
$rKeys = $rRedis->sMembers('ENDED');
if (0 >= count($rKeys)) {
@@ -429,7 +607,14 @@ class ConnectionTracker {
return array_map('igbinary_unserialize', $rRedis->mGet($rKeys));
}
public static function getProxies($rServerID, $rOnline = true) {
/**
* Get proxy servers attached to the specified server.
*
* @param int $rServerID Parent server ID.
* @param bool $rOnline If true — only online proxies.
* @return array<int, array> Map of proxyID => server data.
*/
public static function getProxies(int $rServerID, bool $rOnline = true): array {
global $rServers;
$rReturn = array();
foreach ($rServers as $rProxyID => $rServerInfo) {
@@ -440,7 +625,19 @@ class ConnectionTracker {
return $rReturn;
}
public static function closeConnection($rActivityInfo, $rRemove = true, $rEnd = true) {
/**
* Close an active connection.
*
* Performs the full close cycle: kills the process (RTMP drop client,
* posix_kill, or Redis signal), removes from Redis sorted sets,
* cleans tmp files, and writes to the activity log.
*
* @param array|string $rActivityInfo Connection data or UUID/activity_id.
* @param bool $rRemove Remove connection from Redis/MySQL.
* @param bool $rEnd Mark HLS connection as ended.
* @return bool True on successful close, false otherwise.
*/
public static function closeConnection($rActivityInfo, bool $rRemove = true, bool $rEnd = true): bool {
if (!empty($rActivityInfo)) {
global $rSettings, $rServers, $db;
if (!$rSettings['redis_handler'] || is_object(RedisManager::instance())) {
@@ -499,14 +696,11 @@ class ConnectionTracker {
}
}
}
if ($rActivityInfo['server_id'] != SERVER_ID) {
} else {
if ($rActivityInfo['server_id'] == SERVER_ID) {
@unlink(CONS_TMP_PATH . $rActivityInfo['uuid']);
}
if (!$rRemove) {
} else {
if ($rActivityInfo['server_id'] != SERVER_ID) {
} else {
if ($rRemove) {
if ($rActivityInfo['server_id'] == SERVER_ID) {
@unlink(CONS_TMP_PATH . $rActivityInfo['stream_id'] . '/' . $rActivityInfo['uuid']);
}
if ($rSettings['redis_handler']) {
@@ -515,12 +709,10 @@ class ConnectionTracker {
$rRedis->zRem('LINE_ALL#' . $rActivityInfo['identity'], $rActivityInfo['uuid']);
$rRedis->zRem('STREAM#' . $rActivityInfo['stream_id'], $rActivityInfo['uuid']);
$rRedis->zRem('SERVER#' . $rActivityInfo['server_id'], $rActivityInfo['uuid']);
if (!$rActivityInfo['user_id']) {
} else {
if ($rActivityInfo['user_id']) {
$rRedis->zRem('SERVER_LINES#' . $rActivityInfo['server_id'], $rActivityInfo['uuid']);
}
if (!$rActivityInfo['proxy_id']) {
} else {
if ($rActivityInfo['proxy_id']) {
$rRedis->zRem('PROXY#' . $rActivityInfo['proxy_id'], $rActivityInfo['uuid']);
}
$rRedis->del($rActivityInfo['uuid']);
@@ -532,7 +724,7 @@ class ConnectionTracker {
$db->query('DELETE FROM `lines_live` WHERE `activity_id` = ?', $rActivityInfo['activity_id']);
}
}
self::writeOfflineActivity($rSettings, $rActivityInfo['server_id'], $rActivityInfo['proxy_id'], $rActivityInfo['user_id'], $rActivityInfo['stream_id'], $rActivityInfo['date_start'], $rActivityInfo['user_agent'], $rActivityInfo['user_ip'], $rActivityInfo['container'], $rActivityInfo['geoip_country_code'], $rActivityInfo['isp'], $rActivityInfo['external_device'], $rActivityInfo['divergence'], $rActivityInfo['hmac_id'], $rActivityInfo['hmac_identifier']);
self::writeOfflineActivity($rSettings, $rActivityInfo['server_id'], $rActivityInfo['proxy_id'], $rActivityInfo['user_id'], $rActivityInfo['stream_id'], $rActivityInfo['date_start'], $rActivityInfo['user_agent'], $rActivityInfo['user_ip'], $rActivityInfo['container'], $rActivityInfo['geoip_country_code'], $rActivityInfo['isp'], $rActivityInfo['external_device'] ?? '', $rActivityInfo['divergence'] ?? 0, $rActivityInfo['hmac_id'] ?? null, $rActivityInfo['hmac_identifier'] ?? '');
return true;
}
return false;
@@ -540,7 +732,30 @@ class ConnectionTracker {
return false;
}
public static function writeOfflineActivity($rSettings, $rServerID, $rProxyID, $rUserID, $rStreamID, $rStart, $rUserAgent, $rIP, $rExtension, $rGeoIP, $rISP, $rExternalDevice = '', $rDivergence = 0, $rIsHMAC = null, $rIdentifier = '') {
/**
* Write closed connection data to the activity log file.
*
* Log is written as base64(json) per line to LOGS_TMP_PATH/activity.
* Only writes if save_closed_connection setting is enabled.
*
* @param array $rSettings Global settings.
* @param int $rServerID Server ID.
* @param int $rProxyID Proxy ID (0 if no proxy).
* @param int $rUserID User ID.
* @param int $rStreamID Stream ID.
* @param int $rStart Connection start Unix timestamp.
* @param string $rUserAgent Client User-Agent.
* @param string $rIP Client IP address.
* @param string $rExtension Container type (rtmp, hls, etc.).
* @param string $rGeoIP GeoIP country code.
* @param string $rISP ISP name.
* @param string $rExternalDevice External device identifier.
* @param int $rDivergence Divergence value.
* @param int|null $rIsHMAC HMAC ID.
* @param string $rIdentifier HMAC identifier.
* @return void
*/
public static function writeOfflineActivity(array $rSettings, int $rServerID, int $rProxyID, int $rUserID, int $rStreamID, int $rStart, string $rUserAgent, string $rIP, string $rExtension, string $rGeoIP, string $rISP, string $rExternalDevice = '', int $rDivergence = 0, ?int $rIsHMAC = null, string $rIdentifier = ''): void {
if ($rSettings['save_closed_connection'] != 0) {
if (!($rServerID && $rUserID && $rStreamID)) {
} else {
@@ -548,11 +763,21 @@ class ConnectionTracker {
file_put_contents(LOGS_TMP_PATH . 'activity', base64_encode(json_encode($rActivityInfo)) . "\n", FILE_APPEND | LOCK_EX);
}
} else {
return null;
return;
}
}
public static function getLiveConnections($rServerID, $rProxy = false) {
/**
* Count active (live) connections on a server or proxy.
*
* In Redis mode counts via getRedisConnections.
* In MySQL mode performs COUNT(*) on lines_live.
*
* @param int $rServerID Server or proxy ID.
* @param bool $rProxy If true — count for proxy.
* @return int Number of active connections.
*/
public static function getLiveConnections(int $rServerID, bool $rProxy = false): int {
global $db;
if (SettingsManager::getAll()['redis_handler']) {

View File

@@ -1,9 +1,11 @@
<?php
/**
* RedisManager — управление жизненным циклом Redis-подключения.
* RedisManager — Redis connection lifecycle management.
*
* Singleton хранит активный экземпляр Redis.
* Singleton that holds the active Redis instance. Provides health-check
* via ping (debounced to 30s), auto-reconnect on failure, and low-level
* connect/close helpers for non-singleton usage.
*
* @package XC_VM_Infrastructure_Redis
* @author Divarion_D <https://github.com/Divarion-D>
@@ -13,55 +15,99 @@
*/
class RedisManager {
/** @var Redis|null Singleton-экземпляр */
/** @var Redis|null Singleton instance */
private static $instance = null;
/** @var int Last ping health-check timestamp */
private static $lastPingCheck = 0;
// ──────── Singleton API ────────
/**
* Возвращает активный Redis, при необходимости подключаясь.
* @return Redis|null
* Get the active Redis instance, connecting if necessary.
*
* Performs a ping health-check no more than once every 30 seconds.
* If the connection is dead, attempts to reconnect automatically.
*
* @return Redis|null Active Redis instance, or null on connection failure.
*/
public static function instance() {
public static function instance(): ?Redis {
if (is_object(self::$instance)) {
$rNow = time();
if ($rNow - self::$lastPingCheck > 30) {
try {
self::$instance->ping();
self::$lastPingCheck = $rNow;
} catch (RedisException $e) {
self::$instance = null;
}
}
}
if (!is_object(self::$instance)) {
self::ensureConnected();
self::$lastPingCheck = time();
}
return self::$instance;
}
/**
* Подключается к Redis, если ещё нет соединения.
* @return bool
* Connect to Redis if not already connected.
*
* Uses ConfigReader and SettingsManager for hostname and password.
*
* @return bool True if connected, false otherwise.
*/
public static function ensureConnected() {
public static function ensureConnected(): bool {
self::$instance = self::connect(self::$instance, ConfigReader::getAll(), SettingsManager::getAll());
return is_object(self::$instance);
}
/**
* Закрывает singleton-подключение.
* @return bool
* Close the singleton connection.
*
* @return bool Always returns true.
*/
public static function closeInstance() {
public static function closeInstance(): bool {
self::$instance = self::close(self::$instance);
return true;
}
/**
* Проверяет, подключён ли singleton.
* @return bool
* Check whether the singleton is connected.
*
* @return bool True if connected.
*/
public static function isConnected() {
public static function isConnected(): bool {
return is_object(self::$instance);
}
// ──────── Low-level API (без singleton) ────────
public static function setSignal($rKey, $rData) {
/**
* Write a signal to the filesystem cache.
*
* Stores a JSON-encoded [key, data] pair in SIGNALS_TMP_PATH.
*
* @param string $rKey Signal key.
* @param mixed $rData Signal payload.
* @return void
*/
public static function setSignal(string $rKey, $rData): void {
file_put_contents(SIGNALS_TMP_PATH . 'cache_' . md5($rKey), json_encode(array($rKey, $rData)));
}
public static function connect($rRedis, $rConfig, $rSettings) {
/**
* Connect to Redis (low-level, non-singleton).
*
* If $rRedis is already a live connection, returns it as-is.
* Otherwise creates a new connection using hostname from $rConfig
* and password from $rSettings.
*
* @param Redis|null $rRedis Existing Redis instance or null.
* @param array $rConfig Config array (must contain 'hostname').
* @param array $rSettings Settings array (must contain 'redis_password').
* @return Redis|null Connected Redis instance, or null on failure.
*/
public static function connect(?Redis $rRedis, array $rConfig, array $rSettings): ?Redis {
if (is_object($rRedis)) {
try {
$rRedis->ping();
@@ -87,7 +133,13 @@ class RedisManager {
}
}
public static function close($rRedis) {
/**
* Close a Redis connection.
*
* @param Redis|null $rRedis Redis instance to close.
* @return null Always returns null (for assignment: $redis = close($redis)).
*/
public static function close(?Redis $rRedis): ?Redis {
if (is_object($rRedis)) {
$rRedis->close();
}