00f100/fcphp-cache
Advanced tools
| vendor | ||
| tests/coverage | ||
| composer.lock | ||
| docker-compose.yml | ||
| tests/var |
| language: php | ||
| php: | ||
| - 7.2 | ||
| before_install: | ||
| - sudo apt-get update -qq | ||
| - sudo apt-get install build-essential git -y | ||
| - git clone -b stable https://github.com/jedisct1/libsodium.git | ||
| - cd libsodium && sudo ./configure && sudo make check && sudo make install && cd .. | ||
| install: | ||
| # Manually install libsodium, because the TravicCi image doesn't provide PHP7.2 with libsodium | ||
| - pecl install libsodium | ||
| - echo "extension=sodium.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini | ||
| before_script: | ||
| - echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini | ||
| - travis_retry composer self-update | ||
| - travis_retry composer install --no-interaction --prefer-source --dev | ||
| script: | ||
| - vendor/bin/phpunit --coverage-clover=coverage.xml | ||
| after_success: | ||
| - bash <(curl -s https://codecov.io/bash) | ||
| services: | ||
| - redis-server | ||
| { | ||
| "name": "00f100/fcphp-cache", | ||
| "type": "package", | ||
| "version": "0.3.2", | ||
| "description": "Cache Index for FcPhp", | ||
| "keywords": ["cache", "fcphp", "php7", "php72", "crypto", "redis"], | ||
| "homepage": "https://github.com/00f100/fcphp-cache", | ||
| "authors": [ | ||
| { | ||
| "name": "João Moraes", | ||
| "email": "joaomoraesbr@gmail.com", | ||
| "homepage": "https://github.com/00f100" | ||
| } | ||
| ], | ||
| "require": { | ||
| "php": ">=7.2", | ||
| "00f100/fcphp-redis": "0.*", | ||
| "00f100/fcphp-crypto": "0.*", | ||
| "ext-redis": "*" | ||
| }, | ||
| "require-dev": { | ||
| "00f100/phpdbug": "*", | ||
| "phpunit/phpunit": "6.*" | ||
| }, | ||
| "autoload": { | ||
| "psr-4": { | ||
| "FcPhp\\Cache\\": "src/", | ||
| "FcPhp\\Cache\\Test\\": "tests/" | ||
| } | ||
| } | ||
| } |
| MIT License | ||
| Copyright (c) 2018 João Moraes | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <phpunit bootstrap="vendor/autoload.php"> | ||
| <testsuites> | ||
| <testsuite name="testSuite"> | ||
| <directory>./tests/</directory> | ||
| </testsuite> | ||
| </testsuites> | ||
| <filter> | ||
| <whitelist> | ||
| <directory suffix=".php">./src/</directory> | ||
| </whitelist> | ||
| <blacklist> | ||
| <directory suffix=".php">./vendor/</directory> | ||
| <exclude> | ||
| <directory suffix=".php">./vendor/</directory> | ||
| </exclude> | ||
| </blacklist> | ||
| </filter> | ||
| <logging> | ||
| <log type="coverage-html" target="tests/coverage" charset="UTF-8" yui="true" highlight="true" lowUpperBound="35" highLowerBound="70" /> | ||
| </logging> | ||
| </phpunit> |
| # FcPhp Cache | ||
| Package to manage Cache Index and crypto content using [Sodium PHP](http://php.net/manual/en/book.sodium.php) _(optional)_ | ||
| [](https://travis-ci.org/00F100/fcphp-cache) [](https://codecov.io/gh/00F100/fcphp-cache) [](https://packagist.org/packages/00F100/fcphp-cache) | ||
| ## How to install | ||
| Composer: | ||
| ```sh | ||
| $ composer require 00f100/fcphp-cache | ||
| ``` | ||
| or add in composer.json | ||
| ```json | ||
| { | ||
| "require": { | ||
| "00f100/fcphp-cache": "*" | ||
| } | ||
| } | ||
| ``` | ||
| ## How to use | ||
| ```php | ||
| <?php | ||
| use FcPhp\Cache\Facades\CacheFacade; | ||
| use FcPhp\Crypto\Crypto; | ||
| /** | ||
| * Method to create new instance of Cache | ||
| * | ||
| * @param string|array $cacheRepository Configuration of redis or path to save files cache | ||
| * @param string $nonce Nonce to use crypto into content of cache. To generate: \FcPhp\Crypto\Crypto::getNonce() | ||
| * @param string $pathKeys Path to save keys crypto | ||
| * @return FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| $cache = CacheFacade::getInstance(string|array $cacheRepository, string $nonce = null, string $pathKeys = null); | ||
| /* | ||
| To use with Redis | ||
| ========================= | ||
| */ | ||
| $redis = [ | ||
| 'host' => '127.0.0.1', | ||
| 'port' => '6379', | ||
| 'password' => null, | ||
| 'timeout' => 100, | ||
| ]; | ||
| $cache = CacheFacade::getInstance($redis); | ||
| /* | ||
| To use with Redis and crypto | ||
| ========================= | ||
| */ | ||
| $redis = [ | ||
| 'host' => '127.0.0.1', | ||
| 'port' => '6379', | ||
| 'password' => null, | ||
| 'timeout' => 100, | ||
| ]; | ||
| $cache = CacheFacade::getInstance($redis, Crypto::getNonce(), 'path/to/keys'); | ||
| /* | ||
| To use with file | ||
| ========================= | ||
| */ | ||
| $cache = CacheFacade::getInstance('path/to/cache'); | ||
| /* | ||
| To use with file and crypto | ||
| ========================= | ||
| */ | ||
| $cache = CacheFacade::getInstance('path/to/cache', Crypto::getNonce(), 'path/to/keys'); | ||
| /** | ||
| * Method to create new cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @param mixed $content Content to cache | ||
| * @param int $ttl time to live cache | ||
| * @return FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| $cache->set(string $key, $content, int $ttl) :ICache | ||
| /** | ||
| * Method to verify if cache exists | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return bool | ||
| */ | ||
| $cache->has(string $key) :bool | ||
| /** | ||
| * Method to verify/read cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return mixed | ||
| */ | ||
| $cache->get(string $key) | ||
| /** | ||
| * Method to delete cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return void | ||
| */ | ||
| $cache->delete(string $key) :void | ||
| /** | ||
| * Method to clean old caches | ||
| * | ||
| * @return FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| $cache->clean() :ICache | ||
| ``` |
| <?php | ||
| namespace FcPhp\Cache | ||
| { | ||
| use FcPhp\Cache\Interfaces\ICache; | ||
| use FcPhp\Redis\Interfaces\IRedis; | ||
| use FcPhp\Cache\Traits\CacheTrait; | ||
| use FcPhp\Cache\Traits\CacheFileTrait; | ||
| use FcPhp\Cache\Traits\CacheRedisTrait; | ||
| use FcPhp\Crypto\Interfaces\ICrypto; | ||
| use FcPhp\Cache\Exceptions\PathKeyNotFoundException; | ||
| class Cache implements ICache | ||
| { | ||
| /** | ||
| * Const to alias cache in Redis | ||
| */ | ||
| const CACHE_REDIS_ALIAS = 'cache::'; | ||
| const CACHE_KEY_ALIAS = 'key::'; | ||
| use CacheTrait; | ||
| use CacheFileTrait; | ||
| use CacheRedisTrait; | ||
| /** | ||
| * @var string | ||
| */ | ||
| private $strategy = 'redis'; | ||
| /** | ||
| * @var FcPhp\Redis\Interfaces\IRedis | ||
| */ | ||
| private $redis = null; | ||
| /** | ||
| * @var string | ||
| */ | ||
| private $path = null; | ||
| /** | ||
| * @var FcPhp\Crypto\Interfaces\ICrypto | ||
| */ | ||
| private $crypto = null; | ||
| /** | ||
| * @var string | ||
| */ | ||
| private $pathKeys = null; | ||
| /** | ||
| * Method to construct new instance of Cache | ||
| * | ||
| * @param FcPhp\Redis\Interfaces\IRedis $redis Redis instance | ||
| * @param string $path Path to cache in file | ||
| * @return void | ||
| */ | ||
| public function __construct(?IRedis $redis = null, string $path = null, ?ICrypto $crypto = null, string $pathKeys = null) | ||
| { | ||
| if($redis instanceof IRedis) { | ||
| $this->redis = $redis; | ||
| }else{ | ||
| $this->path = $path; | ||
| $this->strategy = 'path'; | ||
| } | ||
| if($crypto instanceof ICrypto) { | ||
| $this->crypto = $crypto; | ||
| if(empty($pathKeys)) { | ||
| throw new PathKeyNotFoundException(); | ||
| } | ||
| $this->pathKeys = $pathKeys; | ||
| } | ||
| } | ||
| /** | ||
| * Method to create new cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @param mixed $content Content to cache | ||
| * @param int $ttl time to live cache | ||
| * @return FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| public function set(string $key, $content, int $ttl) :ICache | ||
| { | ||
| $content = serialize($content); | ||
| $this->write($key, $content, $ttl); | ||
| return $this; | ||
| } | ||
| /** | ||
| * Method to verify if cache exists | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return bool | ||
| */ | ||
| public function has(string $key) :bool | ||
| { | ||
| if($this->isRedis()) { | ||
| return $this->redis->get(self::CACHE_REDIS_ALIAS . $key) ? true : false; | ||
| }else{ | ||
| return file_exists($this->path . '/' . $key . '.cache'); | ||
| } | ||
| } | ||
| /** | ||
| * Method to delete cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return void | ||
| */ | ||
| public function delete(string $key) :void | ||
| { | ||
| if($this->isRedis()) { | ||
| $this->redis->delete(self::CACHE_REDIS_ALIAS . $key); | ||
| }else{ | ||
| $this->fdelete($key); | ||
| } | ||
| } | ||
| /** | ||
| * Method to verify/read cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return mixed | ||
| */ | ||
| public function get(string $key) | ||
| { | ||
| $content = $this->read($key); | ||
| if(!empty($content)) { | ||
| $content = explode('|', $content); | ||
| $time = $content[0]; | ||
| $content = $content[1]; | ||
| $content = $this->unprocessContent($key, $content); | ||
| if($time < time()) { | ||
| $this->delete($key); | ||
| return null; | ||
| } | ||
| return unserialize($content); | ||
| } | ||
| return null; | ||
| } | ||
| /** | ||
| * Method to clean old caches | ||
| * | ||
| * @return FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| public function clean() :ICache | ||
| { | ||
| if($this->isRedis()) { | ||
| $this->redisClean(); | ||
| }else{ | ||
| $this->fclean(); | ||
| } | ||
| return $this; | ||
| } | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Exceptions | ||
| { | ||
| use Exception; | ||
| class PathKeyNotFoundException extends Exception | ||
| { | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Exceptions | ||
| { | ||
| use Exception; | ||
| class PathNotPermissionFoundException extends Exception | ||
| { | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Facades | ||
| { | ||
| use FcPhp\Cache\Cache; | ||
| use FcPhp\Cache\Interfaces\ICache; | ||
| use FcPhp\Redis\Facades\RedisFacade; | ||
| use FcPhp\Crypto\Crypto; | ||
| class CacheFacade | ||
| { | ||
| /** | ||
| * @var FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| public static $instance; | ||
| /** | ||
| * Method to create new instance of Cache | ||
| * | ||
| * @param string|array $cacheRepository Configuration of redis or path of dir to cache files | ||
| * @return FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| public static function getInstance($cacheRepository, string $nonce = null, string $pathKeys = null) :ICache | ||
| { | ||
| if(!self::$instance instanceof ICache) { | ||
| $crypto = (!empty($nonce) ? new Crypto($nonce) : null); | ||
| if(is_array($cacheRepository) && isset($cacheRepository['host']) && $redis = self::sanitizeRedis($cacheRepository)) { | ||
| self::$instance = new Cache(RedisFacade::getInstance($redis['host'], $redis['port'], $redis['password'], $redis['timeout']), null, $crypto, $pathKeys); | ||
| }else{ | ||
| self::$instance = new Cache(null, $cacheRepository, $crypto, $pathKeys); | ||
| } | ||
| } | ||
| return self::$instance; | ||
| } | ||
| /** | ||
| * Method to reset instance | ||
| * | ||
| * @return void | ||
| */ | ||
| public static function reset() :void | ||
| { | ||
| self::$instance = null; | ||
| } | ||
| /** | ||
| * Method to sanitize array of redis configuration | ||
| * | ||
| * @param array $redis Configuration of redis | ||
| * @return array | ||
| */ | ||
| private static function sanitizeRedis(array &$redis) :array | ||
| { | ||
| $default = [ | ||
| 'host' => '', | ||
| 'port' => '6379', | ||
| 'password' => null, | ||
| 'timeout' => 100, | ||
| ]; | ||
| return array_merge($default, $redis); | ||
| } | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Interfaces | ||
| { | ||
| use FcPhp\Cache\Interfaces\ICache; | ||
| use FcPhp\Redis\Interfaces\IRedis; | ||
| use FcPhp\Crypto\Interfaces\ICrypto; | ||
| interface ICache | ||
| { | ||
| public function __construct(?IRedis $redis = null, string $path = null, ?ICrypto $crypto = null, string $pathKeys = null); | ||
| public function set(string $key, $content, int $ttl) :ICache; | ||
| public function has(string $key) :bool; | ||
| public function delete(string $key) :void; | ||
| public function get(string $key); | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Traits | ||
| { | ||
| use Exception; | ||
| use FcPhp\Cache\Exceptions\PathNotPermissionFoundException; | ||
| trait CacheFileTrait | ||
| { | ||
| /** | ||
| * Method to write cache in file | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @param string $content Content to cache | ||
| * @return void | ||
| */ | ||
| private function fmake(string $key, string $content) :void | ||
| { | ||
| if(!is_dir($this->path)) { | ||
| try { | ||
| mkdir($this->path, 0755, true); | ||
| } catch (Exception $e) { | ||
| throw new PathNotPermissionFoundException($this->path, 500, $e); | ||
| } | ||
| } | ||
| $fopen = fopen($this->path . '/' . $key . '.cache', 'w'); | ||
| fwrite($fopen, $content); | ||
| fclose($fopen); | ||
| } | ||
| /** | ||
| * Method to read cache in file | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return mixed | ||
| */ | ||
| private function fread(string $key) | ||
| { | ||
| $file = $this->path . '/' . $key . '.cache'; | ||
| return $this->has($key) ? file_get_contents($file) : null; | ||
| } | ||
| /** | ||
| * Method to delete cache in file | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return void | ||
| */ | ||
| private function fdelete(string $key) :void | ||
| { | ||
| if($this->has($key)) { | ||
| $file = $this->path . '/' . $key . '.cache'; | ||
| unlink($file); | ||
| } | ||
| } | ||
| /** | ||
| * Method to clean old caches | ||
| * | ||
| * @return void | ||
| */ | ||
| private function fclean() :void | ||
| { | ||
| if(is_dir($this->path)) { | ||
| $list = array_diff(scandir($this->path), ['.','..']); | ||
| if(count($list) > 0) { | ||
| foreach($list as $file) { | ||
| $fileData = explode('.', $file); | ||
| $key = current($fileData); | ||
| if(end($fileData) == 'cache') { | ||
| $content = file_get_contents($this->path . '/' . $file); | ||
| $content = explode('|', $content); | ||
| $time = current($content); | ||
| if($time < time()) { | ||
| $this->delete($key); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Traits | ||
| { | ||
| trait CacheRedisTrait | ||
| { | ||
| /** | ||
| * Method to verify if Cache use Redis | ||
| * | ||
| * @return bool | ||
| */ | ||
| private function isRedis() | ||
| { | ||
| return $this->strategy == 'redis'; | ||
| } | ||
| /** | ||
| * Method to clean Redis old caches | ||
| * | ||
| * @return void | ||
| */ | ||
| private function redisClean() :void | ||
| { | ||
| $list = $this->redis->keys('*'); | ||
| if(is_array($list)) { | ||
| foreach($list as $key) { | ||
| if(substr($key, 0, strlen(self::CACHE_REDIS_ALIAS)) == self::CACHE_REDIS_ALIAS) { | ||
| $content = $this->redis->get($key); | ||
| $content = explode('|', $content); | ||
| $time = current($content); | ||
| if($time < time()) { | ||
| $this->delete($key); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Traits | ||
| { | ||
| use Exception; | ||
| use FcPhp\Crypto\Interfaces\ICrypto; | ||
| use FcPhp\Cache\Exceptions\PathNotPermissionFoundException; | ||
| use FcPhp\Crypto\Crypto; | ||
| trait CacheTrait | ||
| { | ||
| /** | ||
| * Method to write cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @param mixed $content Content to cache | ||
| * @param int $ttl time to live cache | ||
| * @return void | ||
| */ | ||
| private function write(string $key, string $content, int $ttl) :void | ||
| { | ||
| $content = time() + $ttl . '|' . $this->processContent($key, $content); | ||
| if($this->isRedis()) { | ||
| $this->redis->set(self::CACHE_REDIS_ALIAS . $key, $content); | ||
| }else{ | ||
| $this->fmake($key, $content); | ||
| } | ||
| } | ||
| private function processContent(string $key, string $content) | ||
| { | ||
| if($this->crypto instanceof ICrypto) { | ||
| $key = $this->getKey(md5($key)); | ||
| return $this->crypto->encode($key, $content); | ||
| } | ||
| return base64_encode($content); | ||
| } | ||
| private function unprocessContent(string $key, string $content) | ||
| { | ||
| if($this->crypto instanceof ICrypto) { | ||
| $key = $this->getKey(md5($key)); | ||
| return $this->crypto->decode($key, $content); | ||
| } | ||
| return base64_decode($content); | ||
| } | ||
| private function getKey(string $hash) | ||
| { | ||
| if(!is_dir($this->pathKeys)) { | ||
| try { | ||
| mkdir($this->pathKeys, 0755, true); | ||
| } catch (Exception $e) { | ||
| throw new PathNotPermissionFoundException($this->pathKeys, 500, $e); | ||
| } | ||
| } | ||
| $filePath = $this->pathKeys . '/' . $hash . '.key'; | ||
| if(file_exists($filePath)) { | ||
| return file_get_contents($filePath); | ||
| } | ||
| $key = Crypto::getKey(); | ||
| $fopen = fopen($filePath, 'w'); | ||
| fwrite($fopen, $key); | ||
| fclose($fopen); | ||
| return $key; | ||
| } | ||
| /** | ||
| * Method to read cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return mixed | ||
| */ | ||
| private function read(string $key) | ||
| { | ||
| if($this->isRedis()) { | ||
| return $this->redis->get(self::CACHE_REDIS_ALIAS . $key); | ||
| }else{ | ||
| return $this->fread($key); | ||
| } | ||
| } | ||
| } | ||
| } |
| <?php | ||
| use FcPhp\Cache\Cache; | ||
| use FcPhp\Cache\Interfaces\ICache; | ||
| use FcPhp\Cache\Facades\CacheFacade; | ||
| use FcPhp\Crypto\Crypto; | ||
| use PHPUnit\Framework\TestCase; | ||
| class CacheIntegrationTest extends TestCase | ||
| { | ||
| private $instance; | ||
| public function setUp() | ||
| { | ||
| $redis = [ | ||
| 'host' => '127.0.0.1', | ||
| 'port' => '6379', | ||
| 'password' => null, | ||
| 'timeout' => 100 | ||
| ]; | ||
| $this->instance = CacheFacade::getInstance($redis, Crypto::getNonce(), 'tests/var/keys'); | ||
| } | ||
| public function testInstance() | ||
| { | ||
| $this->assertTrue($this->instance instanceof ICache); | ||
| } | ||
| public function testSetGet() | ||
| { | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 10; | ||
| $this->instance->set($key, $content, $ttl); | ||
| $this->assertEquals($this->instance->get($key), $content); | ||
| } | ||
| public function testHas() | ||
| { | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 10; | ||
| $this->instance->set($key, $content, $ttl); | ||
| $this->assertTrue($this->instance->has($key)); | ||
| } | ||
| public function testCleanRedis() | ||
| { | ||
| $this->assertTrue($this->instance->clean() instanceof ICache); | ||
| } | ||
| public function testSetGetOld() | ||
| { | ||
| $redisInstance = $this->createMock('FcPhp\Redis\Interfaces\IRedis'); | ||
| $redisInstance | ||
| ->expects($this->any()) | ||
| ->method('get') | ||
| ->will($this->returnValue(123 . '|' . base64_encode(serialize(['data' => 'value'])))); | ||
| $instance = new Cache($redisInstance); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 10; | ||
| $this->assertTrue($instance->get($key) == null); | ||
| $instance->delete($key); | ||
| } | ||
| public function testSetGetFile() | ||
| { | ||
| CacheFacade::reset(); | ||
| $instance = CacheFacade::getInstance('tests/var/cache'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| $this->assertTrue($instance->has($key)); | ||
| $instance->clean(); | ||
| } | ||
| public function testSetGetFileDelete() | ||
| { | ||
| CacheFacade::reset(); | ||
| $instance = CacheFacade::getInstance('tests/var/cache'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| $this->assertTrue($instance->has($key)); | ||
| $instance->delete($key); | ||
| } | ||
| public function testGetKeyNonExists() | ||
| { | ||
| $this->assertTrue(is_null($this->instance->get('abc'))); | ||
| } | ||
| public function testClean() | ||
| { | ||
| $this->assertTrue($this->instance->clean() instanceof ICache); | ||
| } | ||
| /** | ||
| * @expectedException FcPhp\Cache\Exceptions\PathKeyNotFoundException | ||
| */ | ||
| public function testWithCryptoNonPath() | ||
| { | ||
| $instance = new Cache(null, 'tests/var/cache', new Crypto(Crypto::getNonce())); | ||
| } | ||
| /** | ||
| * @expectedException FcPhp\Cache\Exceptions\PathNotPermissionFoundException | ||
| */ | ||
| public function testWithCryptoNonPermissionPath() | ||
| { | ||
| $instance = new Cache(null, 'tests/var/cache', new Crypto(Crypto::getNonce()), '/root/dir'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| } | ||
| public function testWithCryptoNonNewDir() | ||
| { | ||
| $instance = new Cache(null, 'tests/var/cache', new Crypto(Crypto::getNonce()), 'tests/var/newdir'); | ||
| $key = md5(time() . rand()); | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| $instance->delete($key); | ||
| } | ||
| /** | ||
| * @expectedException FcPhp\Cache\Exceptions\PathNotPermissionFoundException | ||
| */ | ||
| public function testDirCacheNonPermission() | ||
| { | ||
| $instance = new Cache(null, '/root/dir'); | ||
| $key = md5(time() . rand()); | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| } | ||
| } |
| <?php | ||
| use FcPhp\Cache\Cache; | ||
| use FcPhp\Cache\Interfaces\ICache; | ||
| use PHPUnit\Framework\TestCase; | ||
| use FcPhp\Crypto\Interfaces\ICrypto; | ||
| class CacheTest extends TestCase | ||
| { | ||
| private $instance; | ||
| public function setUp() | ||
| { | ||
| $redisInstance = $this->createMock('FcPhp\Redis\Interfaces\IRedis'); | ||
| $redisInstance | ||
| ->expects($this->any()) | ||
| ->method('get') | ||
| ->will($this->returnValue(time() . '|' . base64_encode(serialize(['data' => 'value'])))); | ||
| $redisInstance | ||
| ->expects($this->any()) | ||
| ->method('keys') | ||
| ->will($this->returnValue([Cache::CACHE_REDIS_ALIAS . '1', Cache::CACHE_REDIS_ALIAS . '2', Cache::CACHE_REDIS_ALIAS . '3'])); | ||
| $this->instance = new Cache($redisInstance); | ||
| } | ||
| public function testInstance() | ||
| { | ||
| $this->assertTrue($this->instance instanceof ICache); | ||
| } | ||
| public function testSetGet() | ||
| { | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 84000; | ||
| $this->instance->set($key, $content, $ttl); | ||
| $this->assertEquals($this->instance->get($key), $content); | ||
| } | ||
| public function testHas() | ||
| { | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 84000; | ||
| $this->instance->set($key, $content, $ttl); | ||
| $this->assertTrue($this->instance->has($key)); | ||
| } | ||
| public function testSetGetOld() | ||
| { | ||
| $redisInstance = $this->createMock('FcPhp\Redis\Interfaces\IRedis'); | ||
| $redisInstance | ||
| ->expects($this->any()) | ||
| ->method('get') | ||
| ->will($this->returnValue(123 . '|' . base64_encode(serialize(['data' => 'value'])))); | ||
| $instance = new Cache($redisInstance); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 84000; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertTrue($instance->get($key) == null); | ||
| } | ||
| public function testSetGetFile() | ||
| { | ||
| $instance = new Cache(null, 'tests/var/cache'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| $this->assertTrue($instance->has($key)); | ||
| $instance->delete($key); | ||
| } | ||
| public function testGetKeyNonExists() | ||
| { | ||
| $instance = new Cache(null, 'tests/var/cache'); | ||
| $this->assertTrue(is_null($instance->get('abc'))); | ||
| } | ||
| public function testClean() | ||
| { | ||
| $this->assertTrue($this->instance->clean() instanceof ICache); | ||
| } | ||
| public function testCleanFile() | ||
| { | ||
| $instance = new Cache(null, 'tests/var/cache'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertTrue($instance->clean() instanceof ICache); | ||
| } | ||
| public function testWithCrypto() | ||
| { | ||
| $cryptoInstance = $this->createMock('FcPhp\Crypto\Interfaces\ICrypto'); | ||
| $cryptoInstance | ||
| ->expects($this->any()) | ||
| ->method('decode') | ||
| ->will($this->returnValue(serialize(['data' => 'value']))); | ||
| $instance = new Cache(null, 'tests/var/cache', $cryptoInstance, 'tests/var/keys'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| } | ||
| /** | ||
| * @expectedException FcPhp\Cache\Exceptions\PathKeyNotFoundException | ||
| */ | ||
| public function testWithCryptoNonPath() | ||
| { | ||
| $cryptoInstance = $this->createMock('FcPhp\Crypto\Interfaces\ICrypto'); | ||
| $cryptoInstance | ||
| ->expects($this->any()) | ||
| ->method('decode') | ||
| ->will($this->returnValue(serialize(['data' => 'value']))); | ||
| $instance = new Cache(null, 'tests/var/cache', $cryptoInstance); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| } | ||
| /** | ||
| * @expectedException FcPhp\Cache\Exceptions\PathNotPermissionFoundException | ||
| */ | ||
| public function testWithCryptoNonPermissionPath() | ||
| { | ||
| $cryptoInstance = $this->createMock('FcPhp\Crypto\Interfaces\ICrypto'); | ||
| $cryptoInstance | ||
| ->expects($this->any()) | ||
| ->method('decode') | ||
| ->will($this->returnValue(serialize(['data' => 'value']))); | ||
| $instance = new Cache(null, 'tests/var/cache', $cryptoInstance, '/root/dir'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| } | ||
| public function testWithCryptoNonNewDir() | ||
| { | ||
| $cryptoInstance = $this->createMock('FcPhp\Crypto\Interfaces\ICrypto'); | ||
| $cryptoInstance | ||
| ->expects($this->any()) | ||
| ->method('decode') | ||
| ->will($this->returnValue(serialize(['data' => 'value']))); | ||
| $instance = new Cache(null, 'tests/var/cache', $cryptoInstance, 'tests/var/newdir'); | ||
| $key = md5(time() . rand()); | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| $instance->delete($key); | ||
| } | ||
| /** | ||
| * @expectedException FcPhp\Cache\Exceptions\PathNotPermissionFoundException | ||
| */ | ||
| public function testDirCacheNonPermission() | ||
| { | ||
| $instance = new Cache(null, '/root/dir'); | ||
| $key = md5(time() . rand()); | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| } | ||
| } |
| vendor | ||
| tests/coverage | ||
| composer.lock | ||
| docker-compose.yml | ||
| tests/var |
| language: php | ||
| php: | ||
| - 7.2 | ||
| before_install: | ||
| - sudo apt-get update -qq | ||
| - sudo apt-get install build-essential git -y | ||
| - git clone -b stable https://github.com/jedisct1/libsodium.git | ||
| - cd libsodium && sudo ./configure && sudo make check && sudo make install && cd .. | ||
| install: | ||
| # Manually install libsodium, because the TravicCi image doesn't provide PHP7.2 with libsodium | ||
| - pecl install libsodium | ||
| - echo "extension=sodium.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini | ||
| before_script: | ||
| - echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini | ||
| - travis_retry composer self-update | ||
| - travis_retry composer install --no-interaction --prefer-source --dev | ||
| script: | ||
| - vendor/bin/phpunit --coverage-clover=coverage.xml | ||
| after_success: | ||
| - bash <(curl -s https://codecov.io/bash) | ||
| services: | ||
| - redis-server | ||
| { | ||
| "name": "00f100/fcphp-cache", | ||
| "type": "package", | ||
| "version": "0.3.1", | ||
| "description": "Cache Index for FcPhp", | ||
| "keywords": ["cache", "fcphp", "php7", "php72", "crypto", "redis"], | ||
| "homepage": "https://github.com/00f100/fcphp-cache", | ||
| "authors": [ | ||
| { | ||
| "name": "João Moraes", | ||
| "email": "joaomoraesbr@gmail.com", | ||
| "homepage": "https://github.com/00f100" | ||
| } | ||
| ], | ||
| "require": { | ||
| "php": ">=7.2", | ||
| "00f100/fcphp-redis": "0.4.1", | ||
| "00f100/fcphp-crypto": "0.2.0", | ||
| "ext-redis": "*" | ||
| }, | ||
| "require-dev": { | ||
| "00f100/phpdbug": "*", | ||
| "phpunit/phpunit": "6.*" | ||
| }, | ||
| "autoload": { | ||
| "psr-4": { | ||
| "FcPhp\\Cache\\": "src/", | ||
| "FcPhp\\Cache\\Test\\": "tests/" | ||
| } | ||
| } | ||
| } |
| MIT License | ||
| Copyright (c) 2018 João Moraes | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <phpunit bootstrap="vendor/autoload.php"> | ||
| <testsuites> | ||
| <testsuite name="testSuite"> | ||
| <directory>./tests/</directory> | ||
| </testsuite> | ||
| </testsuites> | ||
| <filter> | ||
| <whitelist> | ||
| <directory suffix=".php">./src/</directory> | ||
| </whitelist> | ||
| <blacklist> | ||
| <directory suffix=".php">./vendor/</directory> | ||
| <exclude> | ||
| <directory suffix=".php">./vendor/</directory> | ||
| </exclude> | ||
| </blacklist> | ||
| </filter> | ||
| <logging> | ||
| <log type="coverage-html" target="tests/coverage" charset="UTF-8" yui="true" highlight="true" lowUpperBound="35" highLowerBound="70" /> | ||
| </logging> | ||
| </phpunit> |
| # FcPhp Cache | ||
| Package to manage Cache Index and crypto content using [Sodium PHP](http://php.net/manual/en/book.sodium.php) _(optional)_ | ||
| [](https://travis-ci.org/00F100/fcphp-cache) [](https://codecov.io/gh/00F100/fcphp-cache) [](https://packagist.org/packages/00F100/fcphp-cache) | ||
| ## How to install | ||
| Composer: | ||
| ```sh | ||
| $ composer require 00f100/fcphp-cache | ||
| ``` | ||
| or add in composer.json | ||
| ```json | ||
| { | ||
| "require": { | ||
| "00f100/fcphp-cache": "*" | ||
| } | ||
| } | ||
| ``` | ||
| ## How to use | ||
| ```php | ||
| <?php | ||
| use FcPhp\Cache\Facades\CacheFacade; | ||
| use FcPhp\Crypto\Crypto; | ||
| /** | ||
| * Method to create new instance of Cache | ||
| * | ||
| * @param string|array $cacheRepository Configuration of redis or path to save files cache | ||
| * @param string $nonce Nonce to use crypto into content of cache. To generate: \FcPhp\Crypto\Crypto::getNonce() | ||
| * @param string $pathKeys Path to save keys crypto | ||
| * @return FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| $cache = CacheFacade::getInstance(string|array $cacheRepository, string $nonce = null, string $pathKeys = null); | ||
| /* | ||
| To use with Redis | ||
| ========================= | ||
| */ | ||
| $redis = [ | ||
| 'host' => '127.0.0.1', | ||
| 'port' => '6379', | ||
| 'password' => null, | ||
| 'timeout' => 100, | ||
| ]; | ||
| $cache = CacheFacade::getInstance($redis); | ||
| /* | ||
| To use with Redis and crypto | ||
| ========================= | ||
| */ | ||
| $redis = [ | ||
| 'host' => '127.0.0.1', | ||
| 'port' => '6379', | ||
| 'password' => null, | ||
| 'timeout' => 100, | ||
| ]; | ||
| $cache = CacheFacade::getInstance($redis, Crypto::getNonce()); | ||
| /* | ||
| To use with file | ||
| ========================= | ||
| */ | ||
| $cache = CacheFacade::getInstance('path/to/dir'); | ||
| /* | ||
| To use with file and crypto | ||
| ========================= | ||
| */ | ||
| $cache = CacheFacade::getInstance('path/to/dir', Crypto::getNonce()); | ||
| /** | ||
| * Method to create new cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @param mixed $content Content to cache | ||
| * @param int $ttl time to live cache | ||
| * @return FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| $cache->set(string $key, $content, int $ttl) :ICache | ||
| /** | ||
| * Method to verify if cache exists | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return bool | ||
| */ | ||
| $cache->has(string $key) :bool | ||
| /** | ||
| * Method to verify/read cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return mixed | ||
| */ | ||
| $cache->get(string $key) | ||
| /** | ||
| * Method to delete cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return void | ||
| */ | ||
| $cache->delete(string $key) :void | ||
| /** | ||
| * Method to clean old caches | ||
| * | ||
| * @return FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| $cache->clean() :ICache | ||
| ``` |
| <?php | ||
| namespace FcPhp\Cache | ||
| { | ||
| use FcPhp\Cache\Interfaces\ICache; | ||
| use FcPhp\Redis\Interfaces\IRedis; | ||
| use FcPhp\Cache\Traits\CacheTrait; | ||
| use FcPhp\Cache\Traits\CacheFileTrait; | ||
| use FcPhp\Cache\Traits\CacheRedisTrait; | ||
| use FcPhp\Crypto\Interfaces\ICrypto; | ||
| use FcPhp\Cache\Exceptions\PathKeyNotFoundException; | ||
| class Cache implements ICache | ||
| { | ||
| /** | ||
| * Const to alias cache in Redis | ||
| */ | ||
| const CACHE_REDIS_ALIAS = 'cache::'; | ||
| const CACHE_KEY_ALIAS = 'key::'; | ||
| use CacheTrait; | ||
| use CacheFileTrait; | ||
| use CacheRedisTrait; | ||
| /** | ||
| * @var string | ||
| */ | ||
| private $strategy = 'redis'; | ||
| /** | ||
| * @var FcPhp\Redis\Interfaces\IRedis | ||
| */ | ||
| private $redis = null; | ||
| /** | ||
| * @var string | ||
| */ | ||
| private $path = null; | ||
| /** | ||
| * @var FcPhp\Crypto\Interfaces\ICrypto | ||
| */ | ||
| private $crypto = null; | ||
| /** | ||
| * @var string | ||
| */ | ||
| private $pathKeys = null; | ||
| /** | ||
| * Method to construct new instance of Cache | ||
| * | ||
| * @param FcPhp\Redis\Interfaces\IRedis $redis Redis instance | ||
| * @param string $path Path to cache in file | ||
| * @return void | ||
| */ | ||
| public function __construct(?IRedis $redis = null, string $path = null, ?ICrypto $crypto = null, string $pathKeys = null) | ||
| { | ||
| if($redis instanceof IRedis) { | ||
| $this->redis = $redis; | ||
| }else{ | ||
| $this->path = $path; | ||
| $this->strategy = 'path'; | ||
| } | ||
| if($crypto instanceof ICrypto) { | ||
| $this->crypto = $crypto; | ||
| if(empty($pathKeys)) { | ||
| throw new PathKeyNotFoundException(); | ||
| } | ||
| $this->pathKeys = $pathKeys; | ||
| } | ||
| } | ||
| /** | ||
| * Method to create new cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @param mixed $content Content to cache | ||
| * @param int $ttl time to live cache | ||
| * @return FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| public function set(string $key, $content, int $ttl) :ICache | ||
| { | ||
| $content = serialize($content); | ||
| $this->write($key, $content, $ttl); | ||
| return $this; | ||
| } | ||
| /** | ||
| * Method to verify if cache exists | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return bool | ||
| */ | ||
| public function has(string $key) :bool | ||
| { | ||
| if($this->isRedis()) { | ||
| return $this->redis->get(self::CACHE_REDIS_ALIAS . $key) ? true : false; | ||
| }else{ | ||
| return file_exists($this->path . '/' . $key . '.cache'); | ||
| } | ||
| } | ||
| /** | ||
| * Method to delete cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return void | ||
| */ | ||
| public function delete(string $key) :void | ||
| { | ||
| if($this->isRedis()) { | ||
| $this->redis->delete(self::CACHE_REDIS_ALIAS . $key); | ||
| }else{ | ||
| $this->fdelete($key); | ||
| } | ||
| } | ||
| /** | ||
| * Method to verify/read cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return mixed | ||
| */ | ||
| public function get(string $key) | ||
| { | ||
| $content = $this->read($key); | ||
| if(!empty($content)) { | ||
| $content = explode('|', $content); | ||
| $time = $content[0]; | ||
| $content = $content[1]; | ||
| $content = $this->unprocessContent($key, $content); | ||
| if($time < time()) { | ||
| $this->delete($key); | ||
| return null; | ||
| } | ||
| return unserialize($content); | ||
| } | ||
| return null; | ||
| } | ||
| /** | ||
| * Method to clean old caches | ||
| * | ||
| * @return FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| public function clean() :ICache | ||
| { | ||
| if($this->isRedis()) { | ||
| $this->redisClean(); | ||
| }else{ | ||
| $this->fclean(); | ||
| } | ||
| return $this; | ||
| } | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Exceptions | ||
| { | ||
| use Exception; | ||
| class PathKeyNotFoundException extends Exception | ||
| { | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Exceptions | ||
| { | ||
| use Exception; | ||
| class PathNotPermissionFoundException extends Exception | ||
| { | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Facades | ||
| { | ||
| use FcPhp\Cache\Cache; | ||
| use FcPhp\Cache\Interfaces\ICache; | ||
| use FcPhp\Redis\Facades\RedisFacade; | ||
| use FcPhp\Crypto\Crypto; | ||
| class CacheFacade | ||
| { | ||
| /** | ||
| * @var FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| public static $instance; | ||
| /** | ||
| * Method to create new instance of Cache | ||
| * | ||
| * @param string|array $cacheRepository Configuration of redis or path of dir to cache files | ||
| * @return FcPhp\Cache\Interfaces\ICache | ||
| */ | ||
| public static function getInstance($cacheRepository, string $nonce = null, string $pathKeys = null) :ICache | ||
| { | ||
| if(!self::$instance instanceof ICache) { | ||
| $crypto = (!empty($nonce) ? new Crypto($nonce) : null); | ||
| if(is_array($cacheRepository) && isset($cacheRepository['host']) && $redis = self::sanitizeRedis($cacheRepository)) { | ||
| self::$instance = new Cache(RedisFacade::getInstance($redis['host'], $redis['port'], $redis['password'], $redis['timeout']), null, $crypto, $pathKeys); | ||
| }else{ | ||
| self::$instance = new Cache(null, $cacheRepository, $crypto, $pathKeys); | ||
| } | ||
| } | ||
| return self::$instance; | ||
| } | ||
| /** | ||
| * Method to reset instance | ||
| * | ||
| * @return void | ||
| */ | ||
| public static function reset() :void | ||
| { | ||
| self::$instance = null; | ||
| } | ||
| /** | ||
| * Method to sanitize array of redis configuration | ||
| * | ||
| * @param array $redis Configuration of redis | ||
| * @return array | ||
| */ | ||
| private static function sanitizeRedis(array &$redis) :array | ||
| { | ||
| $default = [ | ||
| 'host' => '', | ||
| 'port' => '6379', | ||
| 'password' => null, | ||
| 'timeout' => 100, | ||
| ]; | ||
| return array_merge($default, $redis); | ||
| } | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Interfaces | ||
| { | ||
| use FcPhp\Cache\Interfaces\ICache; | ||
| use FcPhp\Redis\Interfaces\IRedis; | ||
| use FcPhp\Crypto\Interfaces\ICrypto; | ||
| interface ICache | ||
| { | ||
| public function __construct(?IRedis $redis = null, string $path = null, ?ICrypto $crypto = null, string $pathKeys = null); | ||
| public function set(string $key, $content, int $ttl) :ICache; | ||
| public function has(string $key) :bool; | ||
| public function delete(string $key) :void; | ||
| public function get(string $key); | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Traits | ||
| { | ||
| use Exception; | ||
| use FcPhp\Cache\Exceptions\PathNotPermissionFoundException; | ||
| trait CacheFileTrait | ||
| { | ||
| /** | ||
| * Method to write cache in file | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @param string $content Content to cache | ||
| * @return void | ||
| */ | ||
| private function fmake(string $key, string $content) :void | ||
| { | ||
| if(!is_dir($this->path)) { | ||
| try { | ||
| mkdir($this->path, 0755, true); | ||
| } catch (Exception $e) { | ||
| throw new PathNotPermissionFoundException($this->path, 500, $e); | ||
| } | ||
| } | ||
| $fopen = fopen($this->path . '/' . $key . '.cache', 'w'); | ||
| fwrite($fopen, $content); | ||
| fclose($fopen); | ||
| } | ||
| /** | ||
| * Method to read cache in file | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return mixed | ||
| */ | ||
| private function fread(string $key) | ||
| { | ||
| $file = $this->path . '/' . $key . '.cache'; | ||
| return $this->has($key) ? file_get_contents($file) : null; | ||
| } | ||
| /** | ||
| * Method to delete cache in file | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return void | ||
| */ | ||
| private function fdelete(string $key) :void | ||
| { | ||
| if($this->has($key)) { | ||
| $file = $this->path . '/' . $key . '.cache'; | ||
| unlink($file); | ||
| } | ||
| } | ||
| /** | ||
| * Method to clean old caches | ||
| * | ||
| * @return void | ||
| */ | ||
| private function fclean() :void | ||
| { | ||
| if(is_dir($this->path)) { | ||
| $list = array_diff(scandir($this->path), ['.','..']); | ||
| if(count($list) > 0) { | ||
| foreach($list as $file) { | ||
| $fileData = explode('.', $file); | ||
| $key = current($fileData); | ||
| if(end($fileData) == 'cache') { | ||
| $content = file_get_contents($this->path . '/' . $file); | ||
| $content = explode('|', $content); | ||
| $time = current($content); | ||
| if($time < time()) { | ||
| $this->delete($key); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Traits | ||
| { | ||
| trait CacheRedisTrait | ||
| { | ||
| /** | ||
| * Method to verify if Cache use Redis | ||
| * | ||
| * @return bool | ||
| */ | ||
| private function isRedis() | ||
| { | ||
| return $this->strategy == 'redis'; | ||
| } | ||
| /** | ||
| * Method to clean Redis old caches | ||
| * | ||
| * @return void | ||
| */ | ||
| private function redisClean() :void | ||
| { | ||
| $list = $this->redis->keys('*'); | ||
| if(is_array($list)) { | ||
| foreach($list as $key) { | ||
| if(substr($key, 0, strlen(self::CACHE_REDIS_ALIAS)) == self::CACHE_REDIS_ALIAS) { | ||
| $content = $this->redis->get($key); | ||
| $content = explode('|', $content); | ||
| $time = current($content); | ||
| if($time < time()) { | ||
| $this->delete($key); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| <?php | ||
| namespace FcPhp\Cache\Traits | ||
| { | ||
| use Exception; | ||
| use FcPhp\Crypto\Interfaces\ICrypto; | ||
| use FcPhp\Cache\Exceptions\PathNotPermissionFoundException; | ||
| use FcPhp\Crypto\Crypto; | ||
| trait CacheTrait | ||
| { | ||
| /** | ||
| * Method to write cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @param mixed $content Content to cache | ||
| * @param int $ttl time to live cache | ||
| * @return void | ||
| */ | ||
| private function write(string $key, string $content, int $ttl) :void | ||
| { | ||
| $content = time() + $ttl . '|' . $this->processContent($key, $content); | ||
| if($this->isRedis()) { | ||
| $this->redis->set(self::CACHE_REDIS_ALIAS . $key, $content); | ||
| }else{ | ||
| $this->fmake($key, $content); | ||
| } | ||
| } | ||
| private function processContent(string $key, string $content) | ||
| { | ||
| if($this->crypto instanceof ICrypto) { | ||
| $key = $this->getKey(md5($key)); | ||
| return $this->crypto->encode($key, $content); | ||
| } | ||
| return base64_encode($content); | ||
| } | ||
| private function unprocessContent(string $key, string $content) | ||
| { | ||
| if($this->crypto instanceof ICrypto) { | ||
| $key = $this->getKey(md5($key)); | ||
| return $this->crypto->decode($key, $content); | ||
| } | ||
| return base64_decode($content); | ||
| } | ||
| private function getKey(string $hash) | ||
| { | ||
| if(!is_dir($this->pathKeys)) { | ||
| try { | ||
| mkdir($this->pathKeys, 0755, true); | ||
| } catch (Exception $e) { | ||
| throw new PathNotPermissionFoundException($this->pathKeys, 500, $e); | ||
| } | ||
| } | ||
| $filePath = $this->pathKeys . '/' . $hash . '.key'; | ||
| if(file_exists($filePath)) { | ||
| return file_get_contents($filePath); | ||
| } | ||
| $key = Crypto::getKey(); | ||
| $fopen = fopen($filePath, 'w'); | ||
| fwrite($fopen, $key); | ||
| fclose($fopen); | ||
| return $key; | ||
| } | ||
| /** | ||
| * Method to read cache | ||
| * | ||
| * @param string $key Key to name cache | ||
| * @return mixed | ||
| */ | ||
| private function read(string $key) | ||
| { | ||
| if($this->isRedis()) { | ||
| return $this->redis->get(self::CACHE_REDIS_ALIAS . $key); | ||
| }else{ | ||
| return $this->fread($key); | ||
| } | ||
| } | ||
| } | ||
| } |
| <?php | ||
| use FcPhp\Cache\Cache; | ||
| use FcPhp\Cache\Interfaces\ICache; | ||
| use FcPhp\Cache\Facades\CacheFacade; | ||
| use FcPhp\Crypto\Crypto; | ||
| use PHPUnit\Framework\TestCase; | ||
| class CacheIntegrationTest extends TestCase | ||
| { | ||
| private $instance; | ||
| public function setUp() | ||
| { | ||
| $redis = [ | ||
| 'host' => '127.0.0.1', | ||
| 'port' => '6379', | ||
| 'password' => null, | ||
| 'timeout' => 100 | ||
| ]; | ||
| $this->instance = CacheFacade::getInstance($redis, Crypto::getNonce(), 'tests/var/keys'); | ||
| } | ||
| public function testInstance() | ||
| { | ||
| $this->assertTrue($this->instance instanceof ICache); | ||
| } | ||
| public function testSetGet() | ||
| { | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 10; | ||
| $this->instance->set($key, $content, $ttl); | ||
| $this->assertEquals($this->instance->get($key), $content); | ||
| } | ||
| public function testHas() | ||
| { | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 10; | ||
| $this->instance->set($key, $content, $ttl); | ||
| $this->assertTrue($this->instance->has($key)); | ||
| } | ||
| public function testCleanRedis() | ||
| { | ||
| $this->assertTrue($this->instance->clean() instanceof ICache); | ||
| } | ||
| public function testSetGetOld() | ||
| { | ||
| $redisInstance = $this->createMock('FcPhp\Redis\Interfaces\IRedis'); | ||
| $redisInstance | ||
| ->expects($this->any()) | ||
| ->method('get') | ||
| ->will($this->returnValue(123 . '|' . base64_encode(serialize(['data' => 'value'])))); | ||
| $instance = new Cache($redisInstance); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 10; | ||
| $this->assertTrue($instance->get($key) == null); | ||
| $instance->delete($key); | ||
| } | ||
| public function testSetGetFile() | ||
| { | ||
| CacheFacade::reset(); | ||
| $instance = CacheFacade::getInstance('tests/var/cache'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| $this->assertTrue($instance->has($key)); | ||
| $instance->clean(); | ||
| } | ||
| public function testSetGetFileDelete() | ||
| { | ||
| CacheFacade::reset(); | ||
| $instance = CacheFacade::getInstance('tests/var/cache'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| $this->assertTrue($instance->has($key)); | ||
| $instance->delete($key); | ||
| } | ||
| public function testGetKeyNonExists() | ||
| { | ||
| $this->assertTrue(is_null($this->instance->get('abc'))); | ||
| } | ||
| public function testClean() | ||
| { | ||
| $this->assertTrue($this->instance->clean() instanceof ICache); | ||
| } | ||
| /** | ||
| * @expectedException FcPhp\Cache\Exceptions\PathKeyNotFoundException | ||
| */ | ||
| public function testWithCryptoNonPath() | ||
| { | ||
| $instance = new Cache(null, 'tests/var/cache', new Crypto(Crypto::getNonce())); | ||
| } | ||
| /** | ||
| * @expectedException FcPhp\Cache\Exceptions\PathNotPermissionFoundException | ||
| */ | ||
| public function testWithCryptoNonPermissionPath() | ||
| { | ||
| $instance = new Cache(null, 'tests/var/cache', new Crypto(Crypto::getNonce()), '/root/dir'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| } | ||
| public function testWithCryptoNonNewDir() | ||
| { | ||
| $instance = new Cache(null, 'tests/var/cache', new Crypto(Crypto::getNonce()), 'tests/var/newdir'); | ||
| $key = md5(time() . rand()); | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| $instance->delete($key); | ||
| } | ||
| /** | ||
| * @expectedException FcPhp\Cache\Exceptions\PathNotPermissionFoundException | ||
| */ | ||
| public function testDirCacheNonPermission() | ||
| { | ||
| $instance = new Cache(null, '/root/dir'); | ||
| $key = md5(time() . rand()); | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| } | ||
| } |
| <?php | ||
| use FcPhp\Cache\Cache; | ||
| use FcPhp\Cache\Interfaces\ICache; | ||
| use PHPUnit\Framework\TestCase; | ||
| use FcPhp\Crypto\Interfaces\ICrypto; | ||
| class CacheTest extends TestCase | ||
| { | ||
| private $instance; | ||
| public function setUp() | ||
| { | ||
| $redisInstance = $this->createMock('FcPhp\Redis\Interfaces\IRedis'); | ||
| $redisInstance | ||
| ->expects($this->any()) | ||
| ->method('get') | ||
| ->will($this->returnValue(time() . '|' . base64_encode(serialize(['data' => 'value'])))); | ||
| $redisInstance | ||
| ->expects($this->any()) | ||
| ->method('keys') | ||
| ->will($this->returnValue([Cache::CACHE_REDIS_ALIAS . '1', Cache::CACHE_REDIS_ALIAS . '2', Cache::CACHE_REDIS_ALIAS . '3'])); | ||
| $this->instance = new Cache($redisInstance); | ||
| } | ||
| public function testInstance() | ||
| { | ||
| $this->assertTrue($this->instance instanceof ICache); | ||
| } | ||
| public function testSetGet() | ||
| { | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 84000; | ||
| $this->instance->set($key, $content, $ttl); | ||
| $this->assertEquals($this->instance->get($key), $content); | ||
| } | ||
| public function testHas() | ||
| { | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 84000; | ||
| $this->instance->set($key, $content, $ttl); | ||
| $this->assertTrue($this->instance->has($key)); | ||
| } | ||
| public function testSetGetOld() | ||
| { | ||
| $redisInstance = $this->createMock('FcPhp\Redis\Interfaces\IRedis'); | ||
| $redisInstance | ||
| ->expects($this->any()) | ||
| ->method('get') | ||
| ->will($this->returnValue(123 . '|' . base64_encode(serialize(['data' => 'value'])))); | ||
| $instance = new Cache($redisInstance); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 84000; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertTrue($instance->get($key) == null); | ||
| } | ||
| public function testSetGetFile() | ||
| { | ||
| $instance = new Cache(null, 'tests/var/cache'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| $this->assertTrue($instance->has($key)); | ||
| $instance->delete($key); | ||
| } | ||
| public function testGetKeyNonExists() | ||
| { | ||
| $instance = new Cache(null, 'tests/var/cache'); | ||
| $this->assertTrue(is_null($instance->get('abc'))); | ||
| } | ||
| public function testClean() | ||
| { | ||
| $this->assertTrue($this->instance->clean() instanceof ICache); | ||
| } | ||
| public function testCleanFile() | ||
| { | ||
| $instance = new Cache(null, 'tests/var/cache'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertTrue($instance->clean() instanceof ICache); | ||
| } | ||
| public function testWithCrypto() | ||
| { | ||
| $cryptoInstance = $this->createMock('FcPhp\Crypto\Interfaces\ICrypto'); | ||
| $cryptoInstance | ||
| ->expects($this->any()) | ||
| ->method('decode') | ||
| ->will($this->returnValue(serialize(['data' => 'value']))); | ||
| $instance = new Cache(null, 'tests/var/cache', $cryptoInstance, 'tests/var/keys'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| } | ||
| /** | ||
| * @expectedException FcPhp\Cache\Exceptions\PathKeyNotFoundException | ||
| */ | ||
| public function testWithCryptoNonPath() | ||
| { | ||
| $cryptoInstance = $this->createMock('FcPhp\Crypto\Interfaces\ICrypto'); | ||
| $cryptoInstance | ||
| ->expects($this->any()) | ||
| ->method('decode') | ||
| ->will($this->returnValue(serialize(['data' => 'value']))); | ||
| $instance = new Cache(null, 'tests/var/cache', $cryptoInstance); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| } | ||
| /** | ||
| * @expectedException FcPhp\Cache\Exceptions\PathNotPermissionFoundException | ||
| */ | ||
| public function testWithCryptoNonPermissionPath() | ||
| { | ||
| $cryptoInstance = $this->createMock('FcPhp\Crypto\Interfaces\ICrypto'); | ||
| $cryptoInstance | ||
| ->expects($this->any()) | ||
| ->method('decode') | ||
| ->will($this->returnValue(serialize(['data' => 'value']))); | ||
| $instance = new Cache(null, 'tests/var/cache', $cryptoInstance, '/root/dir'); | ||
| $key = 'cb6e0e439eff1d257641502d8fa65698'; | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| } | ||
| public function testWithCryptoNonNewDir() | ||
| { | ||
| $cryptoInstance = $this->createMock('FcPhp\Crypto\Interfaces\ICrypto'); | ||
| $cryptoInstance | ||
| ->expects($this->any()) | ||
| ->method('decode') | ||
| ->will($this->returnValue(serialize(['data' => 'value']))); | ||
| $instance = new Cache(null, 'tests/var/cache', $cryptoInstance, 'tests/var/newdir'); | ||
| $key = md5(time() . rand()); | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| $this->assertEquals($instance->get($key), $content); | ||
| $instance->delete($key); | ||
| } | ||
| /** | ||
| * @expectedException FcPhp\Cache\Exceptions\PathNotPermissionFoundException | ||
| */ | ||
| public function testDirCacheNonPermission() | ||
| { | ||
| $instance = new Cache(null, '/root/dir'); | ||
| $key = md5(time() . rand()); | ||
| $content = ['data' => 'value']; | ||
| $ttl = 0; | ||
| $instance->set($key, $content, $ttl); | ||
| } | ||
| } |