From 5fecd7e90a01746234cb3d5c3855f21c5f2334c9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 6 Jun 2025 00:47:24 +0200 Subject: [PATCH 01/17] composer: require stable packages outside of nette --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index d4782da..4222653 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ "nette/di": "^3.1 || ^4.0", "latte/latte": "^2.11 || ^3.0.12", "tracy/tracy": "^2.9", - "phpstan/phpstan": "^1.0", - "psr/simple-cache": "^2.0 || ^3.0" + "psr/simple-cache": "^2.0 || ^3.0", + "phpstan/phpstan-nette": "^2.0@stable" }, "conflict": { "latte/latte": ">=3.0.0 <3.0.12" From 06fa256b5846880817775ec9963bceffa7f141ac Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 6 Jun 2025 01:17:40 +0200 Subject: [PATCH 02/17] composer: added psr-4 loader --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4222653..efbb30b 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,10 @@ "ext-pdo_sqlite": "to use SQLiteStorage or SQLiteJournal" }, "autoload": { - "classmap": ["src/"] + "classmap": ["src/"], + "psr-4": { + "Nette\\": "src" + } }, "minimum-stability": "dev", "scripts": { From d5074d44685962eaf4d79c17d0883e48878a183b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Km=C3=ADnek?= Date: Thu, 8 Aug 2024 15:11:08 +0200 Subject: [PATCH 03/17] MemcachedStorage: fixed warning when Memcached::getMulti() returns false (#79) --- src/Caching/Storages/MemcachedStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Caching/Storages/MemcachedStorage.php b/src/Caching/Storages/MemcachedStorage.php index 6fe9ce4..5176757 100644 --- a/src/Caching/Storages/MemcachedStorage.php +++ b/src/Caching/Storages/MemcachedStorage.php @@ -105,7 +105,7 @@ public function bulkRead(array $keys): array { $prefixedKeys = array_map(fn($key) => urlencode($this->prefix . $key), $keys); $keys = array_combine($prefixedKeys, $keys); - $metas = $this->memcached->getMulti($prefixedKeys); + $metas = $this->memcached->getMulti($prefixedKeys) ?: []; $result = []; $deleteKeys = []; foreach ($metas as $prefixedKey => $meta) { From f5e8a83b4f55f219f28b7450e9b59f5cf2e69654 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 13 Aug 2024 22:19:58 +0200 Subject: [PATCH 04/17] PDO: error mode is ERRMODE_EXCEPTION by default --- src/Caching/Storages/SQLiteJournal.php | 1 - src/Caching/Storages/SQLiteStorage.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Caching/Storages/SQLiteJournal.php b/src/Caching/Storages/SQLiteJournal.php index 2674783..cf96ca3 100644 --- a/src/Caching/Storages/SQLiteJournal.php +++ b/src/Caching/Storages/SQLiteJournal.php @@ -40,7 +40,6 @@ private function open(): void } $this->pdo = new \PDO('sqlite:' . $this->path); - $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $this->pdo->exec(' PRAGMA foreign_keys = OFF; PRAGMA journal_mode = WAL; diff --git a/src/Caching/Storages/SQLiteStorage.php b/src/Caching/Storages/SQLiteStorage.php index 5301a4e..016a4ab 100644 --- a/src/Caching/Storages/SQLiteStorage.php +++ b/src/Caching/Storages/SQLiteStorage.php @@ -28,7 +28,6 @@ public function __construct(string $path) } $this->pdo = new \PDO('sqlite:' . $path); - $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $this->pdo->exec(' PRAGMA foreign_keys = ON; CREATE TABLE IF NOT EXISTS cache ( From 0bb6cf4a060810e7f922d60f16f60a1c5e7843ae Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 16 Jan 2025 05:21:21 +0100 Subject: [PATCH 05/17] cs --- src/Caching/Storages/FileStorage.php | 2 +- tests/Caching/Cache.php | 1 - tests/bootstrap.php | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Caching/Storages/FileStorage.php b/src/Caching/Storages/FileStorage.php index f040013..d9cde69 100644 --- a/src/Caching/Storages/FileStorage.php +++ b/src/Caching/Storages/FileStorage.php @@ -32,7 +32,7 @@ class FileStorage implements Nette\Caching\Storage /** @internal cache file structure: meta-struct size + serialized meta-struct + data */ private const MetaHeaderLen = 6, - // meta structure: array of + // meta structure: array of MetaTime = 'time', // timestamp MetaSerialized = 'serialized', // is content serialized? MetaExpire = 'expire', // expiration timestamp diff --git a/tests/Caching/Cache.php b/tests/Caching/Cache.php index c5e1436..47750ec 100644 --- a/tests/Caching/Cache.php +++ b/tests/Caching/Cache.php @@ -75,7 +75,6 @@ public function bulkRead(array $keys): array public function bulkRemove(array $keys): void { - } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 9093ae2..02cd1ef 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -20,9 +20,9 @@ function getTempDir(): string { $dir = __DIR__ . '/tmp/' . getmypid(); - if (empty($GLOBALS['\\lock'])) { + if (empty($GLOBALS['\lock'])) { // garbage collector - $GLOBALS['\\lock'] = $lock = fopen(__DIR__ . '/lock', 'w'); + $GLOBALS['\lock'] = $lock = fopen(__DIR__ . '/lock', 'w'); if (rand(0, 100)) { flock($lock, LOCK_SH); @mkdir(dirname($dir)); From 6afed26776dfd608896a27849a16f88fc1c7cd64 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Jun 2025 18:56:02 +0200 Subject: [PATCH 06/17] optimized global function calls --- src/Bridges/CacheLatte/CacheMacro.php | 1 + src/Bridges/CacheLatte/Runtime.php | 1 + src/Caching/Cache.php | 1 + src/Caching/Storages/FileStorage.php | 2 ++ src/Caching/Storages/MemcachedStorage.php | 1 + src/Caching/Storages/SQLiteJournal.php | 1 + src/Caching/Storages/SQLiteStorage.php | 1 + 7 files changed, 8 insertions(+) diff --git a/src/Bridges/CacheLatte/CacheMacro.php b/src/Bridges/CacheLatte/CacheMacro.php index d6aeb5d..50e5f22 100644 --- a/src/Bridges/CacheLatte/CacheMacro.php +++ b/src/Bridges/CacheLatte/CacheMacro.php @@ -12,6 +12,7 @@ use Latte; use Nette; use Nette\Caching\Cache; +use function array_key_exists, count; /** diff --git a/src/Bridges/CacheLatte/Runtime.php b/src/Bridges/CacheLatte/Runtime.php index d054669..8b3c8cb 100644 --- a/src/Bridges/CacheLatte/Runtime.php +++ b/src/Bridges/CacheLatte/Runtime.php @@ -13,6 +13,7 @@ use Nette; use Nette\Caching\Cache; use Nette\Caching\OutputHelper; +use function array_intersect_key, array_key_exists, array_merge, array_pop, count, end, is_file, range; /** diff --git a/src/Caching/Cache.php b/src/Caching/Cache.php index 4f4df05..3b7161d 100644 --- a/src/Caching/Cache.php +++ b/src/Caching/Cache.php @@ -10,6 +10,7 @@ namespace Nette\Caching; use Nette; +use function array_keys, array_map, array_shift, array_slice, array_unique, array_values, constant, count, defined, filemtime, func_get_args, get_class, is_array, is_object, is_scalar, md5, serialize, substr, time; /** diff --git a/src/Caching/Storages/FileStorage.php b/src/Caching/Storages/FileStorage.php index d9cde69..19eff36 100644 --- a/src/Caching/Storages/FileStorage.php +++ b/src/Caching/Storages/FileStorage.php @@ -11,6 +11,8 @@ use Nette; use Nette\Caching\Cache; +use function dirname, fclose, filemtime, flock, fopen, fseek, ftruncate, fwrite, is_dir, is_string, microtime, mkdir, mt_getrandmax, mt_rand, rmdir, serialize, str_pad, str_repeat, stream_get_contents, strlen, strrpos, substr_replace, time, touch, unlink, unserialize, urlencode; +use const LOCK_EX, LOCK_SH, LOCK_UN, STR_PAD_LEFT; /** diff --git a/src/Caching/Storages/MemcachedStorage.php b/src/Caching/Storages/MemcachedStorage.php index 5176757..b9430f1 100644 --- a/src/Caching/Storages/MemcachedStorage.php +++ b/src/Caching/Storages/MemcachedStorage.php @@ -11,6 +11,7 @@ use Nette; use Nette\Caching\Cache; +use function array_combine, array_map, extension_loaded, time, urlencode; /** diff --git a/src/Caching/Storages/SQLiteJournal.php b/src/Caching/Storages/SQLiteJournal.php index cf96ca3..4de1912 100644 --- a/src/Caching/Storages/SQLiteJournal.php +++ b/src/Caching/Storages/SQLiteJournal.php @@ -11,6 +11,7 @@ use Nette; use Nette\Caching\Cache; +use function count, extension_loaded, implode, is_file, str_repeat, touch; /** diff --git a/src/Caching/Storages/SQLiteStorage.php b/src/Caching/Storages/SQLiteStorage.php index 016a4ab..576ceb4 100644 --- a/src/Caching/Storages/SQLiteStorage.php +++ b/src/Caching/Storages/SQLiteStorage.php @@ -11,6 +11,7 @@ use Nette; use Nette\Caching\Cache; +use function array_merge, count, is_file, serialize, str_repeat, time, touch, unserialize; /** From 8b705e781deab230dbe1c0c7e8fbbe20d5a85250 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 15:27:42 +0100 Subject: [PATCH 07/17] opened 4.0-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index efbb30b..10d0485 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } } } From af7687dfc769c0f59a2696ed529f5ebb93489f15 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 11 Jun 2024 14:35:09 +0200 Subject: [PATCH 08/17] requires PHP 8.1 --- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 6 +++--- composer.json | 2 +- readme.md | 2 +- tests/Storages/SQLiteJournal.permissions.phpt | 6 +++--- tests/Storages/SQLiteStorage.permissions.phpt | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 5a39324..8af71c6 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.1 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 15b406e..c01fd02 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.0', '8.1', '8.2', '8.3', '8.4'] + php: ['8.1', '8.2', '8.3', '8.4'] fail-fast: false @@ -37,7 +37,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.1 coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable @@ -52,7 +52,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.1 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/composer.json b/composer.json index 10d0485..cd76cd0 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "8.0 - 8.4", + "php": "8.1 - 8.4", "nette/utils": "^4.0" }, "require-dev": { diff --git a/readme.md b/readme.md index 77a0953..bb2ccd0 100644 --- a/readme.md +++ b/readme.md @@ -33,7 +33,7 @@ Installation composer require nette/caching ``` -It requires PHP version 8.0 and supports PHP up to 8.4. +It requires PHP version 8.1 and supports PHP up to 8.4. Basic Usage diff --git a/tests/Storages/SQLiteJournal.permissions.phpt b/tests/Storages/SQLiteJournal.permissions.phpt index 396f6eb..03f92c5 100644 --- a/tests/Storages/SQLiteJournal.permissions.phpt +++ b/tests/Storages/SQLiteJournal.permissions.phpt @@ -26,7 +26,7 @@ test('', function () { umask(0); (new SQLiteJournal($file))->write('foo', []); - Assert::same(0666, fileperms($file) & 0777); + Assert::same(0o666, fileperms($file) & 0o777); }); @@ -34,8 +34,8 @@ test('', function () { $file = getTempDir() . '/sqlitejournal.permissions.2.sqlite'; Assert::false(file_exists($file)); - umask(0077); + umask(0o077); (new SQLiteJournal($file))->write('foo', []); - Assert::same(0600, fileperms($file) & 0777); + Assert::same(0o600, fileperms($file) & 0o777); }); diff --git a/tests/Storages/SQLiteStorage.permissions.phpt b/tests/Storages/SQLiteStorage.permissions.phpt index 331b286..153ac19 100644 --- a/tests/Storages/SQLiteStorage.permissions.phpt +++ b/tests/Storages/SQLiteStorage.permissions.phpt @@ -26,7 +26,7 @@ test('', function () { umask(0); (new SQLiteStorage($file))->write('foo', 'bar', []); - Assert::same(0666, fileperms($file) & 0777); + Assert::same(0o666, fileperms($file) & 0o777); }); @@ -34,8 +34,8 @@ test('', function () { $file = getTempDir() . '/sqlitestorage.permissions.2.sqlite'; Assert::false(file_exists($file)); - umask(0077); + umask(0o077); (new SQLiteStorage($file))->write('foo', 'bar', []); - Assert::same(0600, fileperms($file) & 0777); + Assert::same(0o600, fileperms($file) & 0o777); }); From cf37311cf174eeb364785eaabcc515778c4f97ec Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 13 Jan 2023 04:41:43 +0100 Subject: [PATCH 09/17] Storage::read() added return typehint (BC break) --- src/Caching/Storage.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Caching/Storage.php b/src/Caching/Storage.php index e9565d8..16b8094 100644 --- a/src/Caching/Storage.php +++ b/src/Caching/Storage.php @@ -17,9 +17,8 @@ interface Storage { /** * Read from cache. - * @return mixed */ - function read(string $key); + function read(string $key): mixed; /** * Prevents item reading and writing. Lock is released by write() or remove(). From 7c37a975a0d1077c845fb08f446095ca33b101ac Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 13 Jan 2023 04:48:57 +0100 Subject: [PATCH 10/17] deprecated stuff --- src/Caching/Cache.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Caching/Cache.php b/src/Caching/Cache.php index 3b7161d..bcdb4d5 100644 --- a/src/Caching/Cache.php +++ b/src/Caching/Cache.php @@ -65,9 +65,7 @@ class Cache public const ALL = self::All; /** @internal */ - public const - NamespaceSeparator = "\x00", - NAMESPACE_SEPARATOR = self::NamespaceSeparator; + public const NamespaceSeparator = "\x00"; private Storage $storage; private string $namespace; @@ -386,6 +384,7 @@ public function capture(mixed $key): ?OutputHelper */ public function start($key): ?OutputHelper { + trigger_error(__METHOD__ . '() was renamed to capture()', E_USER_DEPRECATED); return $this->capture($key); } From 5bc3cad40c4cb2e2d7ff25a6a79f32bac7c6d5b1 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 14 Aug 2023 21:27:06 +0200 Subject: [PATCH 11/17] removed support for Latte 2 --- composer.json | 4 +- src/Bridges/CacheLatte/CacheMacro.php | 169 ------------------ tests/Bridges.Latte2/CacheMacro.cache.phpt | 48 ----- .../CacheMacro.createCache.phpt | 49 ----- .../expected/CacheMacro.cache.html | 8 - .../expected/CacheMacro.cache.inc.php | 12 -- .../expected/CacheMacro.cache.php | 28 --- tests/Bridges.Latte2/templates/cache.latte | 9 - .../templates/include.cache.latte | 5 - tests/Bridges.Latte3/Runtime.phpt | 4 - tests/Bridges.Latte3/{cache}.phpt | 4 - 11 files changed, 2 insertions(+), 338 deletions(-) delete mode 100644 src/Bridges/CacheLatte/CacheMacro.php delete mode 100644 tests/Bridges.Latte2/CacheMacro.cache.phpt delete mode 100644 tests/Bridges.Latte2/CacheMacro.createCache.phpt delete mode 100644 tests/Bridges.Latte2/expected/CacheMacro.cache.html delete mode 100644 tests/Bridges.Latte2/expected/CacheMacro.cache.inc.php delete mode 100644 tests/Bridges.Latte2/expected/CacheMacro.cache.php delete mode 100644 tests/Bridges.Latte2/templates/cache.latte delete mode 100644 tests/Bridges.Latte2/templates/include.cache.latte diff --git a/composer.json b/composer.json index cd76cd0..890a359 100644 --- a/composer.json +++ b/composer.json @@ -21,13 +21,13 @@ "require-dev": { "nette/tester": "^2.4", "nette/di": "^3.1 || ^4.0", - "latte/latte": "^2.11 || ^3.0.12", + "latte/latte": "^3.0.12", "tracy/tracy": "^2.9", "psr/simple-cache": "^2.0 || ^3.0", "phpstan/phpstan-nette": "^2.0@stable" }, "conflict": { - "latte/latte": ">=3.0.0 <3.0.12" + "latte/latte": "<3.0.12" }, "suggest": { "ext-pdo_sqlite": "to use SQLiteStorage or SQLiteJournal" diff --git a/src/Bridges/CacheLatte/CacheMacro.php b/src/Bridges/CacheLatte/CacheMacro.php deleted file mode 100644 index 50e5f22..0000000 --- a/src/Bridges/CacheLatte/CacheMacro.php +++ /dev/null @@ -1,169 +0,0 @@ -used = false; - } - - - /** - * Finishes template parsing. - * @return array(prolog, epilog) - */ - public function finalize() - { - if ($this->used) { - return ['Nette\Bridges\CacheLatte\CacheMacro::initRuntime($this);']; - } - } - - - /** - * New node is found. - * @return bool - */ - public function nodeOpened(Latte\MacroNode $node) - { - if ($node->modifiers) { - throw new Latte\CompileException('Modifiers are not allowed in ' . $node->getNotation()); - } - - $this->used = true; - $node->empty = false; - $node->openingCode = Latte\PhpWriter::using($node) - ->write( - 'global->cacheStorage, %var, $this->global->cacheStack, %node.array?)) /* line %var */ try { ?>', - Nette\Utils\Random::generate(), - $node->startLine, - ); - } - - - /** - * Node is closed. - * @return void - */ - public function nodeClosed(Latte\MacroNode $node) - { - $node->closingCode = Latte\PhpWriter::using($node) - ->write( - 'global->cacheStack, %node.array?) /* line %var */; - } catch (\Throwable $ʟ_e) { - Nette\Bridges\CacheLatte\CacheMacro::rollback($this->global->cacheStack); throw $ʟ_e; - } ?>', - $node->startLine, - ); - } - - - /********************* run-time helpers ****************d*g**/ - - - public static function initRuntime(Latte\Runtime\Template $template): void - { - if (!empty($template->global->cacheStack)) { - $file = (new \ReflectionClass($template))->getFileName(); - if (@is_file($file)) { // @ - may trigger error - end($template->global->cacheStack)->dependencies[Cache::Files][] = $file; - } - } - } - - - /** - * Starts the output cache. Returns Nette\Caching\OutputHelper object if buffering was started. - */ - public static function createCache( - Nette\Caching\Storage $cacheStorage, - string $key, - ?array &$parents, - ?array $args = null, - ): Nette\Caching\OutputHelper|\stdClass|null - { - if ($args) { - if (array_key_exists('if', $args) && !$args['if']) { - return $parents[] = new \stdClass; - } - - $key = array_merge([$key], array_intersect_key($args, range(0, count($args)))); - } - - if ($parents) { - end($parents)->dependencies[Cache::Items][] = $key; - } - - $cache = new Cache($cacheStorage, 'Nette.Templating.Cache'); - if ($helper = $cache->capture($key)) { - $parents[] = $helper; - } - - return $helper; - } - - - /** - * Ends the output cache. - * @param Nette\Caching\OutputHelper[] $parents - */ - public static function endCache(array &$parents, ?array $args = null): void - { - $helper = array_pop($parents); - if (!$helper instanceof Nette\Caching\OutputHelper) { - return; - } - - if (isset($args['dependencies'])) { - $args += $args['dependencies'](); - } - - if (isset($args['expire'])) { - $args['expiration'] = $args['expire']; // back compatibility - } - - $helper->dependencies[Cache::Tags] = $args['tags'] ?? null; - $helper->dependencies[Cache::Expire] = $args['expiration'] ?? '+ 7 days'; - $helper->end(); - } - - - /** - * @param Nette\Caching\OutputHelper[] $parents - */ - public static function rollback(array &$parents): void - { - $helper = array_pop($parents); - if ($helper instanceof Nette\Caching\OutputHelper) { - $helper->rollback(); - } - } -} diff --git a/tests/Bridges.Latte2/CacheMacro.cache.phpt b/tests/Bridges.Latte2/CacheMacro.cache.phpt deleted file mode 100644 index 8dfa099..0000000 --- a/tests/Bridges.Latte2/CacheMacro.cache.phpt +++ /dev/null @@ -1,48 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -$latte = new Latte\Engine; -$latte->setTempDirectory(getTempDir()); -$latte->addMacro('cache', new CacheMacro($latte->getCompiler())); -$latte->addProvider('cacheStorage', new Nette\Caching\Storages\MemoryStorage); - -$params['title'] = 'Hello'; -$params['id'] = 456; - -Assert::matchFile( - __DIR__ . '/expected/CacheMacro.cache.php', - $latte->compile(__DIR__ . '/templates/cache.latte'), -); -Assert::matchFile( - __DIR__ . '/expected/CacheMacro.cache.html', - $latte->renderToString( - __DIR__ . '/templates/cache.latte', - $params, - ), -); -Assert::matchFile( - __DIR__ . '/expected/CacheMacro.cache.html', - $latte->renderToString( - __DIR__ . '/templates/cache.latte', - $params, - ), -); -Assert::matchFile( - __DIR__ . '/expected/CacheMacro.cache.inc.php', - file_get_contents($latte->getCacheFile(__DIR__ . strtr('/templates/include.cache.latte', '/', DIRECTORY_SEPARATOR))), -); diff --git a/tests/Bridges.Latte2/CacheMacro.createCache.phpt b/tests/Bridges.Latte2/CacheMacro.createCache.phpt deleted file mode 100644 index 5cd2608..0000000 --- a/tests/Bridges.Latte2/CacheMacro.createCache.phpt +++ /dev/null @@ -1,49 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -test('', function () { - $parents = []; - $dp = [Cache::Tags => ['rum', 'cola']]; - $outputHelper = CacheMacro::createCache(new DevNullStorage, 'test', $parents); - Assert::type(Nette\Caching\OutputHelper::class, $outputHelper); - CacheMacro::endCache($parents, $dp); - Assert::same($dp + [Cache::Expire => '+ 7 days'], $outputHelper->dependencies); -}); - -test('', function () { - $parents = []; - $dp = [Cache::Tags => ['rum', 'cola']]; - $dpFallback = fn() => $dp; - $outputHelper = CacheMacro::createCache(new DevNullStorage, 'test', $parents); - CacheMacro::endCache($parents, ['dependencies' => $dpFallback]); - Assert::same($dp + [Cache::Expire => '+ 7 days'], $outputHelper->dependencies); -}); - -test('', function () { - $parents = []; - $dp = [ - Cache::Tags => ['rum', 'cola'], - Cache::Expire => '+ 1 days', - ]; - $dpFallback = fn() => $dp; - $outputHelper = CacheMacro::createCache(new DevNullStorage, 'test', $parents); - CacheMacro::endCache($parents, ['dependencies' => $dpFallback]); - Assert::same($dp, $outputHelper->dependencies); -}); diff --git a/tests/Bridges.Latte2/expected/CacheMacro.cache.html b/tests/Bridges.Latte2/expected/CacheMacro.cache.html deleted file mode 100644 index 0fe8eeb..0000000 --- a/tests/Bridges.Latte2/expected/CacheMacro.cache.html +++ /dev/null @@ -1,8 +0,0 @@ -Noncached content - - -

HELLO

- -

Included file (11)

- - hello diff --git a/tests/Bridges.Latte2/expected/CacheMacro.cache.inc.php b/tests/Bridges.Latte2/expected/CacheMacro.cache.inc.php deleted file mode 100644 index af95b58..0000000 --- a/tests/Bridges.Latte2/expected/CacheMacro.cache.inc.php +++ /dev/null @@ -1,12 +0,0 @@ -global->cacheStorage, '%[\w]+%', $this->global->cacheStack)) /* line %d% */ try { - echo ' '; - echo LR\Filters::escapeHtmlText(($this->filters->lower)($title)) /* line %d% */; - echo "\n"; - Nette\Bridges\CacheLatte\CacheMacro::endCache($this->global->cacheStack) /* line %d% */; - } catch (\Throwable $ʟ_e) { - Nette\Bridges\CacheLatte\CacheMacro::rollback($this->global->cacheStack); - throw $ʟ_e; - } -%A% diff --git a/tests/Bridges.Latte2/expected/CacheMacro.cache.php b/tests/Bridges.Latte2/expected/CacheMacro.cache.php deleted file mode 100644 index 491297e..0000000 --- a/tests/Bridges.Latte2/expected/CacheMacro.cache.php +++ /dev/null @@ -1,28 +0,0 @@ -global->cacheStorage, '%[\w]+%', $this->global->cacheStack, [$id, 'tags' => 'mytag'])) /* line %d% */ try { - echo ' -

'; - echo LR\Filters::escapeHtmlText(($this->filters->upper)($title)) /* line %d% */; - echo '

- -'; - $this->createTemplate('include.cache.latte', ['localvar' => 11] + $this->params, 'include')->renderToContentType('html') /* line %d% */; - echo "\n"; - Nette\Bridges\CacheLatte\CacheMacro::endCache($this->global->cacheStack, [$id, 'tags' => 'mytag']) /* line %d% */; - } catch (\Throwable $ʟ_e) { - Nette\Bridges\CacheLatte\CacheMacro::rollback($this->global->cacheStack); - throw $ʟ_e; - } -%A% - } - - - public function prepare(): void - { -%A% - Nette\Bridges\CacheLatte\CacheMacro::initRuntime($this); -%A% diff --git a/tests/Bridges.Latte2/templates/cache.latte b/tests/Bridges.Latte2/templates/cache.latte deleted file mode 100644 index 5155ee7..0000000 --- a/tests/Bridges.Latte2/templates/cache.latte +++ /dev/null @@ -1,9 +0,0 @@ -Noncached content - -{cache $id, tags => 'mytag'} - -

{$title|upper}

- -{include 'include.cache.latte', 'localvar' => 11} - -{/cache} diff --git a/tests/Bridges.Latte2/templates/include.cache.latte b/tests/Bridges.Latte2/templates/include.cache.latte deleted file mode 100644 index 595329b..0000000 --- a/tests/Bridges.Latte2/templates/include.cache.latte +++ /dev/null @@ -1,5 +0,0 @@ -

Included file ({$localvar})

- -{cache} - {$title|lower} -{/cache} diff --git a/tests/Bridges.Latte3/Runtime.phpt b/tests/Bridges.Latte3/Runtime.phpt index 83a5121..2360125 100644 --- a/tests/Bridges.Latte3/Runtime.phpt +++ b/tests/Bridges.Latte3/Runtime.phpt @@ -9,10 +9,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - test('', function () { $runtime = new Runtime(new DevNullStorage); diff --git a/tests/Bridges.Latte3/{cache}.phpt b/tests/Bridges.Latte3/{cache}.phpt index e275b25..cb5ea87 100644 --- a/tests/Bridges.Latte3/{cache}.phpt +++ b/tests/Bridges.Latte3/{cache}.phpt @@ -11,10 +11,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - $latte = new Latte\Engine; $latte->setTempDirectory(getTempDir()); From fcdb685d7849545f1c324b653c535d8182476530 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 27 Aug 2023 13:41:56 +0200 Subject: [PATCH 12/17] removed legacy services names --- src/Bridges/CacheDI/CacheExtension.php | 8 -------- tests/Bridges.DI/CacheExtension.phpt | 4 ---- 2 files changed, 12 deletions(-) diff --git a/src/Bridges/CacheDI/CacheExtension.php b/src/Bridges/CacheDI/CacheExtension.php index da6b1f8..5d1ff9d 100644 --- a/src/Bridges/CacheDI/CacheExtension.php +++ b/src/Bridges/CacheDI/CacheExtension.php @@ -45,13 +45,5 @@ public function loadConfiguration(): void $builder->addDefinition($this->prefix('storage')) ->setType(Nette\Caching\Storage::class) ->setFactory(Nette\Caching\Storages\FileStorage::class, [$this->tempDir]); - - if ($this->name === 'cache') { - if (extension_loaded('pdo_sqlite')) { - $builder->addAlias('nette.cacheJournal', $this->prefix('journal')); - } - - $builder->addAlias('cacheStorage', $this->prefix('storage')); - } } } diff --git a/tests/Bridges.DI/CacheExtension.phpt b/tests/Bridges.DI/CacheExtension.phpt index cc73573..9f4c470 100644 --- a/tests/Bridges.DI/CacheExtension.phpt +++ b/tests/Bridges.DI/CacheExtension.phpt @@ -28,8 +28,4 @@ test('', function () { $storage = $container->getService('cache.storage'); Assert::type(Nette\Caching\Storages\FileStorage::class, $storage); - - // aliases - Assert::same($journal, $container->getService('nette.cacheJournal')); - Assert::same($storage, $container->getService('cacheStorage')); }); From 91c80616e0c1c8fe4a474cdacdeaec152ae1d614 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 11 Jun 2024 14:34:18 +0200 Subject: [PATCH 13/17] Cache: md5 replaced with xxHash --- src/Caching/Cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Caching/Cache.php b/src/Caching/Cache.php index bcdb4d5..e7f42d3 100644 --- a/src/Caching/Cache.php +++ b/src/Caching/Cache.php @@ -394,7 +394,7 @@ public function start($key): ?OutputHelper */ protected function generateKey($key): string { - return $this->namespace . md5(is_scalar($key) ? (string) $key : serialize($key)); + return $this->namespace . hash('xxh128', is_scalar($key) ? (string) $key : serialize($key)); } From cde97c379adc3354ea9fbf66988e9ddf4592741b Mon Sep 17 00:00:00 2001 From: luke Date: Sun, 22 Jun 2025 14:46:09 +1200 Subject: [PATCH 14/17] Fix: Added probabilistic cleaning to SQLiteStorage method - addresses Issue #83 --- src/Caching/Storages/SQLiteStorage.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Caching/Storages/SQLiteStorage.php b/src/Caching/Storages/SQLiteStorage.php index 576ceb4..35f0113 100644 --- a/src/Caching/Storages/SQLiteStorage.php +++ b/src/Caching/Storages/SQLiteStorage.php @@ -21,6 +21,8 @@ class SQLiteStorage implements Nette\Caching\Storage, Nette\Caching\BulkReader { private \PDO $pdo; + /** probability that the clean() routine is started */ + public static float $gcProbability = 0.001; public function __construct(string $path) { @@ -46,6 +48,12 @@ public function __construct(string $path) CREATE INDEX IF NOT EXISTS tags_tag ON tags(tag); PRAGMA synchronous = OFF; '); + + // should we run the clean function to remove expired cached items? + if (mt_rand() / mt_getrandmax() < static::$gcProbability) { + $this->clean([]); + } + } From 52db3b42ad8b8d073b170c5b6aaae41c19ff8c33 Mon Sep 17 00:00:00 2001 From: luke Date: Sun, 22 Jun 2025 17:16:41 +1200 Subject: [PATCH 15/17] Add a test for SQLiteStorage probabilistic cleaning --- tests/Storages/SQLiteStorage.clean.phpt | 79 +++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tests/Storages/SQLiteStorage.clean.phpt diff --git a/tests/Storages/SQLiteStorage.clean.phpt b/tests/Storages/SQLiteStorage.clean.phpt new file mode 100644 index 0000000..ff6006e --- /dev/null +++ b/tests/Storages/SQLiteStorage.clean.phpt @@ -0,0 +1,79 @@ +save($key, 'rulez', [ + Cache::Expire => time() + 1 +]); + +// there should be one entry in the cache +Assert::same(1, countStorageEntries($storage), 'Test cache entry should be saved'); + +// wait until the item expires +sleep(2); + +// expired item should still be present in the database +Assert::same(1, countStorageEntries($storage), 'Expired item should still be in DB prior to cleanup'); + +// PHASE 2 + +// Now we will reload the storage object on the same DB file, with garbage collection probability set to 1 (100%) +SQLiteStorage::$gcProbability = 1; + +// The storage constructor should run the clean() function. +$fresh_storage = new SQLiteStorage($db_file); + +// cache DB should now be empty (zero rows) +Assert::same(0, countStorageEntries($fresh_storage), 'Expired item should be cleaned up'); + +// ok, all done + +// clean up our test file +if (file_exists($db_file) && !unlink($db_file)) { + trigger_error("Failed to clean up test database: $db_file", E_USER_WARNING); +} + + +function countStorageEntries(SQLiteStorage $storage): int { + + // because we are checking EXPIRED cache entries, we need to access the SQLite file directly, rather than use the standard SqliteStorage methods + try { + + // we use Reflection to access the private 'pdo' property of the SQLiteStorage object + $reflection = new ReflectionProperty(SQLiteStorage::class, 'pdo'); + $reflection->setAccessible(true); + $pdo = $reflection->getValue($storage); + + // Now we use the storage object's own PDO connection to count the number of rows in the cache + $stmt = $pdo->prepare('SELECT COUNT(*) FROM cache'); + $stmt->execute(); + return (int) $stmt->fetchColumn(); + } catch (ReflectionException $e) { + Assert::fail('Unable to access PDO property: ' . $e->getMessage()); + } + +} \ No newline at end of file From 74975cdf55c32aa5a530f132b7e86a1396f0872b Mon Sep 17 00:00:00 2001 From: luke Date: Sun, 22 Jun 2025 17:26:23 +1200 Subject: [PATCH 16/17] Adjust tests for SQLiteStorage probabilistic cleaning --- tests/Storages/SQLiteStorage.clean.phpt | 140 +++++++++++++++--------- 1 file changed, 90 insertions(+), 50 deletions(-) diff --git a/tests/Storages/SQLiteStorage.clean.phpt b/tests/Storages/SQLiteStorage.clean.phpt index ff6006e..8f1765c 100644 --- a/tests/Storages/SQLiteStorage.clean.phpt +++ b/tests/Storages/SQLiteStorage.clean.phpt @@ -2,6 +2,8 @@ /** * Test: Nette\Caching\Storages\SQLiteStorage probabilistic cleaning + * @phpExtension pdo + * @phpExtension pdo_sqlite */ declare(strict_types=1); @@ -9,71 +11,109 @@ declare(strict_types=1); use Nette\Caching\Cache; use Nette\Caching\Storages\SQLiteStorage; use Tester\Assert; -use Tester\Dumper; require __DIR__ . '/../bootstrap.php'; - -// first we'll set up a test file, storage and cache -$db_file = getTempDir() . '/sqlite_clean_test.db'; - -// we'll start with garbage collection probability set to 0 -SQLiteStorage::$gcProbability = 0; -$storage = new SQLiteStorage($db_file); - -$cache = new Cache($storage); - $key = 'nette'; +$value = 'is the best'; -// We'll write an entry to cache which expires after 1 second -$cache->save($key, 'rulez', [ - Cache::Expire => time() + 1 -]); +// Test 1: With gcProbability = 0, expired items should remain +SQLiteStorage::$gcProbability = 0.0; +$storage1 = new SQLiteStorage(':memory:'); +$cache1 = new Cache($storage1); -// there should be one entry in the cache -Assert::same(1, countStorageEntries($storage), 'Test cache entry should be saved'); +// Save item that expires in 1 second +$cache1->save($key, $value, [ + Cache::Expire => time() + 1, +]); -// wait until the item expires +// Wait for expiration sleep(2); +clearstatcache(); -// expired item should still be present in the database -Assert::same(1, countStorageEntries($storage), 'Expired item should still be in DB prior to cleanup'); - -// PHASE 2 +// With gcProbability = 0, the expired item should still be in DB +// We need to check the storage directly since Cache::load() respects expiration +$reflection = new ReflectionClass($storage1); +$pdoProperty = $reflection->getProperty('pdo'); +$pdoProperty->setAccessible(true); +$pdo = $pdoProperty->getValue($storage1); -// Now we will reload the storage object on the same DB file, with garbage collection probability set to 1 (100%) -SQLiteStorage::$gcProbability = 1; +$stmt = $pdo->prepare('SELECT COUNT(*) FROM cache WHERE key = ?'); +$stmt->execute([$key]); +$count = $stmt->fetchColumn(); -// The storage constructor should run the clean() function. -$fresh_storage = new SQLiteStorage($db_file); +Assert::same(1, (int)$count, 'Expired item should still exist in DB when gcProbability = 0'); -// cache DB should now be empty (zero rows) -Assert::same(0, countStorageEntries($fresh_storage), 'Expired item should be cleaned up'); +// Test 2: With gcProbability = 1.0, expired items should be cleaned +SQLiteStorage::$gcProbability = 1.0; +$storage2 = new SQLiteStorage(':memory:'); +$cache2 = new Cache($storage2); -// ok, all done +// Save item that expires immediately +$cache2->save($key, $value, [ + Cache::Expire => time() - 1, // Already expired +]); -// clean up our test file -if (file_exists($db_file) && !unlink($db_file)) { - trigger_error("Failed to clean up test database: $db_file", E_USER_WARNING); -} +// Create another storage instance - this should trigger cleaning +$storage3 = new SQLiteStorage(':memory:'); +// But we need the same database... let's use a different approach + +// Alternative approach: Test that clean() is called by checking the database +SQLiteStorage::$gcProbability = 1.0; +$tempFile = tempnam(sys_get_temp_dir(), 'nette_cache_test'); +$storage4 = new SQLiteStorage($tempFile); +$cache4 = new Cache($storage4); + +// Add expired item directly to database +$reflection4 = new ReflectionClass($storage4); +$pdoProperty4 = $reflection4->getProperty('pdo'); +$pdoProperty4->setAccessible(true); +$pdo4 = $pdoProperty4->getValue($storage4); + +$pdo4->prepare('INSERT INTO cache (key, data, expire) VALUES (?, ?, ?)') + ->execute([$key, serialize($value), time() - 100]); // Expired 100 seconds ago + +// Verify item exists +$stmt = $pdo4->prepare('SELECT COUNT(*) FROM cache WHERE key = ?'); +$stmt->execute([$key]); +$countBefore = $stmt->fetchColumn(); +Assert::same(1, (int)$countBefore, 'Expired item should exist before cleaning'); + +// Create new storage instance with 100% probability - should clean +$storage5 = new SQLiteStorage($tempFile); + +// Check that expired item was cleaned +$reflection5 = new ReflectionClass($storage5); +$pdoProperty5 = $reflection5->getProperty('pdo'); +$pdoProperty5->setAccessible(true); +$pdo5 = $pdoProperty5->getValue($storage5); + +$stmt = $pdo5->prepare('SELECT COUNT(*) FROM cache WHERE key = ?'); +$stmt->execute([$key]); +$countAfter = $stmt->fetchColumn(); +Assert::same(0, (int)$countAfter, 'Expired item should be cleaned when gcProbability = 1.0'); + +// Test 3: Test that non-expired items are not cleaned +SQLiteStorage::$gcProbability = 1.0; +$tempFile2 = tempnam(sys_get_temp_dir(), 'nette_cache_test2'); +$storage6 = new SQLiteStorage($tempFile2); +$cache6 = new Cache($storage6); + +// Save non-expired item +$cache6->save($key, $value, [ + Cache::Expire => time() + 3600, // Expires in 1 hour +]); +// Create new storage instance - should trigger cleaning but not remove non-expired items +$storage7 = new SQLiteStorage($tempFile2); +$cache7 = new Cache($storage7); -function countStorageEntries(SQLiteStorage $storage): int { +// Non-expired item should still be loadable +Assert::same($value, $cache7->load($key), 'Non-expired item should remain after cleaning'); - // because we are checking EXPIRED cache entries, we need to access the SQLite file directly, rather than use the standard SqliteStorage methods - try { - - // we use Reflection to access the private 'pdo' property of the SQLiteStorage object - $reflection = new ReflectionProperty(SQLiteStorage::class, 'pdo'); - $reflection->setAccessible(true); - $pdo = $reflection->getValue($storage); - - // Now we use the storage object's own PDO connection to count the number of rows in the cache - $stmt = $pdo->prepare('SELECT COUNT(*) FROM cache'); - $stmt->execute(); - return (int) $stmt->fetchColumn(); - } catch (ReflectionException $e) { - Assert::fail('Unable to access PDO property: ' . $e->getMessage()); - } +// Cleanup +unlink($tempFile); +unlink($tempFile2); -} \ No newline at end of file +// Reset gcProbability +SQLiteStorage::$gcProbability = 0.001; \ No newline at end of file From 9c36a26caa925a252044cb2e829a9c32e8fbf9a8 Mon Sep 17 00:00:00 2001 From: luke Date: Sun, 22 Jun 2025 17:27:24 +1200 Subject: [PATCH 17/17] Adjust tests for SQLiteStorage probabilistic cleaning --- tests/Storages/SQLiteStorage.clean.phpt | 138 +++++++++--------------- 1 file changed, 50 insertions(+), 88 deletions(-) diff --git a/tests/Storages/SQLiteStorage.clean.phpt b/tests/Storages/SQLiteStorage.clean.phpt index 8f1765c..d0a440d 100644 --- a/tests/Storages/SQLiteStorage.clean.phpt +++ b/tests/Storages/SQLiteStorage.clean.phpt @@ -11,109 +11,71 @@ declare(strict_types=1); use Nette\Caching\Cache; use Nette\Caching\Storages\SQLiteStorage; use Tester\Assert; +use Tester\Dumper; require __DIR__ . '/../bootstrap.php'; -$key = 'nette'; -$value = 'is the best'; -// Test 1: With gcProbability = 0, expired items should remain -SQLiteStorage::$gcProbability = 0.0; -$storage1 = new SQLiteStorage(':memory:'); -$cache1 = new Cache($storage1); +// first we'll set up a test file, storage and cache +$db_file = getTempDir() . '/sqlite_clean_test.db'; + +// we'll start with garbage collection probability set to 0 +SQLiteStorage::$gcProbability = 0; +$storage = new SQLiteStorage($db_file); + +$cache = new Cache($storage); + +$key = 'nette'; -// Save item that expires in 1 second -$cache1->save($key, $value, [ - Cache::Expire => time() + 1, +// We'll write an entry to cache which expires after 1 second +$cache->save($key, 'rulez', [ + Cache::Expire => time() + 1 ]); -// Wait for expiration +// there should be one entry in the cache +Assert::same(1, countStorageEntries($storage), 'Test cache entry should be saved'); + +// wait until the item expires sleep(2); -clearstatcache(); -// With gcProbability = 0, the expired item should still be in DB -// We need to check the storage directly since Cache::load() respects expiration -$reflection = new ReflectionClass($storage1); -$pdoProperty = $reflection->getProperty('pdo'); -$pdoProperty->setAccessible(true); -$pdo = $pdoProperty->getValue($storage1); +// expired item should still be present in the database +Assert::same(1, countStorageEntries($storage), 'Expired item should still be in DB prior to cleanup'); -$stmt = $pdo->prepare('SELECT COUNT(*) FROM cache WHERE key = ?'); -$stmt->execute([$key]); -$count = $stmt->fetchColumn(); +// PHASE 2 -Assert::same(1, (int)$count, 'Expired item should still exist in DB when gcProbability = 0'); +// Now we will reload the storage object on the same DB file, with garbage collection probability set to 1 (100%) +SQLiteStorage::$gcProbability = 1; -// Test 2: With gcProbability = 1.0, expired items should be cleaned -SQLiteStorage::$gcProbability = 1.0; -$storage2 = new SQLiteStorage(':memory:'); -$cache2 = new Cache($storage2); +// The storage constructor should run the clean() function. +$fresh_storage = new SQLiteStorage($db_file); -// Save item that expires immediately -$cache2->save($key, $value, [ - Cache::Expire => time() - 1, // Already expired -]); +// cache DB should now be empty (zero rows) +Assert::same(0, countStorageEntries($fresh_storage), 'Expired item should be cleaned up'); -// Create another storage instance - this should trigger cleaning -$storage3 = new SQLiteStorage(':memory:'); -// But we need the same database... let's use a different approach - -// Alternative approach: Test that clean() is called by checking the database -SQLiteStorage::$gcProbability = 1.0; -$tempFile = tempnam(sys_get_temp_dir(), 'nette_cache_test'); -$storage4 = new SQLiteStorage($tempFile); -$cache4 = new Cache($storage4); - -// Add expired item directly to database -$reflection4 = new ReflectionClass($storage4); -$pdoProperty4 = $reflection4->getProperty('pdo'); -$pdoProperty4->setAccessible(true); -$pdo4 = $pdoProperty4->getValue($storage4); - -$pdo4->prepare('INSERT INTO cache (key, data, expire) VALUES (?, ?, ?)') - ->execute([$key, serialize($value), time() - 100]); // Expired 100 seconds ago - -// Verify item exists -$stmt = $pdo4->prepare('SELECT COUNT(*) FROM cache WHERE key = ?'); -$stmt->execute([$key]); -$countBefore = $stmt->fetchColumn(); -Assert::same(1, (int)$countBefore, 'Expired item should exist before cleaning'); - -// Create new storage instance with 100% probability - should clean -$storage5 = new SQLiteStorage($tempFile); - -// Check that expired item was cleaned -$reflection5 = new ReflectionClass($storage5); -$pdoProperty5 = $reflection5->getProperty('pdo'); -$pdoProperty5->setAccessible(true); -$pdo5 = $pdoProperty5->getValue($storage5); - -$stmt = $pdo5->prepare('SELECT COUNT(*) FROM cache WHERE key = ?'); -$stmt->execute([$key]); -$countAfter = $stmt->fetchColumn(); -Assert::same(0, (int)$countAfter, 'Expired item should be cleaned when gcProbability = 1.0'); - -// Test 3: Test that non-expired items are not cleaned -SQLiteStorage::$gcProbability = 1.0; -$tempFile2 = tempnam(sys_get_temp_dir(), 'nette_cache_test2'); -$storage6 = new SQLiteStorage($tempFile2); -$cache6 = new Cache($storage6); - -// Save non-expired item -$cache6->save($key, $value, [ - Cache::Expire => time() + 3600, // Expires in 1 hour -]); +// ok, all done + +// clean up our test file +if (file_exists($db_file) && !unlink($db_file)) { + trigger_error("Failed to clean up test database: $db_file", E_USER_WARNING); +} -// Create new storage instance - should trigger cleaning but not remove non-expired items -$storage7 = new SQLiteStorage($tempFile2); -$cache7 = new Cache($storage7); -// Non-expired item should still be loadable -Assert::same($value, $cache7->load($key), 'Non-expired item should remain after cleaning'); +function countStorageEntries(SQLiteStorage $storage): int { -// Cleanup -unlink($tempFile); -unlink($tempFile2); + // because we are checking EXPIRED cache entries, we need to access the SQLite file directly, rather than use the standard SqliteStorage methods + try { + + // we use Reflection to access the private 'pdo' property of the SQLiteStorage object + $reflection = new ReflectionProperty(SQLiteStorage::class, 'pdo'); + $reflection->setAccessible(true); + $pdo = $reflection->getValue($storage); + + // Now we use the storage object's own PDO connection to count the number of rows in the cache + $stmt = $pdo->prepare('SELECT COUNT(*) FROM cache'); + $stmt->execute(); + return (int) $stmt->fetchColumn(); + } catch (ReflectionException $e) { + Assert::fail('Unable to access PDO property: ' . $e->getMessage()); + } -// Reset gcProbability -SQLiteStorage::$gcProbability = 0.001; \ No newline at end of file +} \ No newline at end of file