From 2fc3888d3426eb9af9c350b83841b34c593caac4 Mon Sep 17 00:00:00 2001 From: Mihail Binev Date: Wed, 11 Dec 2019 16:44:26 +0100 Subject: [PATCH 1/9] - use the server timezone for time() --- Client/FileClient.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Client/FileClient.php b/Client/FileClient.php index 4f8465e..e9f973a 100644 --- a/Client/FileClient.php +++ b/Client/FileClient.php @@ -15,7 +15,7 @@ use Koded\Caching\{Cache, CacheException}; use Psr\Log\LoggerInterface; use function Koded\Caching\verify_key; -use function Koded\Stdlib\{now, rmdir}; +use function Koded\Stdlib\rmdir; /** * @property FileClient client @@ -89,8 +89,8 @@ public function clear() public function has($key, &$filename = '', &$cache = null) { verify_key($key); - $filename = $this->filename($key, false); + if (false === is_file($filename)) { return false; } @@ -98,7 +98,7 @@ public function has($key, &$filename = '', &$cache = null) /** @noinspection PhpIncludeInspection */ $cache = include $filename; - if ($cache['timestamp'] <= now()->getTimestamp()) { + if ($cache['timestamp'] <= time()) { unlink($filename); return false; @@ -142,7 +142,7 @@ private function filename(string $key, bool $create): string * * @throws CacheException */ - private function setDirectory(string $directory) + private function setDirectory(string $directory): void { // Overrule shell misconfiguration or the web server umask(umask() | 0002); From fc08b413f7c8e0deb5299007386b1554949934f9 Mon Sep 17 00:00:00 2001 From: Mihail Binev Date: Wed, 11 Dec 2019 16:44:55 +0100 Subject: [PATCH 2/9] - phpdoc update --- Client/ClientTrait.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Client/ClientTrait.php b/Client/ClientTrait.php index 4aed49f..12f4a37 100644 --- a/Client/ClientTrait.php +++ b/Client/ClientTrait.php @@ -17,11 +17,10 @@ trait ClientTrait { - /** @var int|null Global TTL for caching, used as default expiration time in cache clients */ private $ttl; - /** @var \Memcached | \Redis | \Predis\Client | \Koded\Caching\Client\FileClient | \Koded\Caching\Client\MemoryClient */ + /** @var \Memcached | \Redis | \Predis\Client | \Koded\Caching\Client\FileClient | \Koded\Caching\Client\MemoryClient | \Koded\Caching\Client\ShmopClient */ private $client; public function getTtl(): ?int From 8f7a21562ca2c0117e6bd7f1cb9d4a4a3759328e Mon Sep 17 00:00:00 2001 From: Mihail Binev Date: Wed, 11 Dec 2019 17:05:35 +0100 Subject: [PATCH 3/9] - removed suppressed tests --- Tests/Integration/FileClientTest.php | 4 -- Tests/Integration/MemcachedClientTest.php | 15 ------- Tests/Integration/MemoryClientTest.php | 4 -- Tests/Integration/PredisClientTest.php | 4 -- Tests/Integration/PredisJsonClientTest.php | 4 -- Tests/Integration/RedisClientTest.php | 4 -- Tests/Integration/RedisJsonClientTest.php | 4 -- Tests/Integration/ShmopClientTest.php | 26 +++++++++++++ .../SimpleCacheIntegrationTrait.php | 39 ++++++++++++------- Tests/RedisWithOnlyJsonSerializerTest.php | 2 +- 10 files changed, 53 insertions(+), 53 deletions(-) create mode 100644 Tests/Integration/ShmopClientTest.php diff --git a/Tests/Integration/FileClientTest.php b/Tests/Integration/FileClientTest.php index 24bd11d..03f5cce 100644 --- a/Tests/Integration/FileClientTest.php +++ b/Tests/Integration/FileClientTest.php @@ -27,10 +27,6 @@ public function createSimpleCache() protected function setUp(): void { - $this->skippedTests = [ - 'testSetMultipleInvalidKeys' => '', - ]; - $this->dir = vfsStream::setup(); parent::setUp(); } diff --git a/Tests/Integration/MemcachedClientTest.php b/Tests/Integration/MemcachedClientTest.php index 026ec78..f98d763 100644 --- a/Tests/Integration/MemcachedClientTest.php +++ b/Tests/Integration/MemcachedClientTest.php @@ -35,21 +35,6 @@ protected function setUp(): void $this->skippedTests = [ 'testBasicUsageWithLongKey' => 'Memcached max key length is 250 chars', - - 'testSet' => '', - 'testSetTtl' => '', - 'testSetExpiredTtl' => '', - 'testGet' => '', - 'testDelete' => '', - 'testClear' => '', - - 'testSetMultiple' => '', - 'testSetMultipleWithIntegerArrayKey' => '', - 'testSetMultipleTtl' => '', - 'testSetMultipleExpiredTtl' => '', - 'testSetMultipleWithGenerator' => '', - 'testGetMultiple' => '', - 'testSetMultipleInvalidKeys' => '', ]; } } diff --git a/Tests/Integration/MemoryClientTest.php b/Tests/Integration/MemoryClientTest.php index 5dff76c..8e9ec3e 100644 --- a/Tests/Integration/MemoryClientTest.php +++ b/Tests/Integration/MemoryClientTest.php @@ -15,10 +15,6 @@ class MemoryClientTest extends SimpleCacheTest */ public function createSimpleCache() { - $this->skippedTests = [ - 'testSetMultipleInvalidKeys' => '', - ]; - return simple_cache_factory('memory'); } } diff --git a/Tests/Integration/PredisClientTest.php b/Tests/Integration/PredisClientTest.php index afcd836..cec07e7 100644 --- a/Tests/Integration/PredisClientTest.php +++ b/Tests/Integration/PredisClientTest.php @@ -15,10 +15,6 @@ class PredisClientTest extends SimpleCacheTest */ public function createSimpleCache() { - $this->skippedTests = [ - 'testSetMultipleInvalidKeys' => '', - ]; - return simple_cache_factory('predis', [ 'host' => getenv('REDIS_SERVER_HOST'), ]); diff --git a/Tests/Integration/PredisJsonClientTest.php b/Tests/Integration/PredisJsonClientTest.php index 7c8f2f2..f56a8a0 100644 --- a/Tests/Integration/PredisJsonClientTest.php +++ b/Tests/Integration/PredisJsonClientTest.php @@ -16,10 +16,6 @@ class PredisJsonClientTest extends SimpleCacheTest */ public function createSimpleCache() { - $this->skippedTests = [ - 'testSetMultipleInvalidKeys' => '', - ]; - return simple_cache_factory('predis', [ 'host' => getenv('REDIS_SERVER_HOST'), 'port' => getenv('REDIS_SERVER_PORT'), diff --git a/Tests/Integration/RedisClientTest.php b/Tests/Integration/RedisClientTest.php index 2e683c9..d324dee 100644 --- a/Tests/Integration/RedisClientTest.php +++ b/Tests/Integration/RedisClientTest.php @@ -15,10 +15,6 @@ class RedisClientTest extends SimpleCacheTest */ public function createSimpleCache() { - $this->skippedTests = [ - 'testSetMultipleInvalidKeys' => '', - ]; - return simple_cache_factory('redis', [ 'host' => getenv('REDIS_SERVER_HOST'), ]); diff --git a/Tests/Integration/RedisJsonClientTest.php b/Tests/Integration/RedisJsonClientTest.php index 070c84b..69ad789 100644 --- a/Tests/Integration/RedisJsonClientTest.php +++ b/Tests/Integration/RedisJsonClientTest.php @@ -16,10 +16,6 @@ class RedisJsonClientTest extends SimpleCacheTest */ public function createSimpleCache() { - $this->skippedTests = [ - 'testSetMultipleInvalidKeys' => '', - ]; - return simple_cache_factory('redis', [ 'host' => getenv('REDIS_SERVER_HOST'), 'serializer' => Serializer::JSON, diff --git a/Tests/Integration/ShmopClientTest.php b/Tests/Integration/ShmopClientTest.php new file mode 100644 index 0000000..1e88715 --- /dev/null +++ b/Tests/Integration/ShmopClientTest.php @@ -0,0 +1,26 @@ +markTestSkipped('shmop extension is not loaded.'); + } + + parent::setUp(); + $this->cache->clear(); + } +} diff --git a/Tests/Integration/SimpleCacheIntegrationTrait.php b/Tests/Integration/SimpleCacheIntegrationTrait.php index 2f7e8b2..d0f7c5e 100644 --- a/Tests/Integration/SimpleCacheIntegrationTrait.php +++ b/Tests/Integration/SimpleCacheIntegrationTrait.php @@ -4,29 +4,42 @@ trait SimpleCacheIntegrationTrait { + protected function tearDown(): void + { + putenv('CACHE_CLIENT='); + + if ($this->cache !== null) { + $this->cache->clear(); + } + + $this->cache = null; + } + + /** overwritten */ + public static function invalidKeys() + { + return self::removeColumnFromInvalidKeys(); + } + + /** overwritten */ + public static function invalidArrayKeys() + { + return self::removeColumnFromInvalidKeys(); + } + /** * Data provider for invalid keys. * Allows ":" in the cache key name. * * @return array */ - public static function invalidKeys() + private static function removeColumnFromInvalidKeys(): array { $keys = parent::invalidKeys(); + unset($keys[14]); - unset($keys[14]); // allow ":" in the key name - - return array_values($keys); + return $keys; } - protected function tearDown(): void - { - putenv('CACHE_CLIENT='); - - if ($this->cache !== null) { - $this->cache->clear(); - } - $this->cache = null; - } } \ No newline at end of file diff --git a/Tests/RedisWithOnlyJsonSerializerTest.php b/Tests/RedisWithOnlyJsonSerializerTest.php index e67c5e8..6445f21 100644 --- a/Tests/RedisWithOnlyJsonSerializerTest.php +++ b/Tests/RedisWithOnlyJsonSerializerTest.php @@ -68,7 +68,7 @@ public function test_should_store_and_retrieve_the_same_cache_item($data) protected function setUp(): void { - $this->markTestSkipped('Redis JSON serializer skipped for now...'); +// $this->markTestSkipped('Redis JSON serializer skipped for now...'); if (false === extension_loaded('redis')) { $this->markTestSkipped('Redis extension is not loaded.'); From d0de220e01bb98b53ab472a622a98c6466f70371 Mon Sep 17 00:00:00 2001 From: Mihail Binev Date: Wed, 11 Dec 2019 17:07:49 +0100 Subject: [PATCH 4/9] - adds a new cache client (ShmopClient) - removed `FileConfiguration` class --- Client/CacheClientFactory.php | 6 +- Client/ShmopClient.php | 164 ++++++++++++++++++++++++++++ Configuration/FileConfiguration.php | 20 ---- 3 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 Client/ShmopClient.php delete mode 100644 Configuration/FileConfiguration.php diff --git a/Client/CacheClientFactory.php b/Client/CacheClientFactory.php index edc65cc..a03a769 100644 --- a/Client/CacheClientFactory.php +++ b/Client/CacheClientFactory.php @@ -14,7 +14,7 @@ use Exception; use Koded\Caching\{Cache, CacheException}; -use Koded\Caching\Configuration\{FileConfiguration, MemcachedConfiguration, PredisConfiguration, RedisConfiguration}; +use Koded\Caching\Configuration\{MemcachedConfiguration, PredisConfiguration, RedisConfiguration}; use Koded\Stdlib\Interfaces\{Configuration, ConfigurationFactory, Serializer}; use Koded\Stdlib\Serializer\SerializerFactory; use Psr\Log\{LoggerInterface, NullLogger}; @@ -58,8 +58,10 @@ public function new(string $client = ''): Cache /** @var PredisConfiguration $config */ return $this->createPredisClient($config); + case 'shmop': + return new ShmopClient((string)$config->get('dir'), $config->get('ttl')); + case 'file': - /** @var FileConfiguration $config */ return new FileClient($this->getLogger($config), (string)$config->get('dir'), $config->get('ttl')); case 'memory': diff --git a/Client/ShmopClient.php b/Client/ShmopClient.php new file mode 100644 index 0000000..29d36df --- /dev/null +++ b/Client/ShmopClient.php @@ -0,0 +1,164 @@ + + * + * Please view the LICENSE distributed with this source code + * for the full copyright and license information. + * + */ + +namespace Koded\Caching\Client; + +use Exception; +use Koded\Caching\{Cache, CacheException}; +use function Koded\Caching\verify_key; + +/** + * @property ShmopClient client + * + */ +final class ShmopClient implements Cache +{ + use ClientTrait, MultiplesTrait; + + /** @var string */ + private $dir; + + public function __construct(string $dir, ?int $ttl) + { + $this->dir = $dir; + $this->ttl = $ttl; + $this->setDirectory($dir); + } + + + public function get($key, $default = null) + { + if (false === $this->has($key, $filename)) { + return $default; + } + + try { + $resource = shmop_open(fileinode($filename), 'a', 0, 0); + return unserialize(shmop_read($resource, 0, shmop_size($resource))); + } finally { + shmop_close($resource); + } + } + + + public function set($key, $value, $ttl = null) + { + verify_key($key); + + if (1 > $expiration = $this->timestampWithGlobalTtl($ttl, Cache::DATE_FAR_FAR_AWAY)) { + // The item is considered expired and must be deleted + return $this->delete($key); + } + + $value = serialize($value); + $size = strlen($value); + $filename = $this->filename($key, true); + + try { + $resource = shmop_open(fileinode($filename), 'n', 0666, $size); + } catch (Exception $e) { + $resource = shmop_open(fileinode($filename), 'w', 0666, $size); + } + + try { + return shmop_write($resource, $value, 0) === $size + && false !== file_put_contents($filename . '-ttl', $expiration); + + } finally { + shmop_close($resource); + } + } + + + public function delete($key) + { + if (false === $this->has($key, $filename)) { + return true; + } + + return $this->expire($filename); + } + + + public function clear() + { + foreach ((glob($this->dir . 'shmop-*.cache*') ?: []) as $filename) { + $this->expire($filename); + } + return true; + } + + + public function has($key, &$filename = '') + { + verify_key($key); + $filename = $this->filename($key, false); + $expiration = (int)(@file_get_contents($filename . '-ttl') ?: 0); + + if ($expiration <= time()) { + $this->expire($filename); + return false; + } + + return true; + } + + + private function filename(string $key, bool $create): string + { + $filename = $this->dir . 'shmop-' . sha1($key) . '.cache'; + + if ($create) { + touch($filename); + touch($filename . '-ttl'); + chmod($filename, 0666); + chmod($filename . '-ttl', 0666); + } + + return $filename; + } + + /** + * Prepares the cache directory. + * + * @param string $directory + * + * @throws CacheException + */ + private function setDirectory(string $directory): void + { + // Overrule shell misconfiguration or the web server + umask(umask() | 0002); + $dir = $directory ?: sys_get_temp_dir(); + $dir = rtrim($dir, '/') . '/'; + + if (false === is_dir($dir) && false === mkdir($dir, 0775, true)) { + throw CacheException::forCreatingDirectory($dir); + } + + $this->dir = $dir; + } + + private function expire(string $filename): bool + { + if (false === $resource = @shmop_open(fileinode($filename), 'w', 0, 0)) { + return false; + } + + try { + unlink($filename . '-ttl'); + return shmop_delete($resource); + } finally { + shmop_close($resource); + } + } +} diff --git a/Configuration/FileConfiguration.php b/Configuration/FileConfiguration.php deleted file mode 100644 index 22b06a7..0000000 --- a/Configuration/FileConfiguration.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * Please view the LICENSE distributed with this source code - * for the full copyright and license information. - * - */ - -namespace Koded\Caching\Configuration; - -use Koded\Stdlib\Immutable; -use Koded\Stdlib\Interfaces\Configuration; - -final class FileConfiguration extends Immutable implements Configuration -{ -} \ No newline at end of file From d88d892b942c685f2a8fc9aaa01c130f447d3d92 Mon Sep 17 00:00:00 2001 From: Mihail Binev Date: Wed, 11 Dec 2019 17:08:13 +0100 Subject: [PATCH 5/9] version bump --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index e3a4f19..cc6612c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.0 \ No newline at end of file +2.3.0 \ No newline at end of file From f6655ff488f0707bea868c527aad26c6deeda38e Mon Sep 17 00:00:00 2001 From: Mihail Binev Date: Wed, 11 Dec 2019 17:08:50 +0100 Subject: [PATCH 6/9] - updated the composer.json --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 66165b7..93ba021 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,8 @@ "ext-memcached": "For caching in Memcached", "predis/predis": "For using Redis without ext-redis extension", "ext-igbinary": "For Redis igbinary support", - "ext-msgpack": "For de/serializing the cache data" + "ext-msgpack": "For de/serializing the cache data", + "ext-shmop": "For shared-memory caching" }, "autoload": { "psr-4": { From ccce169ff850904a01a66249edba05b498f7899c Mon Sep 17 00:00:00 2001 From: Mihail Binev Date: Wed, 11 Dec 2019 17:09:08 +0100 Subject: [PATCH 7/9] Updates the README --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f3a070..6796aae 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,18 @@ There are many configuration options for this package. Please refer to [Predis configuration page][6]. +### Shared Memory (shmop) + +Requires a [PHP shmop extension][11]. + +```php +$cache = simple_cache_factory('shmop', [ + 'dir' => '/path/to/app/cache', // optional + 'ttl' => null, // global TTL +]); +``` + + ### FileConfiguration This is the slowest cache client, please avoid it for production environments. @@ -281,4 +293,5 @@ The code is distributed under the terms of [The 3-Clause BSD license](LICENSE). [7]: https://github.com/phpredis/phpredis#connect-open [8]: http://php.net/sys_get_temp_dir [9]: http://php.net/json_encode -[10]: https://www.php-fig.org/psr/psr-16/ \ No newline at end of file +[10]: https://www.php-fig.org/psr/psr-16/ +[11]: https://www.php.net/manual/en/book.shmop.php From bf51c8fd95f39248ef49d5b01a50344951ab8606 Mon Sep 17 00:00:00 2001 From: Mihail Binev Date: Wed, 11 Dec 2019 17:24:33 +0100 Subject: [PATCH 8/9] - updates the matrix --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index b351fcb..84bce3e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,13 @@ cache: php: - 7.2 - 7.3 + - 7.4 + - nightly matrix: fast_finish: true + allow_failures: + - php: nightly services: - redis-server From 2cfe92115f595b7f1bfac7b38f267b83d3d59d49 Mon Sep 17 00:00:00 2001 From: Mihail Binev Date: Wed, 11 Dec 2019 17:40:29 +0100 Subject: [PATCH 9/9] - updates exception handling (redis) --- Client/CacheClientFactory.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Client/CacheClientFactory.php b/Client/CacheClientFactory.php index a03a769..10ea18d 100644 --- a/Client/CacheClientFactory.php +++ b/Client/CacheClientFactory.php @@ -12,6 +12,7 @@ namespace Koded\Caching\Client; +use Error; use Exception; use Koded\Caching\{Cache, CacheException}; use Koded\Caching\Configuration\{MemcachedConfiguration, PredisConfiguration, RedisConfiguration}; @@ -136,7 +137,7 @@ private function newRedis(RedisConfiguration $conf): \Redis catch (\RedisException $e) { error_log('[Redis] ' . $e->getMessage()); throw CacheException::withConnectionErrorFor('Redis'); - } catch (Exception $e) { + } catch (Exception | Error $e) { throw CacheException::from($e); } }