refactor: CappedMemoryCache class documentation and logic

Signed-off-by: Josh <josh.t.richards@gmail.com>
This commit is contained in:
Josh 2025-09-13 14:11:40 -04:00 committed by GitHub
parent cf4042dd28
commit bf853e20f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,38 +1,111 @@
<?php
/**
* Part of Nextcloud's public caching API: CappedMemoryCache class
*/
/**
* SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCP\Cache;
use OCP\ICache;
/**
* In-memory cache with a capacity limit to keep memory usage in check
* A simple, fast, memory-only FIFO cache with a configurable size limit.
*
* Uses a simple FIFO expiry mechanism
* Features:
* - Stores key-value pairs in memory, but only up to a specified capacity.
* - Implements standard Nextcloud cache (OCP\ICache) and PHP ArrayAccess interfaces.
* - Designed for temporary, in-memory caching in Nextcloud apps or core.
* - Uses a simple FIFO expiry mechanism.
*
* Benefits:
* - No administrative configuration/dependencies (always available).
* - Can be used via normal method calls (OCP\ICache) or PHP array syntax.
* - Automatically removes oldest entries when limit is reached.
* - Can be used like an array or via standard Nextcloud cache operations.
* - Lowest latency possible.
* - Reduces load on distributed cache.
* - Offers consumers flexibility to choose best cache combo for a use case
*
* Caveats:
* - Not at all shared (even among processes/transactions/requests on the same host).
* - Highly transient (end of process/transaction/request).
* - Consumes RAM on the local host.
*
* Usage examples:
*
* ```php
* [...]
* use OCP\Cache\CappedMemoryCache;
* [...]
* $cache = new CappedMemoryCache(64); // capacity of 64 items
* $cache->set('foo', 'bar'); // give key 'foo' value 'bar'
* if ($cache->hasKey('foo')) {
* echo $cache->get('foo'); // outputs 'bar'
* }
* ```
* Or using array syntax:
* ```php
* [...]
* $cache['baz'] = 'qux';
* if (isset($cache['baz']) {
* echo $cache['baz']; // outputs 'qux'
* }
* ```
*
* @link https://docs.nextcloud.com/server/latest/developer_manual/basics/caching.html
* @link https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/caching_configuration.html
*
* @since 25.0.0
* @template T
* @template-implements \ArrayAccess<string,T>
* @template-implements \ArrayAccess<string, T>
* @since 25.0.0
*/
class CappedMemoryCache implements ICache, \ArrayAccess {
/**
* Maximum number of entries allowed in the cache.
*
* @var int
*/
private int $capacity;
/** @var T[] */
/**
* Internal cache data storage.
*
* @var array<string, T>
*/
private array $cache = [];
/**
* @inheritdoc
* Current count of items stored in cache.
*
* @var int
*/
private int $itemCount = 0;
/**
* Constructor.
*
* @param int $capacity Maximum number of items in the cache. Defaults to 512.
* @since 25.0.0
*/
public function __construct(int $capacity = 512) {
$this->capacity = $capacity;
}
//
// Cache Operations
//
/**
* @inheritdoc
* Checks if the cache contains the specified key.
*
* @param string $key
* @return bool True if the key exists, false otherwise.
* @since 25.0.0
*/
public function hasKey($key): bool {
@ -40,7 +113,10 @@ class CappedMemoryCache implements ICache, \ArrayAccess {
}
/**
* @return ?T
* Retrieves the value for the specified key from the cache.
*
* @param string $key
* @return T|null The value, or null if the key does not exist.
* @since 25.0.0
*/
public function get($key) {
@ -48,41 +124,68 @@ class CappedMemoryCache implements ICache, \ArrayAccess {
}
/**
* @inheritdoc
* Adds or updates a value in the cache.
* If capacity is exceeded, evicts the oldest entries.
*
* @param string $key
* @param T $value
* @param int $ttl
* @param int $ttl Unused. Included for interface compatibility.
* @return bool True on success.
* @since 25.0.0
* @return bool
*/
public function set($key, $value, $ttl = 0): bool {
if (is_null($key)) {
$this->cache[] = $value;
} else {
$isNewKey = !isset($this->cache[$key]) || $key === null;
if ($key !== null) {
$this->cache[$key] = $value;
} else { // for offsetSet() when $key is null
$this->cache[] = $value;
}
if ($isNewKey) {
$this->itemCount++;
}
$this->garbageCollect();
return true;
}
/**
* Removes the specified key from the cache.
*
* @param string $key
* @return bool True on success.
* @since 25.0.0
*/
public function remove($key): bool {
unset($this->cache[$key]);
if (isset($this->cache[$key])) {
unset($this->cache[$key]);
$this->itemCount--;
}
return true;
}
/**
* @inheritdoc
* Clears all cache entries.
*
* @param string $prefix Unused. Included for interface compatibility.
* @return bool True on success.
* @since 25.0.0
*/
public function clear($prefix = ''): bool {
$this->cache = [];
$this->itemCount = 0;
return true;
}
//
// ArrayAccess Support
//
/**
* Determines if an offset exists in the cache.
*
* @param string $offset
* @return bool
* @since 25.0.0
*/
public function offsetExists($offset): bool {
@ -90,17 +193,21 @@ class CappedMemoryCache implements ICache, \ArrayAccess {
}
/**
* @inheritdoc
* @return T
* Retrieves the value at the specified offset.
*
* @param string $offset
* @return T|null
* @since 25.0.0
*/
#[\ReturnTypeWillChange]
public function &offsetGet($offset) {
// TODO: Any side effects with the new GC tracking?
return $this->cache[$offset];
}
/**
* @inheritdoc
* Sets the value at the specified offset.
*
* @param string $offset
* @param T $value
* @since 25.0.0
@ -110,35 +217,51 @@ class CappedMemoryCache implements ICache, \ArrayAccess {
}
/**
* @inheritdoc
* Unsets the value at the specified offset.
*
* @param string $offset
* @since 25.0.0
*/
public function offsetUnset($offset): void {
$this->remove($offset);
}
//
// Utilities
//
/**
* @return T[]
* Returns all cache data as an associative array.
*
* @return array<string, T>
* @since 25.0.0
*/
public function getData(): array {
return $this->cache;
}
/**
* Removes oldest entries if cache exceeds its capacity.
*
* @return void
* @since 25.0.0
*/
private function garbageCollect(): void {
while (count($this->cache) > $this->capacity) {
while ($this->itemCount > $this->capacity) {
reset($this->cache);
$key = key($this->cache);
$this->remove($key);
}
}
//
// Static Methods
//
/**
* @inheritdoc
* Indicates if this cache implementation is available.
*
* @return bool Always returns true.
* @since 25.0.0
*/
public static function isAvailable(): bool {