diff --git a/DataStorage/Cache/Connection/ConnectionAbstract.php b/DataStorage/Cache/Connection/ConnectionAbstract.php index 9d5dea96c..dc7b01ed3 100644 --- a/DataStorage/Cache/Connection/ConnectionAbstract.php +++ b/DataStorage/Cache/Connection/ConnectionAbstract.php @@ -16,6 +16,7 @@ namespace phpOMS\DataStorage\Cache\Connection; use phpOMS\DataStorage\Cache\CacheStatus; use phpOMS\DataStorage\Cache\CacheType; +use phpOMS\DataStorage\Cache\Connection\CacheValueType; /** * Cache handler. @@ -163,20 +164,36 @@ abstract class ConnectionAbstract implements ConnectionInterface } /** - * Parse values for cache storage + * Analyze caching data type. * - * @param mixed $value Value to parse + * @param mixed $value Data to cache * - * @return mixed + * @return int Returns the cache type for a value + * + * @throws \InvalidArgumentException This exception is thrown if an unsupported datatype is used * * @since 1.0.0 */ - protected function parseValue($value) + protected function dataType($value) : int { - if (\is_array($value)) { - return \json_encode($value); + if (\is_int($value)) { + return CacheValueType::_INT; + } elseif (\is_float($value)) { + return CacheValueType::_FLOAT; + } elseif (\is_string($value)) { + return CacheValueType::_STRING; + } elseif (\is_bool($value)) { + return CacheValueType::_BOOL; + } elseif (\is_array($value)) { + return CacheValueType::_ARRAY; + } elseif ($value === null) { + return CacheValueType::_NULL; + } elseif ($value instanceof \Serializable) { + return CacheValueType::_SERIALIZABLE; + } elseif ($value instanceof \JsonSerializable) { + return CacheValueType::_JSONSERIALIZABLE; } - return $value; + throw new \InvalidArgumentException('Invalid value type.'); } } diff --git a/DataStorage/Cache/Connection/FileCache.php b/DataStorage/Cache/Connection/FileCache.php index b7cc4ca64..2ab539cd0 100644 --- a/DataStorage/Cache/Connection/FileCache.php +++ b/DataStorage/Cache/Connection/FileCache.php @@ -194,40 +194,6 @@ class FileCache extends ConnectionAbstract return $type . self::DELIM . $expire . self::DELIM . $raw; } - /** - * Analyze caching data type. - * - * @param mixed $value Data to cache - * - * @return int Returns the cache type for a value - * - * @throws \InvalidArgumentException This exception is thrown if an unsupported datatype is used - * - * @since 1.0.0 - */ - private function dataType($value) : int - { - if (\is_int($value)) { - return CacheValueType::_INT; - } elseif (\is_float($value)) { - return CacheValueType::_FLOAT; - } elseif (\is_string($value)) { - return CacheValueType::_STRING; - } elseif (\is_bool($value)) { - return CacheValueType::_BOOL; - } elseif (\is_array($value)) { - return CacheValueType::_ARRAY; - } elseif ($value === null) { - return CacheValueType::_NULL; - } elseif ($value instanceof \Serializable) { - return CacheValueType::_SERIALIZABLE; - } elseif ($value instanceof \JsonSerializable) { - return CacheValueType::_JSONSERIALIZABLE; - } - - throw new \InvalidArgumentException('Invalid value type.'); - } - /** * Create string representation of data for storage * @@ -350,6 +316,15 @@ class FileCache extends ConnectionAbstract case CacheValueType::_NULL: return null; case CacheValueType::_JSONSERIALIZABLE: + $namespaceStart = (int) \strpos($raw, self::DELIM, $expireEnd); + $namespaceEnd = (int) \strpos($raw, self::DELIM, $namespaceStart + 1); + $namespace = \substr($raw, $namespaceStart + 1, $namespaceEnd - $namespaceStart - 1); + + if ($namespace === false) { + return null; + } + + return new $namespace(); case CacheValueType::_SERIALIZABLE: $namespaceStart = (int) \strpos($raw, self::DELIM, $expireEnd); $namespaceEnd = (int) \strpos($raw, self::DELIM, $namespaceStart + 1); diff --git a/DataStorage/Cache/Connection/MemCached.php b/DataStorage/Cache/Connection/MemCached.php index ab8dafb82..508b45579 100644 --- a/DataStorage/Cache/Connection/MemCached.php +++ b/DataStorage/Cache/Connection/MemCached.php @@ -170,7 +170,7 @@ class MemCached extends ConnectionAbstract return false; } - $value = $this->parseValue($value); + // todo: handle parsing return $this->con->replace($key, $value, \max($expire, 0)); } diff --git a/DataStorage/Cache/Connection/RedisCache.php b/DataStorage/Cache/Connection/RedisCache.php index 290d464a8..62e421d59 100644 --- a/DataStorage/Cache/Connection/RedisCache.php +++ b/DataStorage/Cache/Connection/RedisCache.php @@ -17,6 +17,8 @@ namespace phpOMS\DataStorage\Cache\Connection; use phpOMS\DataStorage\Cache\CacheStatus; use phpOMS\DataStorage\Cache\CacheType; use phpOMS\DataStorage\Cache\Exception\InvalidConnectionConfigException; +use phpOMS\Stdlib\Base\Exception\InvalidEnumValue; +use phpOMS\DataStorage\Cache\Connection\CacheValueType; /** * RedisCache class. @@ -33,6 +35,14 @@ class RedisCache extends ConnectionAbstract */ protected string $type = CacheType::REDIS; + /** + * Delimiter for cache meta data + * + * @var string + * @since 1.0.0 + */ + private const DELIM = '$'; + /** * Constructor * @@ -95,21 +105,11 @@ class RedisCache extends ConnectionAbstract return; } - if (!(\is_scalar($value) || $value === null || \is_array($value) || $value instanceof \JsonSerializable || $value instanceof \Serializable)) { - throw new \InvalidArgumentException(); - } - - if (\is_bool($value)) { - $value = $value ? 'true' : 'false'; - } elseif ($value === null) { - $value = 'null'; - } - if ($expire > 0) { - $this->con->set($key, $value, $expire); + $this->con->set($key, $this->build($value), $expire); } - $this->con->set($key, $value); + $this->con->set($key, $this->build($value)); } /** @@ -121,23 +121,11 @@ class RedisCache extends ConnectionAbstract return false; } - // todo: pull out - if (!(\is_scalar($value) || $value === null || \is_array($value) || $value instanceof \JsonSerializable || $value instanceof \Serializable)) { - throw new \InvalidArgumentException(); - } - - // todo: pull out - if (\is_bool($value)) { - $value = $value ? 'true' : 'false'; - } elseif ($value === null) { - $value = 'null'; - } - if ($expire > 0) { - return $this->con->setNx($key, $value, $expire); + return $this->con->setNx($key, $this->build($value), $expire); } - return $this->con->setNx($key, $value); + return $this->con->setNx($key, $this->build($value)); } /** @@ -151,12 +139,10 @@ class RedisCache extends ConnectionAbstract $result = $this->con->get($key); - if ($result === 'true') { - $result = true; - } elseif ($result === 'false') { - $result = false; - } elseif ($result === 'null') { - $result = null; + if (\is_string($result)) { + $type = (int) $result[0]; + $start = (int) \strpos($result, self::DELIM); + $result = $this->reverseValue($type, $result, $start); } return $result; @@ -211,10 +197,10 @@ class RedisCache extends ConnectionAbstract return false; } - $value = $this->parseValue($value); + // todo: parse value if ($this->con->exists($key) > 0) { - $this->set($key, $value, $expire); + $this->set($key, $this->build($value), $expire); return true; } @@ -258,4 +244,97 @@ class RedisCache extends ConnectionAbstract { $this->close(); } + + /** + * Removing all cache elements larger or equal to the expiration date. Call flushAll for removing persistent cache elements (expiration is negative) as well. + * + * @param mixed $value Data to cache + * + * @return mixed + * + * @since 1.0.0 + */ + private function build($value) + { + $type = $this->dataType($value); + $raw = $this->cachify($value, $type); + + return \is_string($raw) ? $type . self::DELIM . $raw : $raw; + } + + /** + * Create string representation of data for storage + * + * @param mixed $value Value of the data + * @param int $type Type of the cache data + * + * @return mixed + * + * @throws InvalidEnumValue This exception is thrown if an unsupported cache value type is used + * + * @since 1.0.0 + */ + private function cachify($value, int $type) + { + if (\is_float($value) || \is_int($value)) { + return $value; + } elseif ($type === CacheValueType::_BOOL) { + return (string) ((int) $value); + } elseif ($type === CacheValueType::_ARRAY) { + return (string) \json_encode($value); + } elseif ($type === CacheValueType::_SERIALIZABLE) { + return \get_class($value) . self::DELIM . $value->serialize(); + } elseif ($type === CacheValueType::_JSONSERIALIZABLE) { + return \get_class($value) . self::DELIM . ((string) \json_encode($value->jsonSerialize())); + } elseif ($type === CacheValueType::_NULL) { + return ''; + } + + throw new InvalidEnumValue($type); + } + + /** + * Parse cached value + * + * @param int $type Cached value type + * @param mixed $raw Cached value + * @param int $start Value start position + * + * @return mixed + * + * @since 1.0.0 + */ + private function reverseValue(int $type, $raw, int $start) + { + switch ($type) { + case \is_int($raw): + case \is_float($raw): + return $raw; + case CacheValueType::_BOOL: + return (bool) \substr($raw, $start + 1); + case CacheValueType::_STRING: + return \substr($raw, $start + 1); + case CacheValueType::_ARRAY: + $array = \substr($raw, $start + 1); + return \json_decode($array === false ? '[]' : $array, true); + case CacheValueType::_NULL: + return null; + case CacheValueType::_JSONSERIALIZABLE: + case CacheValueType::_SERIALIZABLE: + $namespaceStart = (int) \strpos($raw, self::DELIM, $start); + $namespaceEnd = (int) \strpos($raw, self::DELIM, $namespaceStart + 1); + $namespace = \substr($raw, $namespaceStart + 1, $namespaceEnd - $namespaceStart - 1); + + if ($namespace === false) { + return null; + } + + $obj = new $namespace(); + $obj->unserialize(\substr($raw, $namespaceEnd + 1)); + + return $obj; + default: + return null; + } + } } diff --git a/tests/DataStorage/Cache/Connection/FileCacheTest.php b/tests/DataStorage/Cache/Connection/FileCacheTest.php index 38b1286de..097cce834 100644 --- a/tests/DataStorage/Cache/Connection/FileCacheTest.php +++ b/tests/DataStorage/Cache/Connection/FileCacheTest.php @@ -108,7 +108,7 @@ class FileCacheTest extends \PHPUnit\Framework\TestCase self::assertEquals('abc', $this->cache->get('key7')->val); $this->cache->set('key8', new FileCacheJsonSerializable()); - self::assertEquals('abc', $this->cache->get('key8')->val); + self::assertEquals('asdf', $this->cache->get('key8')->val); } /** diff --git a/tests/DataStorage/Cache/Connection/MemCachedTest.php b/tests/DataStorage/Cache/Connection/MemCachedTest.php index 7fa4e21bf..7905e3ee9 100644 --- a/tests/DataStorage/Cache/Connection/MemCachedTest.php +++ b/tests/DataStorage/Cache/Connection/MemCachedTest.php @@ -109,7 +109,7 @@ class MemCachedTest extends \PHPUnit\Framework\TestCase self::assertEquals('abc', $this->cache->get('key7')->val); $this->cache->set('key8', new FileCacheJsonSerializable()); - self::assertEquals('asdf', $this->cache->get('key8')->val); // @todo: different from file cache fix! + self::assertEquals('asdf', $this->cache->get('key8')->val); } /** diff --git a/tests/DataStorage/Cache/Connection/RedisCacheTest.php b/tests/DataStorage/Cache/Connection/RedisCacheTest.php index d82572dd1..8ece095e1 100644 --- a/tests/DataStorage/Cache/Connection/RedisCacheTest.php +++ b/tests/DataStorage/Cache/Connection/RedisCacheTest.php @@ -105,7 +105,7 @@ class RedisCacheTest extends \PHPUnit\Framework\TestCase self::assertEquals('abc', $this->cache->get('key7')->val); $this->cache->set('key8', new FileCacheJsonSerializable()); - self::assertEquals('asdf', $this->cache->get('key8')->val); // todo: different from file cache fix! + self::assertEquals('asdf', $this->cache->get('key8')->val); } /**