diff --git a/src/domain/Stream/ConnectionTracker.php b/src/domain/Stream/ConnectionTracker.php index 3cc0b15..86b0a81 100644 --- a/src/domain/Stream/ConnectionTracker.php +++ b/src/domain/Stream/ConnectionTracker.php @@ -1,7 +1,12 @@ @@ -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 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 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 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']) { diff --git a/src/infrastructure/redis/RedisManager.php b/src/infrastructure/redis/RedisManager.php index 06ef15e..45ed36e 100644 --- a/src/infrastructure/redis/RedisManager.php +++ b/src/infrastructure/redis/RedisManager.php @@ -1,9 +1,11 @@ @@ -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(); }