diff --git a/DataStorage/Cache/CacheFactory.php b/DataStorage/Cache/CacheFactory.php new file mode 100644 index 000000000..6760ba8fb --- /dev/null +++ b/DataStorage/Cache/CacheFactory.php @@ -0,0 +1,69 @@ + + * @author Dennis Eichhorn + * @copyright 2013 Dennis Eichhorn + * @license OMS License 1.0 + * @version 1.0.0 + * @link http://orange-management.com + */ +namespace phpOMS\DataStorage\Database\Connection; +use phpOMS\DataStorage\Cache\CacheInterface; +use phpOMS\DataStorage\Cache\FileCache; + + +/** + * Database connection factory. + * + * @category Framework + * @package phpOMS\DataStorage\Database + * @author OMS Development Team + * @author Dennis Eichhorn + * @license OMS License 1.0 + * @link http://orange-management.com + * @since 1.0.0 + */ +class CacheFactory +{ + + /** + * Constructor. + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + private function __construct() + { + } + + /** + * Create cache connection. + * + * Overwrites current connection if existing + * + * @param string[] $cacheData the basic database information for establishing a connection + * + * @return CacheInterface + * + * @throws \InvalidArgumentException Throws this exception if the database is not supported. + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + public static function create(array $cacheData) : CacheInterface + { + switch ($cacheData['type']) { + case 'file': + return new FileCache($cacheData); + break; + default: + throw new \InvalidArgumentException('Cache "' . $cacheData['type'] . '" is not supported.'); + } + } +} diff --git a/DataStorage/Cache/CacheInterface.php b/DataStorage/Cache/CacheInterface.php index 6e0890057..c53eb3b5b 100644 --- a/DataStorage/Cache/CacheInterface.php +++ b/DataStorage/Cache/CacheInterface.php @@ -14,6 +14,7 @@ * @link http://orange-management.com */ namespace phpOMS\DataStorage\Cache; +use phpOMS\Datatypes\Exception\InvalidEnumValue; /** * Cache interface. @@ -32,85 +33,106 @@ interface CacheInterface /** * Updating or adding cache data. * - * @param mixed $key Unique cache key - * @param mixed $value Cache value - * @param CacheStatus $type Cache type - * @param int $expire Valid duration (in s) + * @param mixed $key Unique cache key + * @param mixed $value Cache value + * @param int $expire Valid duration (in s). Negative expiration means no expiration. * * @return void * * @since 1.0.0 * @author Dennis Eichhorn */ - public function set($key, $value, CacheStatus $type = null, int $expire = 2592000); + public function set($key, $value, int $expire = -1); /** * Adding new data if it doesn't exist. * - * @param mixed $key Unique cache key - * @param mixed $value Cache value - * @param CacheStatus $type Cache type - * @param int $expire Valid duration (in s) + * @param mixed $key Unique cache key + * @param mixed $value Cache value + * @param int $expire Valid duration (in s) * * @return bool * * @since 1.0.0 * @author Dennis Eichhorn */ - public function add($key, $value, CacheStatus $type = null, int $expire = 2592000) : bool; + public function add($key, $value, int $expire = -1) : bool; /** * Get cache by key. * - * @param mixed $key Unique cache key - * @param CacheStatus $type Cache status/type + * @param mixed $key Unique cache key + * @param int $expire Valid duration (in s). In case the data needs to be newer than the defined expiration time. If the expiration date is larger than the defined expiration time and supposed to be expired it will not remove the outdated cache. * * @return mixed Cache value * * @since 1.0.0 * @author Dennis Eichhorn */ - public function get($key, CacheStatus $type = null); + public function get($key, int $expire = -1); /** * Remove value by key. * - * @param mixed $key Unique cache key - * @param CacheStatus $type Cache status/type + * @param mixed $key Unique cache key + * @param int $expire Valid duration (in s) * * @return bool * * @since 1.0.0 * @author Dennis Eichhorn */ - public function delete($key, CacheStatus $type = null) : bool; + public function delete($key, int $expire = -1) : bool; + + /** + * Removing all cache elements larger or equal to the expiration date. Call flushAll for removing persistent cache elements (expiration is negative) as well. + * + * @param int $expire Valid duration (in s) + * + * @return bool + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + public function flush(int $expire = 0) : bool; /** * Removing all elements from cache (invalidate cache). * - * @param CacheStatus $type Cache status/type - * - * @return void + * @return bool * * @since 1.0.0 * @author Dennis Eichhorn */ - public function flush(CacheStatus $type = null); + public function flushAll() : bool; + + /** + * Set cache status + * + * @param int $status Cache status + * + * @return void + * + * @throws InvalidEnumValue + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + public function setStatus(int $status); /** * Updating existing value/key. * - * @param mixed $key Unique cache key - * @param mixed $value Cache value - * @param CacheType $type Cache type - * @param int $expire Timestamp + * @param mixed $key Unique cache key + * @param mixed $value Cache value + * @param int $expire Valid duration (in s) * * @return bool * * @since 1.0.0 * @author Dennis Eichhorn */ - public function replace($key, $value, CacheType $type = null, int $expire = -1) : bool; + public function replace($key, $value, int $expire = -1) : bool; /** * Requesting cache stats. diff --git a/DataStorage/Cache/CachePool.php b/DataStorage/Cache/CachePool.php new file mode 100644 index 000000000..915432811 --- /dev/null +++ b/DataStorage/Cache/CachePool.php @@ -0,0 +1,143 @@ + + * @author Dennis Eichhorn + * @copyright 2013 Dennis Eichhorn + * @license OMS License 1.0 + * @version 1.0.0 + * @link http://orange-management.com + */ +namespace phpOMS\DataStorage\Cache; + +use phpOMS\Config\OptionsInterface; +use phpOMS\Config\OptionsTrait; +use phpOMS\DataStorage\Database\Connection\CacheFactory; + + +/** + * Cache class. + * + * Responsible for caching scalar data types and arrays. + * Caching HTML output and objects coming soon/is planned. + * + * @category Framework + * @package phpOMS\DataStorage\Cache + * @author OMS Development Team + * @author Dennis Eichhorn + * @license OMS License 1.0 + * @link http://orange-management.com + * @since 1.0.0 + */ +class CachePool implements OptionsInterface +{ + use OptionsTrait; + + /** + * MemCache instance. + * + * @var \phpOMS\DataStorage\Cache\CacheInterface + * @since 1.0.0 + */ + private $pool = null; + + + /** + * Constructor. + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + public function __construct() + { + } + + /** + * Add database. + * + * @param mixed $key Database key + * @param CacheInterface $cache Cache + * + * @return bool + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + public function add(string $key = 'core', CacheInterface $cache) : bool + { + if (isset($this->pool[$key])) { + return false; + } + + $this->pool[$key] = $cache; + + return true; + } + + /** + * Remove database. + * + * @param mixed $key Database key + * + * @return bool + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + public function remove(string $key) : bool + { + if (!isset($this->pool[$key])) { + return false; + } + + unset($this->pool[$key]); + + return true; + } + + /** + * Requesting caching instance. + * + * @param string $key Cache to request + * + * @return \phpOMS\DataStorage\Cache\CacheInterface + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + public function get(string $key) + { + if (!isset($this->pool[$key])) { + return false; + } + + return $this->pool[$key]; + } + + /** + * Create Cache. + * + * @param mixed $key Database key + * @param array $config Database config data + * + * @return bool + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + public function create(string $key, array $config) + { + if (isset($this->pool[$key])) { + return false; + } + + $this->pool[$key] = CacheFactory::create($config); + + return true; + } +} diff --git a/DataStorage/Cache/CacheStatus.php b/DataStorage/Cache/CacheStatus.php index b54719ae2..527f2c584 100644 --- a/DataStorage/Cache/CacheStatus.php +++ b/DataStorage/Cache/CacheStatus.php @@ -32,10 +32,7 @@ use phpOMS\Datatypes\Enum; */ abstract class CacheStatus extends Enum { - const INACTIVE = 0; /* Caching is disabled */ - const ERROR = 1; /* Caching failed */ - const MEMCACHE = 2; /* Caching OK */ - const FILECACHE = 3; /* Caching OK */ - const REDISCACHE = 4; /* Caching OK */ - const WINCACHE = 5; /* Caching OK */ + const ACTIVE = 0; + const INACTIVE = 1; + const ERROR = 2; } diff --git a/DataStorage/Cache/CacheType.php b/DataStorage/Cache/CacheType.php index dd1734e27..36960335e 100644 --- a/DataStorage/Cache/CacheType.php +++ b/DataStorage/Cache/CacheType.php @@ -32,9 +32,11 @@ use phpOMS\Datatypes\Enum; */ abstract class CacheType extends Enum { - const _NUMERIC = 0; /* Data is numeric */ + const _INT = 0; /* Data is integer */ const _STRING = 1; /* Data is string */ const _ARRAY = 2; /* Data is array */ - const _OBJECT = 3; /* Data is object */ - const _HEX = 4; /* Data is object */ + const _SERIALIZABLE = 3; /* Data is object */ + const _JSONSERIALIZABLE = 6; + const _FLOAT = 4; /* Data is float */ + const _BOOL = 5; /* Data is float */ } diff --git a/DataStorage/Cache/FileCache.php b/DataStorage/Cache/FileCache.php index 5639d871d..582d20115 100644 --- a/DataStorage/Cache/FileCache.php +++ b/DataStorage/Cache/FileCache.php @@ -15,7 +15,9 @@ */ namespace phpOMS\DataStorage\Cache; +use phpOMS\Datatypes\Exception\InvalidEnumValue; use phpOMS\System\File\Local\Directory; +use phpOMS\System\File\Local\File; /** * MemCache class. @@ -33,13 +35,21 @@ use phpOMS\System\File\Local\Directory; class FileCache implements CacheInterface { + /** + * Delimiter for cache meta data + * + * @var string + * @since 1.0.0 + */ + const DELIM = '$'; + /** * Cache path. * * @var string * @since 1.0.0 */ - const CACHE_PATH = __DIR__ . '/../../../Cache'; + private $cachePath = __DIR__ . '/../../../Cache'; /** * Only cache if data is larger than threshold (0-100). @@ -50,46 +60,55 @@ class FileCache implements CacheInterface private $threshold = 50; /** - * {@inheritdoc} + * Cache status. + * + * @var int + * @since 1.0.0 */ - public function set($key, $value, CacheStatus $type = null, int $expire = 2592000) + private $status = CacheStatus::ACTIVE; + + /** + * Constructor + * + * @param array $config Cache config + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + public function __construct(array $config) { + $path = $config['path'] ?? ''; + + if (!file_exists($path)) { + mkdir($path); + } + + $this->cachePath = realpath($path); } /** * {@inheritdoc} */ - public function add($key, $value, CacheStatus $type = null, int $expire = 2592000) : bool + public function flushAll() : bool { + if ($this->status !== CacheStatus::ACTIVE) { + return false; + } + + array_map('unlink', glob($this->cachePath . '/*')); + + return true; } /** * {@inheritdoc} */ - public function get($key, CacheStatus $type = null) - { - } + public function setStatus(int $status) { + if(!CacheStatus::isValidValue($status)) { + throw new InvalidEnumValue($status); + } - /** - * {@inheritdoc} - */ - public function delete($key, CacheStatus $type = null) : bool - { - } - - /** - * {@inheritdoc} - */ - public function flush(CacheStatus $type = null) - { - array_map('unlink', glob(self::CACHE_PATH . '/*')); - } - - /** - * {@inheritdoc} - */ - public function replace($key, $value, CacheType $type = null, int $expire = -1) : bool - { + $this->status = $status; } /** @@ -97,10 +116,11 @@ class FileCache implements CacheInterface */ public function stats() : array { - $stats = []; - $stats['count'] = Directory::getFileCount(self::CACHE_PATH); - - // size, avg. last change compared to now + $stats = []; + $stats['status'] = $this->status; + $stats['count'] = Directory::count($this->cachePath); + $stats['size'] = Directory::size($this->cachePath); + $stats['changed'] = Directory::changed($this->cachePath); return $stats; } @@ -113,4 +133,279 @@ class FileCache implements CacheInterface return $this->threshold; } + /** + * {@inheritdoc} + */ + public function set($key, $value, int $expire = -1) + { + if($this->status !== CacheStatus::ACTIVE) { + return false; + } + + $path = File::sanitize($key); + + file_put_contents($this->cachePath . '/' . $path, $this->build($value, $expire)); + + return false; + } + + /** + * {@inheritdoc} + */ + public function add($key, $value, int $expire = -1) : bool + { + if($this->status !== CacheStatus::ACTIVE) { + return false; + } + + $path = File::sanitize($key); + + if (!file_exists($this->cachePath . '/' . $path)) { + file_put_contents($this->cachePath . '/' . $path, $this->build($value, $expire)); + + return true; + } + + return false; + } + + /** + * 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 + * @param int $expire Expire date of the cached data + * + * @return string + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + private function build($value, int $expire) : string + { + $type = $this->dataType($value); + $raw = $this->stringify($value, $type); + + return $type . self::DELIM . $expire . self::DELIM . $raw; + } + + /** + * Analyze caching data type. + * + * @param mixed $value Data to cache + * + * @return int + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + private function dataType($value) : int + { + if (is_int($value)) { + return CacheType::_INT; + } elseif (is_float($value)) { + return CacheType::_FLOAT; + } elseif (is_string($value)) { + return CacheType::_STRING; + } elseif (is_bool($value)) { + return CacheType::_BOOL; + } elseif (is_array($value)) { + return CacheType::_ARRAY; + } elseif ($value instanceof \Serializable) { + return CacheType::_SERIALIZABLE; + } elseif ($value instanceof \JsonSerializable) { + return CacheType::_JSONSERIALIZABLE; + } + + throw new \InvalidArgumentException('Invalid value'); + } + + /** + * Create string representation of data for storage + * + * @param mixed $value Value of the data + * @param int $type Type of the cache data + * + * @return string + * + * @throws InvalidEnumValue + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + private function stringify($value, int $type) : string + { + if ($type === CacheType::_INT || $type === CacheType::_FLOAT || $type === CacheType::_STRING || $type === CacheType::_BOOL) { + return (string) $value; + } elseif ($type === CacheType::_ARRAY) { + return json_encode($value); + } elseif ($type === CacheType::_SERIALIZABLE) { + return get_class($value) . self::DELIM . $value->serialize(); + } elseif ($type === CacheType::_JSONSERIALIZABLE) { + return get_class($value) . self::DELIM . $value->jsonSerialize(); + } + + throw new InvalidEnumValue($type); + } + + /** + * Get expire offset + * + * @param string $raw Raw data + * + * @return int + * + * @since 1.0.0 + * @author Dennis Eichhorn + */ + private function getExpire(string $raw) : int + { + $expireStart = strpos($raw, self::DELIM); + $expireEnd = strpos($raw, self::DELIM, $expireStart); + + return (int) substr($raw, $expireStart, $expireEnd); + } + + /** + * {@inheritdoc} + */ + public function get($key, int $expire = -1) + { + if($this->status !== CacheStatus::ACTIVE) { + return null; + } + + $name = Directory::sanitize($key); + $created = Directory::created($path = $this->cachePath . '/' . $name)->getTimestamp(); + $now = time(); + + if ($expire >= 0 && $created + $expire > $now) { + return null; + } + + $raw = file_get_contents($path); + $type = $raw[0]; + + $expireStart = strpos($raw, self::DELIM); + $expireEnd = strpos($raw, self::DELIM, $expireStart); + $cacheExpire = substr($raw, $expireStart, $expireEnd); + + if ($cacheExpire >= 0 && $created + $cacheExpire > $now) { + $this->delete($key); + + return null; + } + + $value = null; + + switch ($type) { + case CacheType::_INT: + $value = (int) substr($raw, $expireEnd + 1); + break; + case CacheType::_FLOAT: + $value = (float) substr($raw, $expireEnd + 1); + break; + case CacheType::_BOOL: + $value = (bool) substr($raw, $expireEnd + 1); + break; + case CacheType::_STRING: + $value = substr($raw, $expireEnd + 1); + break; + case CacheType::_ARRAY: + $value = json_decode(substr($raw, $expireEnd + 1)); + break; + case CacheType::_SERIALIZABLE: + case CacheType::_JSONSERIALIZABLE: + $namespaceStart = strpos($raw, self::DELIM, $expireEnd); + $namespaceEnd = strpos($raw, self::DELIM, $namespaceStart); + $namespace = substr($raw, $namespaceStart, $namespaceEnd); + + $value = $namespace::unserialize(substr($raw, $namespaceEnd + 1)); + break; + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function delete($key, int $expire = -1) : bool + { + if($this->status !== CacheStatus::ACTIVE) { + return false; + } + + $name = File::sanitize($key); + $path = $this->cachePath . '/' . $name; + + if ($expire < 0 && file_exists($path)) { + unlink($path); + + return true; + } + + if ($expire >= 0) { + $created = Directory::created($name)->getTimestamp(); + $now = time(); + $raw = file_get_contents($path); + $expireStart = strpos($raw, self::DELIM); + $expireEnd = strpos($raw, self::DELIM, $expireStart); + $cacheExpire = substr($raw, $expireStart, $expireEnd); + + if ($cacheExpire >= 0 && $created + $cacheExpire > $now) { + unlink($path); + + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function flush(int $expire = 0) : bool + { + if ($this->status !== CacheStatus::ACTIVE) { + return false; + } + + $dir = new Directory($this->cachePath); + $now = time(); + + foreach ($dir as $file) { + if ($file instanceof File) { + $created = $file->getCreatedAt()->getTimestamp(); + if ( + ($expire >= 0 && $created + $expire < $now) + || ($expire < 0 && $created + $this->getExpire($file->getContent()) < $now) + ) { + unlink($file->getPath()); + } + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function replace($key, $value, int $expire = -1) : bool + { + if($this->status !== CacheStatus::ACTIVE) { + return false; + } + + $path = File::sanitize($key); + + if (file_exists($this->cachePath . '/' . $path)) { + file_put_contents($this->cachePath . '/' . $path, $this->build($value, $expire)); + + return true; + } + + return false; + } } diff --git a/DataStorage/Cache/Pool.php b/DataStorage/Cache/Pool.php deleted file mode 100644 index 3d52331e8..000000000 --- a/DataStorage/Cache/Pool.php +++ /dev/null @@ -1,131 +0,0 @@ - - * @author Dennis Eichhorn - * @copyright 2013 Dennis Eichhorn - * @license OMS License 1.0 - * @version 1.0.0 - * @link http://orange-management.com - */ -namespace phpOMS\DataStorage\Cache; - -use phpOMS\Config\OptionsInterface; -use phpOMS\Config\OptionsTrait; - - -/** - * Cache class. - * - * Responsible for caching scalar data types and arrays. - * Caching HTML output and objects coming soon/is planned. - * - * @category Framework - * @package phpOMS\DataStorage\Cache - * @author OMS Development Team - * @author Dennis Eichhorn - * @license OMS License 1.0 - * @link http://orange-management.com - * @since 1.0.0 - */ -class Pool implements OptionsInterface -{ - use OptionsTrait; - - /** - * MemCache instance. - * - * @var \phpOMS\DataStorage\Cache\MemCache - * @since 1.0.0 - */ - private $memc = null; - - /** - * RedisCache instance. - * - * @var \phpOMS\DataStorage\Cache\RedisCache - * @since 1.0.0 - */ - private $redisc = null; - - /** - * RedisCache instance. - * - * @var \phpOMS\DataStorage\Cache\WinCache - * @since 1.0.0 - */ - private $winc = null; - - /** - * FileCache instance. - * - * @var \phpOMS\DataStorage\Cache\FileCache - * @since 1.0.0 - */ - private $filec = null; - - - /** - * Constructor. - * - * @since 1.0.0 - * @author Dennis Eichhorn - */ - public function __construct() - { - } - - /** - * Requesting caching instance. - * - * @param int $type Cache to request - * - * @return \phpOMS\DataStorage\Cache\MemCache|\phpOMS\DataStorage\Cache\FileCache|null - * - * @since 1.0.0 - * @author Dennis Eichhorn - */ - public function get(int $type = null) - { - if (($type === null || $type === CacheStatus::MEMCACHE) && $this->memc !== null) { - return $this->memc; - } - - if (($type === null || $type === CacheStatus::REDISCACHE) && $this->redisc !== null) { - return $this->redisc; - } - - if (($type === null || $type === CacheStatus::WINCACHE) && $this->winc !== null) { - return $this->winc; - } - - if (($type === null || $type === CacheStatus::FILECACHE) && $this->filec !== null) { - return $this->filec; - } - - return new NullCache(); - } - - /** - * {@inheritdoc} - */ - public function add(int $type, CacheInterface $cache) - { - if (($type === null || $type === CacheStatus::MEMCACHE) && $this->memc !== null) { - $this->memc = $cache; - } elseif (($type === null || $type === CacheStatus::REDISCACHE) && $this->redisc !== null) { - $this->redisc = $cache; - } elseif (($type === null || $type === CacheStatus::WINCACHE) && $this->winc !== null) { - $this->winc = $cache; - } elseif (($type === null || $type === CacheStatus::FILECACHE) && $this->filec !== null) { - $this->filec = $cache; - } else { - throw new \Exception('Invalid type'); - } - } -}