aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/StudipCachedArray.php
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+studip@gmail.com>2022-01-21 13:09:26 +0000
committerJan-Hendrik Willms <tleilax+studip@gmail.com>2022-01-21 13:09:26 +0000
commit4df9f170f16d60edc7437c9161c8668dc229b033 (patch)
tree1009cffc08fd5ad331328e1bb91b8c40a579debb /lib/classes/StudipCachedArray.php
parent88a5f717f90e0e2c31599d159144559ae5be795e (diff)
fixes #237
Diffstat (limited to 'lib/classes/StudipCachedArray.php')
-rw-r--r--lib/classes/StudipCachedArray.php252
1 files changed, 66 insertions, 186 deletions
diff --git a/lib/classes/StudipCachedArray.php b/lib/classes/StudipCachedArray.php
index 1d07ecf..221b76c 100644
--- a/lib/classes/StudipCachedArray.php
+++ b/lib/classes/StudipCachedArray.php
@@ -3,60 +3,43 @@
* This class represents an array in cache and removes the neccessity to
* encode/decode and store the data after every change.
*
- * Caching is handled by splitting the data into partitions based on the key.
- *
* @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
* @license GPL2 or any later version
* @since Stud.IP 5.0
- *
- * @todo Automagic partition size?
*/
-class StudipCachedArray implements ArrayAccess, Countable
+class StudipCachedArray implements ArrayAccess
{
- const ENCODE_JSON = 0;
- const ENCODE_SERIALIZE = 1;
-
protected $key;
protected $cache;
- protected $partitions;
- protected $partition_by;
- protected $encoding;
+ protected $duration;
protected $data = [];
/**
* Constructs the cached array
*
- * @param string $key Cache key where the array is/should be stored
- * @param mixed $partition_by Defines the partitioning, this may either be
- * an int which will be length of the substring
- * of the given chache offset or a callable which
- * will return the partition key.
+ * @param string $key Cache key where the array is/should be stored
+ * an int which will be length of the substring
+ * of the given chache offset or a callable which
+ * will return the partition key.
+ * @param int $duration Duration in seconds for which the item shall be
+ * stored
*/
- public function __construct($key, $partition_by = 1, $encoding = self::ENCODE_JSON, StudipCache $cache = null)
+ public function __construct(string $key, int $duration = StudipCache::DEFAULT_EXPIRATION)
{
- if (!is_callable($partition_by) && !is_int($partition_by)) {
- throw new Exception('Parameter $partition_by may only be a number or a callable if set');
- }
- if (ctype_digit($partition_by) && $partition_by <= 1) {
- throw new Exception('Parameter $partition_by must be positive and not zero');
- }
+ $this->key = self::class . "/{$key}";
+ $this->cache = StudipCacheFactory::getCache();
+ $this->duration = $duration;
- $this->key = $key;
- $this->partition_by = $partition_by;
- $this->encoding = $encoding;
- $this->setCache($cache ?? StudipCacheFactory::getCache());
+ $this->reset();
}
/**
- * Sets the cache for this array and resets internal states.
- *
- * @param StudipCache $cache
+ * Clears cached values from memory, but does not remove them from the cache.
*/
- public function setCache(StudipCache $cache)
+ public function reset(): void
{
- $this->cache = $cache;
- $this->reset();
+ $this->data = [];
}
/**
@@ -65,12 +48,10 @@ class StudipCachedArray implements ArrayAccess, Countable
* @param string $offset Offset
* @return bool
*/
- public function offsetExists($offset)
+ public function offsetExists($offset): bool
{
- return array_key_exists(
- $offset,
- $this->loadData($offset)
- );
+ $this->loadData($offset);
+ return isset($this->data[$offset]);
}
/**
@@ -81,7 +62,8 @@ class StudipCachedArray implements ArrayAccess, Countable
*/
public function offsetGet($offset)
{
- return $this->loadData($offset)[$offset] ?? null;
+ $this->loadData($offset);
+ return $this->data[$offset];
}
/**
@@ -90,192 +72,90 @@ class StudipCachedArray implements ArrayAccess, Countable
* @param string $offset Offset
* @param mixed $value Value
*/
- public function offsetSet($offset, $value)
- {
- $this->loadData($offset)[$offset] = $value;
-
- $this->storeData($offset);
- }
-
- /**
- * Unsets the value at a given offset
- *
- * @param string $offset Offset
- */
- public function offsetUnset($offset)
+ public function offsetSet($offset, $value): void
{
- $data = &$this->loadData($offset);
-
- if (array_key_exists($offset, $data)) {
- unset($data[$offset]);
- $this->storeData($offset);
+ if ($offset === null) {
+ throw new Exception('Cannot push to cached array, use StudipCachedArray instead');
}
- }
- /**
- * Counts all values in chache.
- * @return int
- */
- public function count()
- {
- return array_sum($this->partitions);
- }
+ if (!isset($this->data[$offset]) || $this->data[$offset] !== $value) {
+ $this->data[$offset] = $value;
- /**
- * Clears all data.
- */
- public function clear()
- {
- foreach (array_keys($this->partitions) as $partition) {
- $this->cache->expire($this->getPartitionKey($partition));
- }
-
- $this->partitions = [];
- $this->data = [];
-
- $this->storeData();
- }
-
- /**
- * Returns all items in cache as an array.
- * @return array
- */
- public function getArrayCopy()
- {
- $result = [];
- foreach (array_keys($this->partitions) as $partition) {
- foreach ($this->loadData($partition) as $key => $value) {
- $result[$key] = $value;
- }
+ $this->storeData($offset);
}
- return $result;
}
/**
- * Loads all partitions of this cache and resets the data array
- * @return array
+ * Unsets the value at a given offset
+ *
+ * @param string $offset Offset
*/
- public function reset()
+ public function offsetUnset($offset): void
{
- $this->data = [];
- $this->partitions = [];
-
- $cached = $this->cache->read($this->key);
- if ($cached) {
- $this->partitions = $this->decode($cached);
- }
+ $this->cache->expire($this->getCacheKey($offset));
+ unset($this->data[$offset]);
}
/**
* Loads the data from cache.
+ *
+ * @param string $offset Offset to load
*/
- protected function &loadData($offset)
+ protected function loadData(string $offset)
{
- $partition = $this->getPartition($offset);
- if (!isset($this->data[$partition])) {
- $cached = $this->cache->read($this->getPartitionKey($offset));
- if ($cached) {
- $this->data[$partition] = $this->decode($cached);
- } else {
- $this->data[$partition] = [];
- }
+ if (!array_key_exists($offset, $this->data)) {
+ $cached = $this->cache->read($this->getCacheKey($offset));
+ $this->data[$offset] = $this->swapNullAndFalse($cached);
}
- return $this->data[$partition];
+ return $this->data[$offset];
}
/**
* Stores the data back to the cache.
+ * Data needs to be wrapped in another array so that we can correctly read
+ * back a value of "false".
+ *
+ * @param string $offset Offset to store
*/
- protected function storeData($offset = null)
+ protected function storeData(string $offset): void
{
- $partition = false;
-
- if ($offset !== null) {
- $partition = $this->getPartition($offset);
- if (!array_key_exists($partition, $this->partitions) || count($this->data[$partition]) > 0) {
- $this->partitions[$partition] = count($this->data[$partition]);
- } elseif (array_key_exists($partition, $this->partitions) && count($this->data[$partition]) === 0) {
- unset($this->partitions[$partition]);
- }
- }
-
- foreach ($this->data as $p => $data) {
- if ($partition === false || $p == $partition) {
- $key = $this->getPartitionKey($p);
-
- if (count($data) === 0) {
- $this->cache->expire($key);
- } else {
- $this->cache->write($key, $this->encode($data));
- }
- }
- }
-
- if (count($this->partitions) === 0) {
- $this->cache->expire($this->key);
- } else {
- $this->cache->write($this->key, $this->encode($this->partitions));
- }
+ $this->cache->write(
+ $this->getCacheKey($offset),
+ $this->swapNullAndFalse($this->data[$offset]),
+ $this->duration
+ );
}
/**
- * Encodes the given data based on the set encoding mechanism.
- * @param mixed $data
+ * Returns the cache key for a specific offset.
+ *
+ * @param string $offset Offset of the cached item
* @return string
*/
- protected function encode($data)
+ private function getCacheKey(string $offset): string
{
- if ($this->encoding === self::ENCODE_JSON) {
- return json_encode($data);
- }
-
- if ($this->encoding === self::ENCODE_SERIALIZE) {
- return serialize($data);
- }
-
- throw new Exception('Unknown encoding type');
+ return rtrim($this->key, '/') . "/{$offset}";
}
/**
- * Decodes the given data based on the set encoding mechanism.
- * @param string $data
+ * Swaps null and false for a value because the Stud.IP cache will return
+ * false if a cached item is not found instead of null.
+ *
+ * @param mixed $value Value to swap if appropriate
+ *
* @return mixed
*/
- protected function decode($data)
+ private function swapNullAndFalse($value)
{
- if ($this->encoding === self::ENCODE_JSON) {
- return json_decode($data, true);
+ if ($value === null) {
+ return false;
}
- if ($this->encoding === self::ENCODE_SERIALIZE) {
- return unserialize($data);
- }
-
- throw new Exception('Unknown decoding type');
- }
-
- /**
- * Extracts the partition from the key.
- * @param string $offset
- * @return string partition
- */
- protected function getPartition($offset)
- {
- if (is_callable($this->partition_by)) {
- return call_user_func($this->partition_by, $offset);
+ if ($value === false) {
+ return null;
}
- return mb_substr($offset, 0, $this->partition_by);
- }
- /**
- * Returns the partition key for storage.
- *
- * @param string $offset
- * @return string
- */
- protected function getPartitionKey($offset)
- {
- return rtrim($this->key, '/') . '/' . $this->getPartition($offset);
+ return $value;
}
}