├── LICENSE ├── README.md ├── composer.json └── src ├── APCUCache.php ├── CacheDriverAbstract.php ├── CacheException.php ├── CacheOptions.php ├── CacheOptionsTrait.php ├── FileCache.php ├── MemcachedCache.php ├── MemoryCache.php ├── RedisCache.php └── SessionCache.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Smiley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chillerlan/php-cache 2 | 3 | A psr/simple-cache implementation for PHP 8.1+. 4 | 5 | [![PHP Version Support][php-badge]][php] 6 | [![version][packagist-badge]][packagist] 7 | [![license][license-badge]][license] 8 | [![Coverage][coverage-badge]][coverage] 9 | [![Codacy][codacy-badge]][codacy] 10 | [![Packagist downloads][downloads-badge]][downloads]
11 | [![Continuous Integration][gh-action-badge]][gh-action] 12 | 13 | [php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-cache?logo=php&color=8892BF 14 | [php]: https://www.php.net/supported-versions.php 15 | [packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-cache.svg?logo=packagist 16 | [packagist]: https://packagist.org/packages/chillerlan/php-cache 17 | [license-badge]: https://img.shields.io/github/license/chillerlan/php-cache.svg 18 | [license]: https://github.com/chillerlan/php-cache/blob/master/LICENSE 19 | [coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-cache.svg?logo=codecov 20 | [coverage]: https://codecov.io/github/chillerlan/php-cache 21 | [codacy-badge]: https://img.shields.io/codacy/grade/69b19ba81ae6492f973c4f05b92884aa/main?logo=codacy 22 | [codacy]: https://app.codacy.com/gh/chillerlan/php-cache/dashboard?branch=main 23 | [downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-cache.svg?logo=packagist 24 | [downloads]: https://packagist.org/packages/chillerlan/php-cache/stats 25 | [gh-action-badge]: https://github.com/chillerlan/php-cache/workflows/Continuous%20Integration/badge.svg 26 | [gh-action]: https://github.com/chillerlan/php-cache/actions 27 | 28 | ## Features: 29 | - [PSR-16 simple-cache-implementation](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) 30 | - persistent: File based, Memcached, Redis 31 | - non-persistent: Session, Memory 32 | 33 | ## Requirements 34 | - **PHP 8.1+** 35 | - optionally one of the following extensions 36 | - [Memcached](https://www.php.net/manual/en/book.memcached.php) 37 | - [Redis](https://github.com/phpredis/phpredis/) 38 | - [APCU](https://www.php.net/manual/en/book.apcu.php) 39 | 40 | ## Documentation 41 | ### Installation using [composer](https://getcomposer.org) 42 | You can simply clone the repo and run `composer install` in the root directory. 43 | In case you want to include it elsewhere, just add the following to your *composer.json*: 44 | 45 | (note: replace `dev-main` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), 46 | e.g. `^4.1` - see [releases](https://github.com/chillerlan/php-cache/releases) for valid versions) 47 | ```json 48 | { 49 | "require": { 50 | "php": "^8.1", 51 | "chillerlan/php-cache": "dev-main" 52 | } 53 | } 54 | ``` 55 | 56 | Installation via terminal: `composer require chillerlan/php-cache` 57 | 58 | Profit! 59 | 60 | ### Usage 61 | Just invoke a cache instance with the desired `CacheInterface` like so: 62 | ```php 63 | // Redis 64 | $redis = new Redis; 65 | $redis->pconnect('127.0.0.1', 6379); 66 | 67 | $cache = new RedisCache($redis); 68 | 69 | // Memcached 70 | $memcached = new Memcached('myCacheInstance'); 71 | $memcached->addServer('localhost', 11211); 72 | 73 | $cache = new MemcachedCache($memcached); 74 | 75 | // APCU 76 | $cache = new APCUCache; 77 | 78 | // File 79 | $cache = new FileCache(new CacheOptions(['cacheFilestorage' => __DIR__.'/../.cache'])); 80 | 81 | // Session 82 | $cache = new SessionCache(new CacheOptions(['cacheSessionkey' => '_my_session_cache'])); 83 | 84 | // Memory 85 | $cache = new MemoryCache; 86 | ``` 87 | 88 | #### Methods 89 | See: [`Psr\SimpleCache\CacheInterface`](https://github.com/php-fig/simple-cache/blob/master/src/CacheInterface.php) 90 | 91 | ```php 92 | $cache->get(string $key, $default = null); // -> mixed 93 | $cache->set(string $key, $value, int $ttl = null):bool 94 | $cache->delete(string $key):bool 95 | $cache->has(string $key):bool 96 | $cache->clear():bool 97 | $cache->getMultiple(array $keys, $default = null):array // -> mixed[] 98 | $cache->setMultiple(array $values, int $ttl = null):bool 99 | $cache->deleteMultiple(array $keys):bool 100 | ``` 101 | 102 | ## Disclaimer! 103 | I don't take responsibility for molten memory modules, bloated hard disks, self-induced DoS, broken screens etc. Use at your own risk! :see_no_evil: 104 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chillerlan/php-cache", 3 | "description": "A psr/simple-cache implementation. PHP 8.1+", 4 | "homepage": "https://github.com/chillerlan/php-cache", 5 | "license": "MIT", 6 | "type": "library", 7 | "keywords": [ 8 | "cache", "psr-16", "php8" 9 | ], 10 | "authors": [ 11 | { 12 | "name": "Smiley", 13 | "email": "smiley@chillerlan.net", 14 | "homepage": "https://github.com/codemasher" 15 | }, 16 | { 17 | "name": "Contributors", 18 | "homepage":"https://github.com/chillerlan/php-cache/graphs/contributors" 19 | } 20 | ], 21 | "support": { 22 | "issues": "https://github.com/chillerlan/php-cache/issues", 23 | "source": "https://github.com/chillerlan/php-cache" 24 | }, 25 | "minimum-stability": "stable", 26 | "prefer-stable": true, 27 | "require": { 28 | "php": "^8.1", 29 | "psr/log": "^1.1 || ^2.0 || ^3.0", 30 | "psr/simple-cache": "^2.0 || ^3.0", 31 | "chillerlan/php-settings-container": "^3.1" 32 | }, 33 | "require-dev": { 34 | "phan/phan": "^5.4", 35 | "phpmd/phpmd": "^2.15", 36 | "phpunit/phpunit": "^10.5", 37 | "squizlabs/php_codesniffer": "^3.9" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "chillerlan\\SimpleCache\\": "src" 42 | } 43 | }, 44 | "autoload-dev": { 45 | "psr-4": { 46 | "chillerlan\\SimpleCacheTest\\": "tests" 47 | } 48 | }, 49 | "provide": { 50 | "psr/simple-cache-implementation": "2.0 || 3.0" 51 | }, 52 | "scripts": { 53 | "phpunit": "@php vendor/bin/phpunit", 54 | "phan": "@php vendor/bin/phan" 55 | }, 56 | "config": { 57 | "lock": false, 58 | "sort-packages": true, 59 | "platform-check": true 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/APCUCache.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Smiley 8 | * @license MIT 9 | * 10 | * @noinspection PhpComposerExtensionStubsInspection 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace chillerlan\SimpleCache; 16 | 17 | use DateInterval; 18 | use function apcu_clear_cache, apcu_delete, apcu_fetch, apcu_store, implode, is_array, is_bool, sprintf; 19 | 20 | /** 21 | * Implements a cache via the APCu extension 22 | * 23 | * @see https://www.php.net/manual/en/book.apcu.php 24 | * @see https://github.com/krakjoe/apcu 25 | */ 26 | class APCUCache extends CacheDriverAbstract{ 27 | 28 | /** @inheritdoc */ 29 | public function get(string $key, mixed $default = null):mixed{ 30 | $value = apcu_fetch($this->checkKey($key)); 31 | 32 | if($value !== false){ 33 | return $value; 34 | } 35 | 36 | return $default; 37 | } 38 | 39 | /** 40 | * @inheritdoc 41 | * @throws \Psr\SimpleCache\CacheException 42 | */ 43 | public function set(string $key, mixed $value, int|DateInterval|null $ttl = null):bool{ 44 | $ret = apcu_store($this->checkKey($key), $value, ($this->getTTL($ttl) ?? 0)); 45 | 46 | if(is_bool($ret)){ 47 | return $ret; 48 | } 49 | 50 | if(is_array($ret)){ 51 | throw new CacheException(sprintf('error keys: %s', implode(', ', $ret))); 52 | } 53 | 54 | throw new CacheException('unknown apcu_store() error'); 55 | } 56 | 57 | /** 58 | * @inheritdoc 59 | * @throws \chillerlan\SimpleCache\CacheException 60 | */ 61 | public function delete(string $key):bool{ 62 | $ret = apcu_delete($this->checkKey($key)); 63 | 64 | if(is_bool($ret)){ 65 | return $ret; 66 | } 67 | 68 | if(is_array($ret)){ 69 | throw new CacheException(sprintf('error keys: %s', implode(', ', $ret))); 70 | } 71 | 72 | throw new CacheException('unknown apcu_delete() error'); 73 | } 74 | 75 | /** @inheritdoc */ 76 | public function clear():bool{ 77 | return apcu_clear_cache(); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/CacheDriverAbstract.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Smiley 8 | * @license MIT 9 | * 10 | * @phan-file-suppress PhanTypeInvalidThrowsIsInterface 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace chillerlan\SimpleCache; 16 | 17 | use chillerlan\Settings\SettingsContainerInterface; 18 | use Psr\Log\{LoggerAwareInterface, LoggerAwareTrait, LoggerInterface, NullLogger}; 19 | use Psr\SimpleCache\CacheInterface; 20 | use DateInterval, DateTime, InvalidArgumentException, Traversable; 21 | use function is_array, is_int, iterator_to_array, time; 22 | 23 | abstract class CacheDriverAbstract implements CacheInterface, LoggerAwareInterface{ 24 | use LoggerAwareTrait; 25 | 26 | protected SettingsContainerInterface|CacheOptions $options; 27 | 28 | /** 29 | * CacheDriverAbstract constructor. 30 | */ 31 | public function __construct( 32 | SettingsContainerInterface|CacheOptions $options = new CacheOptions, 33 | LoggerInterface $logger = new NullLogger 34 | ){ 35 | $this->options = $options; 36 | $this->logger = $logger; 37 | } 38 | 39 | /** @inheritdoc */ 40 | public function has(string $key):bool{ 41 | return $this->get($key) !== null; 42 | } 43 | 44 | /** @inheritdoc */ 45 | public function getMultiple(iterable $keys, mixed $default = null):iterable{ 46 | $data = []; 47 | 48 | foreach($this->fromIterable($keys) as $key){ 49 | $data[$key] = $this->get($key, $default); 50 | } 51 | 52 | return $data; 53 | } 54 | 55 | /** @inheritdoc */ 56 | public function setMultiple(iterable $values, int|DateInterval|null $ttl = null):bool{ 57 | $return = []; 58 | 59 | foreach($this->fromIterable($values) as $key => $value){ 60 | $return[] = $this->set($key, $value, $ttl); 61 | } 62 | 63 | return $this->checkReturn($return); 64 | } 65 | 66 | /** @inheritdoc */ 67 | public function deleteMultiple(iterable $keys):bool{ 68 | $return = []; 69 | 70 | foreach($this->fromIterable($keys) as $key){ 71 | $return[] = $this->delete($key); 72 | } 73 | 74 | return $this->checkReturn($return); 75 | } 76 | 77 | /** 78 | * @throws \InvalidArgumentException 79 | */ 80 | protected function checkKey(string $key):string{ 81 | 82 | if(empty($key)){ 83 | throw new InvalidArgumentException('cache key is empty'); 84 | } 85 | 86 | return $key; 87 | } 88 | 89 | /** */ 90 | protected function checkKeyArray(array $keys):array{ 91 | 92 | foreach($keys as $key){ 93 | $this->checkKey($key); 94 | } 95 | 96 | return $keys; 97 | } 98 | 99 | /** 100 | * @throws \InvalidArgumentException 101 | */ 102 | protected function fromIterable(iterable $data):array{ 103 | 104 | if(is_array($data)){ 105 | return $data; 106 | } 107 | 108 | if($data instanceof Traversable){ 109 | return iterator_to_array($data); // @codeCoverageIgnore 110 | } 111 | 112 | throw new InvalidArgumentException('invalid data'); 113 | } 114 | 115 | /** */ 116 | protected function getTTL(DateInterval|int|null $ttl):?int{ 117 | 118 | if($ttl instanceof DateInterval){ 119 | return ((new DateTime)->add($ttl)->getTimeStamp() - time()); 120 | } 121 | 122 | if((is_int($ttl) && $ttl > 0)){ 123 | return $ttl; 124 | } 125 | 126 | return null; 127 | } 128 | 129 | /** 130 | * @param bool[]|int[] $booleans 131 | */ 132 | protected function checkReturn(array $booleans):bool{ 133 | 134 | foreach($booleans as $boolean){ 135 | 136 | if(!(bool)$boolean){ 137 | return false; // @codeCoverageIgnore 138 | } 139 | 140 | } 141 | 142 | return true; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/CacheException.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Smiley 8 | * @license MIT 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace chillerlan\SimpleCache; 14 | 15 | class CacheException extends \Exception implements \Psr\SimpleCache\CacheException{ 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/CacheOptions.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2018 Smiley 8 | * @license MIT 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace chillerlan\SimpleCache; 14 | 15 | use chillerlan\Settings\SettingsContainerAbstract; 16 | 17 | /** 18 | * 19 | */ 20 | class CacheOptions extends SettingsContainerAbstract{ 21 | use CacheOptionsTrait; 22 | } 23 | -------------------------------------------------------------------------------- /src/CacheOptionsTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2018 smiley 8 | * @license MIT 9 | */ 10 | 11 | namespace chillerlan\SimpleCache; 12 | 13 | use function is_dir, is_writable, realpath, rtrim, sprintf; 14 | use const DIRECTORY_SEPARATOR; 15 | 16 | trait CacheOptionsTrait{ 17 | 18 | /** 19 | * the file storage root directory 20 | */ 21 | protected string $cacheFilestorage = ''; 22 | 23 | /** 24 | * the key name for the session cache 25 | */ 26 | protected string $cacheSessionkey = '_session_cache'; 27 | 28 | /** 29 | * @throws \Psr\SimpleCache\CacheException 30 | */ 31 | protected function set_cacheFilestorage(string $dir):void{ 32 | $dir = rtrim($dir, '\\/'); 33 | 34 | if(!is_dir($dir)){ 35 | throw new CacheException(sprintf('invalid cachedir "%s"', $dir)); 36 | } 37 | 38 | if(!is_writable($dir)){ 39 | throw new CacheException('cachedir is read-only. permissions?'); 40 | } 41 | 42 | $this->cacheFilestorage = realpath($dir).DIRECTORY_SEPARATOR; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/FileCache.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Smiley 8 | * @license MIT 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace chillerlan\SimpleCache; 14 | 15 | use DateInterval, FilesystemIterator, RecursiveDirectoryIterator, RecursiveIteratorIterator, stdClass; 16 | use function dirname, file_get_contents, file_put_contents, hash, is_dir, 17 | is_file, mkdir, serialize, str_replace, substr, time, unlink, unserialize; 18 | use const DIRECTORY_SEPARATOR; 19 | 20 | class FileCache extends CacheDriverAbstract{ 21 | 22 | /** @inheritdoc */ 23 | public function get(string $key, mixed $default = null):mixed{ 24 | $filename = $this->getFilepath($key); 25 | 26 | if(is_file($filename)){ 27 | $content = file_get_contents($filename); 28 | 29 | if(!empty($content)){ 30 | $data = unserialize($content); 31 | 32 | if($data->ttl === null || $data->ttl > time()){ 33 | return $data->content; 34 | } 35 | 36 | unlink($filename); 37 | } 38 | 39 | } 40 | 41 | return $default; 42 | } 43 | 44 | /** @inheritdoc */ 45 | public function set(string $key, mixed $value, int|DateInterval|null $ttl = null):bool{ 46 | $ttl = $this->getTTL($ttl); 47 | $file = $this->getFilepath($key); 48 | $dir = dirname($file); 49 | 50 | if(!is_dir($dir)){ 51 | mkdir($dir, 0755, true); 52 | } 53 | 54 | $data = new stdClass; 55 | $data->ttl = null; 56 | $data->content = $value; 57 | 58 | if($ttl !== null){ 59 | $data->ttl = (time() + $ttl); 60 | } 61 | 62 | file_put_contents($file, serialize($data)); 63 | 64 | if(is_file($file)){ 65 | return true; 66 | } 67 | 68 | return false; // @codeCoverageIgnore 69 | } 70 | 71 | /** @inheritdoc */ 72 | public function delete(string $key):bool{ 73 | $filename = $this->getFilepath($key); 74 | 75 | if(is_file($filename)){ 76 | return unlink($filename); 77 | } 78 | 79 | return false; 80 | } 81 | 82 | /** @inheritdoc */ 83 | public function clear():bool{ 84 | $dir = $this->options->cacheFilestorage; // copy to avoid calling the magic getter in loop 85 | $return = []; 86 | $iterator = new RecursiveDirectoryIterator( 87 | $dir, 88 | (FilesystemIterator::CURRENT_AS_PATHNAME | FilesystemIterator::SKIP_DOTS) 89 | ); 90 | 91 | foreach(new RecursiveIteratorIterator($iterator) as $path){ 92 | // skip files in the parent directory - cache files are only under /a/ab/[hash] 93 | if(!str_contains(str_replace($dir, '', $path), DIRECTORY_SEPARATOR)){ 94 | continue; 95 | } 96 | 97 | $return[] = unlink($path); 98 | } 99 | 100 | return $this->checkReturn($return); // @codeCoverageIgnore 101 | } 102 | 103 | /** 104 | * 105 | */ 106 | protected function getFilepath(mixed $key):string{ 107 | $h = hash('sha256', $this->checkKey($key)); 108 | $subdir = ''; 109 | 110 | for($i = 1; $i <= 3; $i++){ // @todo: subdir depth to options? 111 | $subdir .= substr($h, 0, $i).DIRECTORY_SEPARATOR; 112 | } 113 | 114 | return $this->options->cacheFilestorage.$subdir.$h; 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/MemcachedCache.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Smiley 8 | * @license MIT 9 | * 10 | * @noinspection PhpComposerExtensionStubsInspection 11 | * @phan-file-suppress PhanUndeclaredClassMethod, PhanUndeclaredTypeProperty, PhanUndeclaredTypeParameter 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace chillerlan\SimpleCache; 17 | 18 | use chillerlan\Settings\SettingsContainerInterface; 19 | use Psr\Log\{LoggerInterface, NullLogger}; 20 | use DateInterval, Memcached; 21 | use function array_keys, extension_loaded; 22 | 23 | /** 24 | * Implements a cache via the Memcached extension 25 | * 26 | * @see https://www.php.net/manual/en/book.memcached.php 27 | */ 28 | class MemcachedCache extends CacheDriverAbstract{ 29 | 30 | protected Memcached $memcached; 31 | 32 | /** 33 | * MemcachedCache constructor. 34 | * 35 | * @throws \chillerlan\SimpleCache\CacheException 36 | */ 37 | public function __construct( 38 | Memcached $memcached, 39 | SettingsContainerInterface|CacheOptions $options = new CacheOptions, 40 | LoggerInterface $logger = new NullLogger 41 | ){ 42 | 43 | if(!extension_loaded('memcached')){ 44 | throw new CacheException('Memcached not installed/enabled'); 45 | } 46 | 47 | parent::__construct($options, $logger); 48 | 49 | $this->memcached = $memcached; 50 | 51 | if(empty($this->memcached->getServerList())){ 52 | throw new CacheException('no memcache server available'); 53 | } 54 | 55 | } 56 | 57 | /** @inheritdoc */ 58 | public function get(string $key, mixed $default = null):mixed{ 59 | $value = $this->memcached->get($this->checkKey($key)); 60 | 61 | if($value !== false){ 62 | return $value; 63 | } 64 | 65 | return $default; 66 | } 67 | 68 | /** @inheritdoc */ 69 | public function set(string $key, mixed $value, int|DateInterval|null $ttl = null):bool{ 70 | return $this->memcached->set($this->checkKey($key), $value, ($this->getTTL($ttl) ?? 0)); 71 | } 72 | 73 | /** @inheritdoc */ 74 | public function delete(string $key):bool{ 75 | return $this->memcached->delete($this->checkKey($key)); 76 | } 77 | 78 | /** @inheritdoc */ 79 | public function clear():bool{ 80 | return $this->memcached->flush(); 81 | } 82 | 83 | /** @inheritdoc */ 84 | public function getMultiple(iterable $keys, mixed $default = null):iterable{ 85 | $keys = $this->checkKeyArray($this->fromIterable($keys)); 86 | $values = $this->memcached->getMulti($keys); 87 | $return = []; 88 | 89 | foreach($keys as $key){ 90 | $return[$key] = ($values[$key] ?? $default); 91 | } 92 | 93 | return $return; 94 | } 95 | 96 | /** @inheritdoc */ 97 | public function setMultiple(iterable $values, int|DateInterval|null $ttl = null):bool{ 98 | $values = $this->fromIterable($values); 99 | 100 | $this->checkKeyArray(array_keys($values)); 101 | 102 | return $this->memcached->setMulti($values, ($this->getTTL($ttl) ?? 0)); 103 | } 104 | 105 | /** @inheritdoc */ 106 | public function deleteMultiple(iterable $keys):bool{ 107 | $keys = $this->checkKeyArray($this->fromIterable($keys)); 108 | 109 | return $this->checkReturn($this->memcached->deleteMulti($keys)); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/MemoryCache.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Smiley 8 | * @license MIT 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace chillerlan\SimpleCache; 14 | 15 | use DateInterval; 16 | use function time; 17 | 18 | /** 19 | * Implements a cache in memory 20 | */ 21 | class MemoryCache extends CacheDriverAbstract{ 22 | 23 | protected array $cache = []; 24 | 25 | /** @inheritdoc */ 26 | public function get(string $key, mixed $default = null):mixed{ 27 | $key = $this->checkKey($key); 28 | 29 | if(isset($this->cache[$key])){ 30 | 31 | if($this->cache[$key]['ttl'] === null || $this->cache[$key]['ttl'] > time()){ 32 | return $this->cache[$key]['content']; 33 | } 34 | 35 | unset($this->cache[$key]); 36 | } 37 | 38 | return $default; 39 | } 40 | 41 | /** @inheritdoc */ 42 | public function set(string $key, mixed $value, int|DateInterval|null $ttl = null):bool{ 43 | $ttl = $this->getTTL($ttl); 44 | 45 | if($ttl !== null){ 46 | $ttl = (time() + $ttl); 47 | } 48 | 49 | $this->cache[$this->checkKey($key)] = ['ttl' => $ttl, 'content' => $value]; 50 | 51 | return true; 52 | } 53 | 54 | /** @inheritdoc */ 55 | public function delete(string $key):bool{ 56 | unset($this->cache[$this->checkKey($key)]); 57 | 58 | return true; 59 | } 60 | 61 | /** @inheritdoc */ 62 | public function clear():bool{ 63 | $this->cache = []; 64 | 65 | return true; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/RedisCache.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Smiley 8 | * @license MIT 9 | * 10 | * @noinspection PhpComposerExtensionStubsInspection 11 | * @phan-file-suppress PhanUndeclaredClassMethod, PhanUndeclaredTypeProperty, PhanUndeclaredTypeParameter 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace chillerlan\SimpleCache; 17 | 18 | use chillerlan\Settings\SettingsContainerInterface; 19 | use Psr\Log\{LoggerInterface, NullLogger}; 20 | use DateInterval, Redis; 21 | 22 | use function array_combine, array_keys, extension_loaded; 23 | 24 | /** 25 | * Implements a cache via Redis 26 | * 27 | * Note: this implementation ignores "multimode" entirely, which would return a Redis instance for every operation 28 | * 29 | * @see https://github.com/phpredis/phpredis/ 30 | */ 31 | class RedisCache extends CacheDriverAbstract{ 32 | 33 | protected Redis $redis; 34 | 35 | /** 36 | * RedisCache constructor. 37 | * 38 | * @throws \Psr\SimpleCache\CacheException 39 | */ 40 | public function __construct( 41 | Redis $redis, 42 | SettingsContainerInterface|CacheOptions $options = new CacheOptions, 43 | LoggerInterface $logger = new NullLogger 44 | ){ 45 | 46 | if(!extension_loaded('redis')){ 47 | throw new CacheException('Redis not installed/enabled'); 48 | } 49 | 50 | parent::__construct($options, $logger); 51 | 52 | $this->redis = $redis; 53 | } 54 | 55 | /** @inheritdoc */ 56 | public function get(string $key, mixed $default = null):mixed{ 57 | $value = $this->redis->get($this->checkKey($key)); 58 | 59 | if($value !== false){ 60 | return $value; 61 | } 62 | 63 | return $default; 64 | } 65 | 66 | /** @inheritdoc */ 67 | public function set(string $key, mixed $value, int|DateInterval|null $ttl = null):bool{ 68 | $key = $this->checkKey($key); 69 | $ttl = $this->getTTL($ttl); 70 | 71 | if($ttl === null){ 72 | return $this->redis->set($key, $value); 73 | } 74 | 75 | return $this->redis->setex($key, $ttl, $value); 76 | } 77 | 78 | /** @inheritdoc */ 79 | public function delete(string $key):bool{ 80 | return (bool)$this->redis->del($this->checkKey($key));; 81 | } 82 | 83 | /** @inheritdoc */ 84 | public function clear():bool{ 85 | return $this->redis->flushDB(); 86 | } 87 | 88 | /** @inheritdoc */ 89 | public function getMultiple(iterable $keys, mixed $default = null):iterable{ 90 | $keys = $this->checkKeyArray($this->fromIterable($keys)); 91 | 92 | // scary 93 | $values = array_combine($keys, $this->redis->mget($keys)); 94 | $return = []; 95 | 96 | foreach($keys as $key){ 97 | /** @phan-suppress-next-line PhanTypeArraySuspiciousNullable */ 98 | $return[$key] = ($values[$key] !== false) ? $values[$key] : $default; 99 | } 100 | 101 | return $return; 102 | } 103 | 104 | /** @inheritdoc */ 105 | public function setMultiple(iterable $values, int|DateInterval|null $ttl = null):bool{ 106 | $values = $this->fromIterable($values); 107 | $ttl = $this->getTTL($ttl); 108 | 109 | if($ttl === null){ 110 | $this->checkKeyArray(array_keys($values)); 111 | 112 | return $this->redis->msetnx($values); 113 | } 114 | 115 | $return = []; 116 | 117 | foreach($values as $key => $value){ 118 | $return[] = $this->set($key, $value, $ttl); 119 | } 120 | 121 | return $this->checkReturn($return); 122 | } 123 | 124 | /** @inheritdoc */ 125 | public function deleteMultiple(iterable $keys):bool{ 126 | $keys = $this->checkKeyArray($this->fromIterable($keys)); 127 | 128 | return (bool)$this->redis->del($keys); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/SessionCache.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Smiley 8 | * @license MIT 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace chillerlan\SimpleCache; 14 | 15 | use chillerlan\Settings\SettingsContainerInterface; 16 | use Psr\Log\{LoggerInterface, NullLogger}; 17 | use DateInterval; 18 | 19 | use function time; 20 | 21 | /** 22 | * Implements a cache via PHP sessions 23 | */ 24 | class SessionCache extends CacheDriverAbstract{ 25 | 26 | protected string $name; 27 | 28 | /** 29 | * SessionCache constructor. 30 | * 31 | * @throws \Psr\SimpleCache\CacheException 32 | */ 33 | public function __construct( 34 | SettingsContainerInterface|CacheOptions $options = new CacheOptions, 35 | LoggerInterface $logger = new NullLogger 36 | ){ 37 | parent::__construct($options, $logger); 38 | 39 | $this->name = $this->options->cacheSessionkey; 40 | 41 | if(empty($this->name)){ 42 | throw new CacheException('invalid session cache key'); 43 | } 44 | 45 | $this->clear(); 46 | } 47 | 48 | /** @inheritdoc */ 49 | public function get(string $key, mixed $default = null):mixed{ 50 | $key = $this->checkKey($key); 51 | 52 | if(isset($_SESSION[$this->name][$key])){ 53 | 54 | if($_SESSION[$this->name][$key]['ttl'] === null || $_SESSION[$this->name][$key]['ttl'] > time()){ 55 | return $_SESSION[$this->name][$key]['content']; 56 | } 57 | 58 | unset($_SESSION[$this->name][$key]); 59 | } 60 | 61 | return $default; 62 | } 63 | 64 | /** @inheritdoc */ 65 | public function set(string $key, mixed $value, int|DateInterval|null $ttl = null):bool{ 66 | $ttl = $this->getTTL($ttl); 67 | 68 | if($ttl !== null){ 69 | $ttl = (time() + $ttl); 70 | } 71 | 72 | $_SESSION[$this->name][$this->checkKey($key)] = ['ttl' => $ttl, 'content' => $value]; 73 | 74 | return true; 75 | } 76 | 77 | /** @inheritdoc */ 78 | public function delete(string $key):bool{ 79 | unset($_SESSION[$this->name][$this->checkKey($key)]); 80 | 81 | return true; 82 | } 83 | 84 | /** @inheritdoc */ 85 | public function clear():bool{ 86 | $_SESSION[$this->name] = []; 87 | 88 | return true; 89 | } 90 | 91 | } 92 | --------------------------------------------------------------------------------