99use imperazim \components \plugin \traits \PluginComponentsTrait ;
1010
1111/**
12- * Class Cache
13- * @package imperazim\components\cache
12+ * Runtime-only in-memory cache used by EasyLibrary and compatibility components.
13+ *
14+ * This cache is intentionally small: it does not persist data, does not provide
15+ * distributed state and should not grow into a storage framework.
16+ *
17+ * @phpstan-type CacheEntry array{value: mixed, expires_at: int|null}
1418*/
1519final class Cache extends PluginComponent {
1620 use PluginComponentsTrait;
1721
1822 /**
19- * @var array An associative array where keys represent cache keys and values represent cached data with expiration.
23+ * @var array<string, CacheEntry>
2024 */
2125 private static array $ cache = [];
2226
2327 /**
24- * Initializes the Cache component.
25- * @param PluginToolkit $plugin The Plugin.
28+ * Initializes the runtime cache component.
29+ *
30+ * @return array<string, mixed>
2631 */
2732 public static function init (PluginToolkit $ plugin ): array {
2833 return [];
@@ -44,45 +49,35 @@ public static function close(PluginToolkit $plugin): void {
4449 public static function put (string $ key , mixed $ value , ?int $ ttl = null ): void {
4550 self ::$ cache [$ key ] = [
4651 'value ' => $ value ,
47- 'expires_at ' => $ ttl !== null ? time () + $ ttl : null
52+ 'expires_at ' => $ ttl !== null ? self :: now () + max ( 0 , $ ttl) : null
4853 ];
4954 }
5055
5156 /**
52- * Retrieves cached data by key, returning null if the data is expired or doesn't exist.
53- * @param string $key The cache key.
54- * @return mixed|null The cached value or null if expired/not found.
57+ * Retrieves cached data by key, returning null if the entry is expired or missing.
5558 */
5659 public static function get (string $ key ): mixed {
57- if (!isset ( self ::$ cache[ $ key ] )) {
60+ if (!array_key_exists ( $ key , self ::$ cache )) {
5861 return null ;
5962 }
6063
61- $ cacheItem = self ::$ cache [$ key ];
62-
63- // Check if the item has expired
64- if ($ cacheItem ['expires_at ' ] !== null && $ cacheItem ['expires_at ' ] < time ()) {
64+ if (self ::isEntryExpired (self ::$ cache [$ key ])) {
6565 self ::invalidate ($ key );
6666 return null ;
6767 }
6868
69- return $ cacheItem ['value ' ];
69+ return self :: $ cache [ $ key ] ['value ' ];
7070 }
7171
7272 /**
7373 * Checks if a cache key exists and is not expired.
74- * @param string $key The cache key.
75- * @return bool True if the key exists and is not expired, false otherwise.
7674 */
7775 public static function has (string $ key ): bool {
78- if (!isset ( self ::$ cache[ $ key ] )) {
76+ if (!array_key_exists ( $ key , self ::$ cache )) {
7977 return false ;
8078 }
8179
82- $ cacheItem = self ::$ cache [$ key ];
83-
84- // Check if the item has expired
85- if ($ cacheItem ['expires_at ' ] !== null && $ cacheItem ['expires_at ' ] < time ()) {
80+ if (self ::isEntryExpired (self ::$ cache [$ key ])) {
8681 self ::invalidate ($ key );
8782 return false ;
8883 }
@@ -98,9 +93,8 @@ public static function has(string $key): bool {
9893 * @return mixed The cached value or callback result.
9994 */
10095 public static function remember (string $ key , callable $ callback , ?int $ ttl = null ): mixed {
101- $ value = self ::get ($ key );
102- if ($ value !== null ) {
103- return $ value ;
96+ if (self ::has ($ key )) {
97+ return self ::get ($ key );
10498 }
10599
106100 $ value = $ callback ();
@@ -125,19 +119,21 @@ public static function clear(): void {
125119
126120 /**
127121 * Stores multiple values in the cache with optional TTL.
128- * @param array $items Associative array of key => value pairs.
122+ *
123+ * @param array<int|string, mixed> $items Associative array of key => value pairs.
129124 * @param int|null $ttl Time to live in seconds. Null for no expiration.
130125 */
131126 public static function putMany (array $ items , ?int $ ttl = null ): void {
132127 foreach ($ items as $ key => $ value ) {
133- self ::put ($ key , $ value , $ ttl );
128+ self ::put (( string ) $ key , $ value , $ ttl );
134129 }
135130 }
136131
137132 /**
138133 * Retrieves multiple cached values by keys.
139- * @param array $keys Array of cache keys.
140- * @return array Associative array of key => value pairs (null for missing/expired).
134+ *
135+ * @param array<int, string> $keys Array of cache keys.
136+ * @return array<string, mixed|null> Associative array of key => value pairs.
141137 */
142138 public static function getMany (array $ keys ): array {
143139 $ results = [];
@@ -151,9 +147,9 @@ public static function getMany(array $keys): array {
151147 * Removes expired cache entries.
152148 */
153149 public static function purgeExpired (): void {
154- $ currentTime = time ();
150+ $ currentTime = self :: now ();
155151 foreach (self ::$ cache as $ key => $ item ) {
156- if ($ item[ ' expires_at ' ] !== null && $ item [ ' expires_at ' ] < $ currentTime ) {
152+ if (self :: isEntryExpired ( $ item, $ currentTime) ) {
157153 unset(self ::$ cache [$ key ]);
158154 }
159155 }
@@ -222,6 +218,7 @@ public static function exists(string $key): bool {
222218 * @return int Number of cached entries.
223219 */
224220 public static function size (): int {
221+ self ::purgeExpired ();
225222 return count (self ::$ cache );
226223 }
227224
@@ -231,11 +228,11 @@ public static function size(): int {
231228 * @return bool True if the key exists but has expired.
232229 */
233230 public static function isExpired (string $ key ): bool {
234- if (!isset ( self ::$ cache[ $ key ] )) {
235- return false ; // Key doesn't exist, so it's not "expired" - it never existed
231+ if (!array_key_exists ( $ key , self ::$ cache )) {
232+ return false ;
236233 }
237- $ item = self :: $ cache [ $ key ];
238- return $ item [ ' expires_at ' ] !== null && $ item [ ' expires_at ' ] < time ( );
234+
235+ return self :: isEntryExpired ( self :: $ cache [ $ key ] );
239236 }
240237
241238 /**
@@ -244,48 +241,64 @@ public static function isExpired(string $key): bool {
244241 * @return int|null Remaining TTL in seconds, or null if key doesn't exist or has no expiration.
245242 */
246243 public static function getRemainingTtl (string $ key ): ?int {
247- if (!isset ( self ::$ cache [ $ key] )) {
244+ if (!self ::has ( $ key )) {
248245 return null ;
249246 }
247+
250248 $ item = self ::$ cache [$ key ];
251249 if ($ item ['expires_at ' ] === null ) {
252- return null ; // No expiration
250+ return null ;
253251 }
254- $ remaining = $ item [ ' expires_at ' ] - time ();
255- return max (0 , $ remaining ); // Don't return negative values
252+
253+ return max (0 , $ item [ ' expires_at ' ] - self :: now ());
256254 }
257255
258256 /**
259257 * Returns all cache entries as an associative array.
260- * @return array All cache entries [key => value].
258+ *
259+ * @return array<string, mixed> All active cache entries [key => value].
261260 */
262261 public static function all (): array {
262+ self ::purgeExpired ();
263+
263264 $ result = [];
264265 foreach (self ::$ cache as $ key => $ item ) {
265- if ($ item ['expires_at ' ] === null || $ item ['expires_at ' ] > time ()) {
266- $ result [$ key ] = $ item ['value ' ];
267- }
266+ $ result [$ key ] = $ item ['value ' ];
268267 }
269268 return $ result ;
270269 }
271270
272271 /**
273272 * Returns cache statistics such as total entries, hits, misses, and expired items.
274- * @return array Cache statistics.
273+ *
274+ * @return array{total_entries: int, active_entries: int, expired_entries: int}
275275 */
276276 public static function getStats (): array {
277277 $ totalEntries = count (self ::$ cache );
278278 $ expiredEntries = 0 ;
279+ $ currentTime = self ::now ();
279280
280281 foreach (self ::$ cache as $ key => $ item ) {
281- if ($ item[ ' expires_at ' ] !== null && $ item [ ' expires_at ' ] < time ( )) {
282+ if (self :: isEntryExpired ( $ item, $ currentTime )) {
282283 $ expiredEntries ++;
283284 }
284285 }
285286
286287 return [
287288 'total_entries ' => $ totalEntries ,
289+ 'active_entries ' => $ totalEntries - $ expiredEntries ,
288290 'expired_entries ' => $ expiredEntries
289291 ];
290292 }
291- }
293+
294+ private static function now (): int {
295+ return time ();
296+ }
297+
298+ /**
299+ * @param CacheEntry $entry
300+ */
301+ private static function isEntryExpired (array $ entry , ?int $ now = null ): bool {
302+ return $ entry ['expires_at ' ] !== null && $ entry ['expires_at ' ] <= ($ now ?? self ::now ());
303+ }
304+ }
0 commit comments