diff --git a/system/Cache/Handlers/BaseHandler.php b/system/Cache/Handlers/BaseHandler.php index 10c2b3e82fe0..b2d17d369c2e 100644 --- a/system/Cache/Handlers/BaseHandler.php +++ b/system/Cache/Handlers/BaseHandler.php @@ -13,6 +13,7 @@ use Closure; use CodeIgniter\Cache\CacheInterface; +use Exception; /** * Base class for cache handling @@ -41,4 +42,18 @@ public function remember(string $key, int $ttl, Closure $callback) return $value; } + + //-------------------------------------------------------------------- + + /** + * Deletes items from the cache store matching a given pattern. + * + * @param string $pattern Cache items glob-style pattern + * + * @throws Exception + */ + public function deleteMatching(string $pattern) + { + throw new Exception('The deleteMatching method is not implemented.'); + } } diff --git a/system/Cache/Handlers/DummyHandler.php b/system/Cache/Handlers/DummyHandler.php index b888a98cec0e..5f6e50d5762a 100644 --- a/system/Cache/Handlers/DummyHandler.php +++ b/system/Cache/Handlers/DummyHandler.php @@ -88,6 +88,20 @@ public function delete(string $key) //-------------------------------------------------------------------- + /** + * Deletes items from the cache store matching a given pattern. + * + * @param string $pattern Cache items glob-style pattern + * + * @return boolean + */ + public function deleteMatching(string $pattern) + { + return true; + } + + //-------------------------------------------------------------------- + /** * Performs atomic incrementation of a raw stored value. * diff --git a/system/Cache/Handlers/FileHandler.php b/system/Cache/Handlers/FileHandler.php index a2cdbe8c135c..d184d19f0dd4 100644 --- a/system/Cache/Handlers/FileHandler.php +++ b/system/Cache/Handlers/FileHandler.php @@ -160,6 +160,30 @@ public function delete(string $key) //-------------------------------------------------------------------- + /** + * Deletes items from the cache store matching a given pattern. + * + * @param string $pattern Cache items glob-style pattern + * + * @return boolean + */ + public function deleteMatching(string $pattern) + { + $success = true; + + foreach (glob($this->path . $pattern) as $filename) + { + if (! is_file($filename) || ! @unlink($filename)) + { + $success = false; + } + } + + return $success; + } + + //-------------------------------------------------------------------- + /** * Performs atomic incrementation of a raw stored value. * diff --git a/system/Cache/Handlers/MemcachedHandler.php b/system/Cache/Handlers/MemcachedHandler.php index ea1a9e3ed1d0..8758b01423cc 100644 --- a/system/Cache/Handlers/MemcachedHandler.php +++ b/system/Cache/Handlers/MemcachedHandler.php @@ -249,6 +249,20 @@ public function delete(string $key) //-------------------------------------------------------------------- + /** + * Deletes items from the cache store matching a given pattern. + * + * @param string $pattern Cache items glob-style pattern + * + * @throws Exception + */ + public function deleteMatching(string $pattern) + { + throw new Exception('The deleteMatching method is not implemented for Memcached. You must select File, Redis or Predis handlers to use it.'); + } + + //-------------------------------------------------------------------- + /** * Performs atomic incrementation of a raw stored value. * diff --git a/system/Cache/Handlers/PredisHandler.php b/system/Cache/Handlers/PredisHandler.php index 145d560f28c3..5b932733cc88 100644 --- a/system/Cache/Handlers/PredisHandler.php +++ b/system/Cache/Handlers/PredisHandler.php @@ -15,6 +15,7 @@ use Config\Cache; use Exception; use Predis\Client; +use Predis\Collection\Iterator\Keyspace; /** * Predis cache handler @@ -184,6 +185,30 @@ public function delete(string $key) //-------------------------------------------------------------------- + /** + * Deletes items from the cache store matching a given pattern. + * + * @param string $pattern Cache items glob-style pattern + * + * @return boolean + */ + public function deleteMatching(string $pattern) + { + $success = true; + + foreach (new Keyspace($this->redis, $pattern) as $key) + { + if ($this->redis->del($key) !== 1) + { + $success = false; + } + } + + return $success; + } + + //-------------------------------------------------------------------- + /** * Performs atomic incrementation of a raw stored value. * diff --git a/system/Cache/Handlers/RedisHandler.php b/system/Cache/Handlers/RedisHandler.php index a7fdcb9da2d5..d5fad5b86ce8 100644 --- a/system/Cache/Handlers/RedisHandler.php +++ b/system/Cache/Handlers/RedisHandler.php @@ -223,6 +223,34 @@ public function delete(string $key) //-------------------------------------------------------------------- + /** + * Deletes items from the cache store matching a given pattern. + * + * @param string $pattern Cache items glob-style pattern + * + * @return boolean + */ + public function deleteMatching(string $pattern) + { + $success = true; + $iterator = null; + + while ($keys = $this->redis->scan($iterator, $pattern)) + { + foreach ($keys as $key) + { + if ($this->redis->del($key) !== 1) + { + $success = false; + } + } + } + + return $success; + } + + //-------------------------------------------------------------------- + /** * Performs atomic incrementation of a raw stored value. * diff --git a/system/Cache/Handlers/WincacheHandler.php b/system/Cache/Handlers/WincacheHandler.php index 2ad1238e9592..f6ca9d8d5d40 100644 --- a/system/Cache/Handlers/WincacheHandler.php +++ b/system/Cache/Handlers/WincacheHandler.php @@ -12,6 +12,7 @@ namespace CodeIgniter\Cache\Handlers; use Config\Cache; +use Exception; /** * Cache handler for WinCache from Microsoft & IIS. @@ -113,6 +114,22 @@ public function delete(string $key) //-------------------------------------------------------------------- + /** + * Deletes items from the cache store matching a given pattern. + * + * @param string $pattern Cache items glob-style pattern + * + * @throws Exception + * + * @codeCoverageIgnore + */ + public function deleteMatching(string $pattern) + { + throw new Exception('The deleteMatching method is not implemented for Wincache. You must select File, Redis or Predis handlers to use it.'); + } + + //-------------------------------------------------------------------- + /** * Performs atomic incrementation of a raw stored value. * diff --git a/system/Test/Mock/MockCache.php b/system/Test/Mock/MockCache.php index 67356cf50f62..368b3b0f7ee9 100644 --- a/system/Test/Mock/MockCache.php +++ b/system/Test/Mock/MockCache.php @@ -123,6 +123,28 @@ public function delete(string $key) //-------------------------------------------------------------------- + /** + * Deletes items from the cache store matching a given pattern. + * + * @param string $pattern Cache items glob-style pattern + * + * @return boolean + */ + public function deleteMatching(string $pattern) + { + foreach (array_keys($this->cache) as $key) + { + if (fnmatch($pattern, $key)) + { + unset($this->cache[$key]); + } + } + + return true; + } + + //-------------------------------------------------------------------- + /** * Performs atomic incrementation of a raw stored value. * diff --git a/tests/system/Cache/Handlers/DummyHandlerTest.php b/tests/system/Cache/Handlers/DummyHandlerTest.php index 72c89db5b395..4a200fa695b1 100644 --- a/tests/system/Cache/Handlers/DummyHandlerTest.php +++ b/tests/system/Cache/Handlers/DummyHandlerTest.php @@ -41,6 +41,11 @@ public function testDelete() $this->assertTrue($this->dummyHandler->delete('key')); } + public function testDeleteMatching() + { + $this->assertTrue($this->dummyHandler->deleteMatching('key*')); + } + public function testIncrement() { $this->assertTrue($this->dummyHandler->increment('key')); diff --git a/tests/system/Cache/Handlers/FileHandlerTest.php b/tests/system/Cache/Handlers/FileHandlerTest.php index 69c0460f87e2..b2b126634f8b 100644 --- a/tests/system/Cache/Handlers/FileHandlerTest.php +++ b/tests/system/Cache/Handlers/FileHandlerTest.php @@ -13,6 +13,7 @@ class FileHandlerTest extends CIUnitTestCase private static $key1 = 'key1'; private static $key2 = 'key2'; private static $key3 = 'key3'; + private static $key4 = 'another_key'; private static function getKeyArray() { @@ -20,6 +21,7 @@ private static function getKeyArray() self::$key1, self::$key2, self::$key3, + self::$key4, ]; } @@ -133,6 +135,26 @@ public function testDelete() $this->assertFalse($this->fileHandler->delete(self::$dummy)); } + public function testDeleteMatching() + { + $this->fileHandler->save(self::$key1, 'value'); + $this->fileHandler->save(self::$key2, 'value2'); + $this->fileHandler->save(self::$key3, 'value3'); + $this->fileHandler->save(self::$key4, 'value4'); + + $this->assertSame('value', $this->fileHandler->get(self::$key1)); + $this->assertSame('value2', $this->fileHandler->get(self::$key2)); + $this->assertSame('value3', $this->fileHandler->get(self::$key3)); + $this->assertSame('value4', $this->fileHandler->get(self::$key4)); + + $this->assertTrue($this->fileHandler->deleteMatching('key*')); + + $this->assertNull($this->fileHandler->get(self::$key1)); + $this->assertNull($this->fileHandler->get(self::$key2)); + $this->assertNull($this->fileHandler->get(self::$key3)); + $this->assertSame('value4', $this->fileHandler->get(self::$key4)); + } + public function testIncrement() { $this->fileHandler->save(self::$key1, 1); diff --git a/tests/system/Cache/Handlers/MemcachedHandlerTest.php b/tests/system/Cache/Handlers/MemcachedHandlerTest.php index 32abcbec9a4c..ff3ab1027942 100644 --- a/tests/system/Cache/Handlers/MemcachedHandlerTest.php +++ b/tests/system/Cache/Handlers/MemcachedHandlerTest.php @@ -5,6 +5,7 @@ use CodeIgniter\CLI\CLI; use CodeIgniter\Test\CIUnitTestCase; use Config\Cache; +use Exception; class MemcachedHandlerTest extends CIUnitTestCase { @@ -89,6 +90,14 @@ public function testDelete() $this->assertFalse($this->memcachedHandler->delete(self::$dummy)); } + public function testDeleteMatching() + { + // Not implemented for Memcached, should throw an exception + $this->expectException(Exception::class); + + $this->memcachedHandler->deleteMatching('key*'); + } + public function testIncrement() { $this->memcachedHandler->save(self::$key1, 1); diff --git a/tests/system/Cache/Handlers/PredisHandlerTest.php b/tests/system/Cache/Handlers/PredisHandlerTest.php index 121d53569024..7261253eaba4 100644 --- a/tests/system/Cache/Handlers/PredisHandlerTest.php +++ b/tests/system/Cache/Handlers/PredisHandlerTest.php @@ -12,12 +12,15 @@ class PredisHandlerTest extends CIUnitTestCase private static $key1 = 'key1'; private static $key2 = 'key2'; private static $key3 = 'key3'; + private static $key4 = 'another_key'; + private static function getKeyArray() { return [ self::$key1, self::$key2, self::$key3, + self::$key4, ]; } @@ -97,6 +100,26 @@ public function testDelete() $this->assertFalse($this->PredisHandler->delete(self::$dummy)); } + public function testDeleteMatching() + { + $this->PredisHandler->save(self::$key1, 'value'); + $this->PredisHandler->save(self::$key2, 'value2'); + $this->PredisHandler->save(self::$key3, 'value3'); + $this->PredisHandler->save(self::$key4, 'value4'); + + $this->assertSame('value', $this->PredisHandler->get(self::$key1)); + $this->assertSame('value2', $this->PredisHandler->get(self::$key2)); + $this->assertSame('value3', $this->PredisHandler->get(self::$key3)); + $this->assertSame('value4', $this->PredisHandler->get(self::$key4)); + + $this->assertTrue($this->PredisHandler->deleteMatching('key*')); + + $this->assertNull($this->PredisHandler->get(self::$key1)); + $this->assertNull($this->PredisHandler->get(self::$key2)); + $this->assertNull($this->PredisHandler->get(self::$key3)); + $this->assertSame('value4', $this->PredisHandler->get(self::$key4)); + } + public function testClean() { $this->PredisHandler->save(self::$key1, 1); diff --git a/tests/system/Cache/Handlers/RedisHandlerTest.php b/tests/system/Cache/Handlers/RedisHandlerTest.php index ab390bd69869..50f8ed2a7b72 100644 --- a/tests/system/Cache/Handlers/RedisHandlerTest.php +++ b/tests/system/Cache/Handlers/RedisHandlerTest.php @@ -12,12 +12,15 @@ class RedisHandlerTest extends CIUnitTestCase private static $key1 = 'key1'; private static $key2 = 'key2'; private static $key3 = 'key3'; + private static $key4 = 'another_key'; + private static function getKeyArray() { return [ self::$key1, self::$key2, self::$key3, + self::$key4, ]; } @@ -97,6 +100,26 @@ public function testDelete() $this->assertFalse($this->redisHandler->delete(self::$dummy)); } + public function testDeleteMatching() + { + $this->redisHandler->save(self::$key1, 'value'); + $this->redisHandler->save(self::$key2, 'value2'); + $this->redisHandler->save(self::$key3, 'value3'); + $this->redisHandler->save(self::$key4, 'value4'); + + $this->assertSame('value', $this->redisHandler->get(self::$key1)); + $this->assertSame('value2', $this->redisHandler->get(self::$key2)); + $this->assertSame('value3', $this->redisHandler->get(self::$key3)); + $this->assertSame('value4', $this->redisHandler->get(self::$key4)); + + $this->assertTrue($this->redisHandler->deleteMatching('key*')); + + $this->assertNull($this->redisHandler->get(self::$key1)); + $this->assertNull($this->redisHandler->get(self::$key2)); + $this->assertNull($this->redisHandler->get(self::$key3)); + $this->assertSame('value4', $this->redisHandler->get(self::$key4)); + } + //FIXME: I don't like all Hash logic very much. It's wasting memory. //public function testIncrement() //{ diff --git a/user_guide_src/source/libraries/caching.rst b/user_guide_src/source/libraries/caching.rst index 3f5b8109c30a..c3f7720d31bc 100644 --- a/user_guide_src/source/libraries/caching.rst +++ b/user_guide_src/source/libraries/caching.rst @@ -136,6 +136,27 @@ Class Reference $cache->delete('cache_item_id'); +.. php:method:: deleteMatching($pattern): bool + + :param string $pattern: glob-style pattern to match cached items keys + :returns: ``true`` on success, ``false`` if one of the deletes fails + :rtype: bool + + This method will delete multiple items from the cache store at once by + matching their keys against a glob pattern. + If one of the cache item deletion fails, the method will return FALSE. + + .. important:: This method is only implemented for File, Redis and Predis handlers. + Due to limitations, it couldn't be implemented for Memcached and Wincache handlers. + + Example:: + + $cache->deleteMatching('prefix_*'); // deletes all keys starting with "prefix_" + $cache->deleteMatching('*_suffix'); // deletes all keys ending with "_suffix" + + For more information on glob-style syntax, please see + `https://en.wikipedia.org/wiki/Glob_(programming) `_. + .. php:method:: increment($key[, $offset = 1]): mixed :param string $key: Cache ID