diff --git a/readme.md b/readme.md index 420b42d1..fa548c39 100644 --- a/readme.md +++ b/readme.md @@ -7,19 +7,32 @@ Nette Caching [![Latest Stable Version](https://poser.pugx.org/nette/caching/v/stable)](https://github.com/nette/caching/releases) [![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/caching/blob/master/license.md) + +Introduction +------------ + Cache accelerates your application by storing data - once hardly retrieved - for future use. -Install it using Composer: +Documentation can be found on the [website](https://doc.nette.org/caching). + + +Installation +------------ + +The recommended way to install Nette Caching is via Composer: ``` composer require nette/caching ``` -The last stable release requires PHP version 5.6 or newer (is compatible with PHP 7.0 and 7.1). The dev-master version requires PHP 7.1. +It requires PHP version 5.6 and supports PHP up to 7.2. The dev-master version requires PHP 7.1. -Nette offers a very intuitive API for cache manipulation. After all, you wouldn't expect anything else, right? ;-) -Before we show you the first example, we need to think about place where to store data physically. We can use a database, //Memcached// server, -or the most available storage - hard drive: + +Usage +----- + +Nette Caching offers a very intuitive API for cache manipulation. Before we show you the first example, we need to think about place where +to store data physically. We can use a database, Memcached server, or the most available storage - hard drive: ```php // the `temp` directory will be the storage @@ -82,7 +95,7 @@ $cache = new Cache($storage, 'htmlOutput'); Caching Function Results -------------------------- +------------------------ Caching the result of a function or method call can be achieved using the `call()` method: @@ -94,7 +107,7 @@ The `gethostbyaddr($ip)` will therefore be called only once and next time, only different results are cached. Output Caching ------------------- +-------------- The output can be cached not only in templates: @@ -217,9 +230,8 @@ $cache->clean(array( ``` - Cache Storage --------- +------------- In addition to already mentioned `FileStorage`, Nette Framework also provides `MemcachedStorage` which stores data to the `Memcached` server, and also `MemoryStorage` for storing data in memory for duration of the request. The special `DevNullStorage`, which does precisely nothing, can be used for testing, when we want to eliminate the influence of caching. @@ -238,8 +250,8 @@ The solution is to modify application behaviour so that data are created only by or use an anonymous function: ```php -$result = $cache->save($key, function() { // or callback(...) - return buildData(); // difficult operation +$result = $cache->save($key, function() { + return buildData(); // difficult operation }); ``` diff --git a/src/Caching/Storages/SQLiteStorage.php b/src/Caching/Storages/SQLiteStorage.php index 73cf538d..2e33b7a6 100644 --- a/src/Caching/Storages/SQLiteStorage.php +++ b/src/Caching/Storages/SQLiteStorage.php @@ -34,6 +34,8 @@ public function __construct($path) $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $this->pdo->exec(' PRAGMA foreign_keys = ON; + PRAGMA case_sensitive_like = ON; + CREATE TABLE IF NOT EXISTS cache ( key BLOB NOT NULL PRIMARY KEY, data BLOB NOT NULL, @@ -58,6 +60,7 @@ public function __construct($path) */ public function read(string $key) { + $key = self::sanitize($key); $stmt = $this->pdo->prepare('SELECT data, slide FROM cache WHERE key=? AND (expire IS NULL OR expire >= ?)'); $stmt->execute([$key, time()]); if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { @@ -75,6 +78,7 @@ public function read(string $key) */ public function bulkRead(array $keys): array { + $keys = array_map([self::class, 'sanitize'], $keys); $stmt = $this->pdo->prepare('SELECT key, data, slide FROM cache WHERE key IN (?' . str_repeat(',?', count($keys) - 1) . ') AND (expire IS NULL OR expire >= ?)'); $stmt->execute(array_merge($keys, [time()])); $result = []; @@ -83,7 +87,7 @@ public function bulkRead(array $keys): array if ($row['slide'] !== null) { $updateSlide[] = $row['key']; } - $result[$row['key']] = unserialize($row['data']); + $result[str_replace("\x01", Cache::NAMESPACE_SEPARATOR, $row['key'])] = unserialize($row['data']); } if (!empty($updateSlide)) { $stmt = $this->pdo->prepare('UPDATE cache SET expire = ? + slide WHERE key IN(?' . str_repeat(',?', count($updateSlide) - 1) . ')'); @@ -106,6 +110,7 @@ public function lock(string $key): void */ public function write(string $key, $data, array $dependencies): void { + $key = self::sanitize($key); $expire = isset($dependencies[Cache::EXPIRATION]) ? $dependencies[Cache::EXPIRATION] + time() : null; $slide = isset($dependencies[Cache::SLIDING]) ? $dependencies[Cache::EXPIRATION] : null; @@ -131,7 +136,7 @@ public function write(string $key, $data, array $dependencies): void public function remove(string $key): void { $this->pdo->prepare('DELETE FROM cache WHERE key=?') - ->execute([$key]); + ->execute([self::sanitize($key)]); } @@ -142,18 +147,31 @@ public function clean(array $conditions): void { if (!empty($conditions[Cache::ALL])) { $this->pdo->prepare('DELETE FROM cache')->execute(); + return; + } - } else { - $sql = 'DELETE FROM cache WHERE expire < ?'; - $args = [time()]; + $sql = 'DELETE FROM cache WHERE expire < ?'; + $args = [time()]; - if (!empty($conditions[Cache::TAGS])) { - $tags = $conditions[Cache::TAGS]; - $sql .= ' OR key IN (SELECT key FROM tags WHERE tag IN (?' . str_repeat(',?', count($tags) - 1) . '))'; - $args = array_merge($args, $tags); - } + if (!empty($conditions[Cache::TAGS])) { + $tags = $conditions[Cache::TAGS]; + $sql .= ' OR key IN (SELECT key FROM tags WHERE tag IN (?' . str_repeat(',?', count($tags) - 1) . '))'; + $args = array_merge($args, $tags); + } - $this->pdo->prepare($sql)->execute($args); + if (!empty($conditions[Cache::NAMESPACES])) { + foreach ($conditions[Cache::NAMESPACES] as $namespace) { + $sql .= ' OR key LIKE ?'; + $args[] = self::sanitize($namespace . Cache::NAMESPACE_SEPARATOR . '%'); + } } + + $this->pdo->prepare($sql)->execute($args); + } + + + private function sanitize($key) + { + return str_replace(Cache::NAMESPACE_SEPARATOR, "\x01", $key); } } diff --git a/tests/Storages/SQLiteStorage.clean-namespace.phpt b/tests/Storages/SQLiteStorage.clean-namespace.phpt new file mode 100644 index 00000000..b8cd1f29 --- /dev/null +++ b/tests/Storages/SQLiteStorage.clean-namespace.phpt @@ -0,0 +1,94 @@ +save('test1', 'David'); +$cacheA->save('test2', 'Grudl'); + +$cacheB->save('test1', 'Barry'); +$cacheB->save('test2', 'Allen'); + +$cacheC->save('test1', 'Oliver'); +$cacheC->save('test2', 'Queen'); + +$cacheD->save('test1', 'Bruce'); +$cacheD->save('test2', 'Wayne'); + + +/* + * Check if fill wass successfull + */ +Assert::same('David', $cacheA->load('test1')); +Assert::same('Grudl', $cacheA->load('test2')); + +Assert::same('Barry', $cacheB->load('test1')); +Assert::same('Allen', $cacheB->load('test2')); + +Assert::same('Oliver', $cacheC->load('test1')); +Assert::same('Queen', $cacheC->load('test2')); + +Assert::same('Bruce', $cacheD->load('test1')); +Assert::same('Wayne', $cacheD->load('test2')); + + +/* + * Clean one namespace + */ +$storage->clean([Cache::NAMESPACES => [$cacheB->getNamespace()]]); + +Assert::same('David', $cacheA->load('test1')); +Assert::same('Grudl', $cacheA->load('test2')); + +// Only these should be null now +Assert::null($cacheB->load('test1')); +Assert::null($cacheB->load('test2')); + +Assert::same('Oliver', $cacheC->load('test1')); +Assert::same('Queen', $cacheC->load('test2')); + +Assert::same('Bruce', $cacheD->load('test1')); +Assert::same('Wayne', $cacheD->load('test2')); + + +/* + * Test cleaning multiple namespaces + */ +$storage->clean([Cache::NAMESPACES => [$cacheC->getNamespace(), $cacheD->getNamespace()]]); + +Assert::same('David', $cacheA->load('test1')); +Assert::same('Grudl', $cacheA->load('test2')); + +// All other should be null +Assert::null($cacheB->load('test1')); +Assert::null($cacheB->load('test2')); + +Assert::null($cacheC->load('test1')); +Assert::null($cacheC->load('test2')); + +Assert::null($cacheD->load('test1')); +Assert::null($cacheD->load('test2'));