├── src ├── Config │ ├── .gitignore │ └── Option │ │ ├── .gitignore │ │ └── Builder │ │ └── OptionBuilder.php ├── Enums │ ├── CacheStoreType.php │ ├── CacheTimeConstants.php │ └── DatabaseDriver.php ├── Helpers │ ├── EnvHelper.php │ ├── FlushHelper.php │ ├── CacheDatabaseHelper.php │ ├── CacheRedisHelper.php │ ├── SqliteHelper.php │ ├── CacheFileHelper.php │ ├── CacheConfig.php │ └── CacheerHelper.php ├── CacheStore │ ├── Support │ │ ├── FileCacheBatchProcessor.php │ │ └── FileCachePathBuilder.php │ ├── CacheManager │ │ ├── RedisCacheManager.php │ │ ├── GenericFlusher.php │ │ ├── FileCacheFlusher.php │ │ ├── OptionBuilders │ │ │ ├── RedisOptionBuilder.php │ │ │ ├── DatabaseOptionBuilder.php │ │ │ └── FileOptionBuilder.php │ │ └── FileCacheManager.php │ └── ArrayCacheStore.php ├── Utils │ ├── CacheDataFormatter.php │ ├── CacheLogger.php │ └── CacheDriver.php ├── Exceptions │ ├── CacheDatabaseException.php │ ├── CacheFileException.php │ ├── ConnectionException.php │ ├── CacheRedisException.php │ └── BaseException.php ├── Core │ ├── ConnectionFactory.php │ ├── Connect.php │ └── MigrationManager.php ├── Support │ └── TimeBuilder.php ├── Boot │ └── Configs.php ├── Interface │ └── CacheerInterface.php ├── Service │ ├── CacheRetriever.php │ └── CacheMutator.php ├── Repositories │ └── CacheDatabaseRepository.php └── Cacheer.php ├── art └── cacheer_php_logo__.png ├── .env.example ├── phpunit.xml ├── .gitignore ├── Examples ├── example03.php ├── example01.php ├── example02.php ├── example04.php ├── example05.php ├── example08.php ├── example06.php └── example07.php ├── .scrutinizer.yml ├── tests ├── Unit │ ├── BooleanReturnTest.php │ ├── StaticAccessTest.php │ ├── RedisOptionBuilderStoreTest.php │ ├── SecurityFeatureTest.php │ ├── MigrationManagerDynamicTableTest.php │ ├── RedisOptionBuilderTTLAndFlushTest.php │ ├── DatabaseOptionBuilderStoreTest.php │ ├── DatabaseOptionBuilderTTLAndFlushTest.php │ ├── FileCacheStoreTest.php │ ├── ArrayCacheStoreTest.php │ └── DatabaseCacheStoreTest.php └── Feature │ ├── RedisOptionBuilderFeatureTest.php │ ├── DatabaseOptionBuilderFeatureTest.php │ ├── OptionBuildTest.php │ └── FileCacheStoreFeatureTest.php ├── composer.json └── README.md /src/Config/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Config/Option/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /art/cacheer_php_logo__.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silviooosilva/CacheerPHP/HEAD/art/cacheer_php_logo__.png -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DB_CONNECTION=sqlite 2 | # DB_HOST=localhost 3 | # DB_PORT=3306 4 | # DB_DATABASE=cacheer_db 5 | # DB_USERNAME=root 6 | # DB_PASSWORD= 7 | 8 | REDIS_CLIENT= 9 | REDIS_HOST=localhost 10 | REDIS_PASSWORD= 11 | REDIS_PORT=6379 12 | REDIS_NAMESPACE= -------------------------------------------------------------------------------- /src/Enums/CacheStoreType.php: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | tests 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Enums/CacheTimeConstants.php: -------------------------------------------------------------------------------- 1 | __DIR__ . "/cache", 9 | ]; 10 | 11 | $Cacheer = new Cacheer($options); 12 | 13 | // Chave do cache a ser limpo 14 | $cacheKey = 'user_profile_123'; 15 | 16 | // Limpando um item específico do cache 17 | 18 | $Cacheer->clearCache($cacheKey); 19 | 20 | if ($Cacheer->isSuccess()) { 21 | echo $Cacheer->getMessage(); 22 | } else { 23 | echo $Cacheer->getMessage(); 24 | } 25 | 26 | $Cacheer->flushCache(); 27 | 28 | if ($Cacheer->isSuccess()) { 29 | echo $Cacheer->getMessage(); 30 | } else { 31 | echo $Cacheer->getMessage(); 32 | } 33 | -------------------------------------------------------------------------------- /Examples/example01.php: -------------------------------------------------------------------------------- 1 | __DIR__ . "/cache", 9 | ]; 10 | 11 | $Cacheer = new Cacheer($options); 12 | 13 | // Dados a serem armazenados no cache 14 | $cacheKey = 'user_profile_1234'; 15 | $userProfile = [ 16 | 'id' => 123, 17 | 'name' => 'John Doe', 18 | 'email' => 'john.doe@example.com', 19 | ]; 20 | 21 | // Armazenando dados no cache 22 | $Cacheer->putCache($cacheKey, $userProfile); 23 | 24 | // Recuperando dados do cache 25 | $cachedProfile = $Cacheer->getCache($cacheKey); 26 | 27 | if ($Cacheer->isSuccess()) { 28 | echo "Cache Found: "; 29 | print_r($cachedProfile); 30 | } else { 31 | echo $Cacheer->getMessage(); 32 | } 33 | 34 | 35 | -------------------------------------------------------------------------------- /Examples/example02.php: -------------------------------------------------------------------------------- 1 | __DIR__ . "/cache", 8 | "expirationTime" => "2 hour" 9 | ]; 10 | 11 | $Cacheer = new Cacheer($options); 12 | 13 | // Dados a serem armazenados no cache 14 | $cacheKey = 'daily_stats'; 15 | $dailyStats = [ 16 | 'visits' => 1500, 17 | 'signups' => 35, 18 | 'revenue' => 500.75, 19 | ]; 20 | 21 | // Armazenando dados no cache 22 | $Cacheer->putCache($cacheKey, $dailyStats); 23 | 24 | // Recuperando dados do cache por 2 horas 25 | $cachedStats = $Cacheer->getCache($cacheKey); 26 | 27 | if ($Cacheer->isSuccess()) { 28 | echo "Cache Found: "; 29 | print_r($cachedStats); 30 | } else { 31 | echo $Cacheer->getMessage(); 32 | } 33 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build: 2 | environment: 3 | php: 4 | version: 8.2 5 | dependencies: 6 | before: 7 | - sudo apt-get update 8 | - sudo apt-get install -y software-properties-common 9 | - sudo add-apt-repository ppa:ondrej/php -y 10 | - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y openssl libssl-dev 11 | - openssl version 12 | - export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH 13 | - export OPENSSL_CFLAGS="-I/usr/local/include" 14 | - export OPENSSL_LIBS="-L/usr/local/lib -lssl -lcrypto" 15 | tests: 16 | override: 17 | - echo "Skipping tests" 18 | tools: 19 | php_analyzer: 20 | enabled: true 21 | php_cs_fixer: 22 | enabled: true 23 | -------------------------------------------------------------------------------- /Examples/example04.php: -------------------------------------------------------------------------------- 1 | __DIR__ . "/cache", 8 | ]; 9 | 10 | $Cacheer = new Cacheer($options); 11 | 12 | // Dados a serem armazenados no cache com namespace 13 | $namespace = 'session_data_01'; 14 | $cacheKey = 'session_456'; 15 | $sessionData = [ 16 | 'user_id' => 456, 17 | 'login_time' => time(), 18 | ]; 19 | 20 | // Armazenando dados no cache com namespace 21 | $Cacheer->putCache($cacheKey, $sessionData, $namespace); 22 | 23 | // Recuperando dados do cache 24 | $cachedSessionData = $Cacheer->getCache($cacheKey, $namespace); 25 | 26 | if ($Cacheer->isSuccess()) { 27 | echo "Cache Found: "; 28 | print_r($cachedSessionData); 29 | } else { 30 | echo $Cacheer->getMessage(); 31 | } 32 | -------------------------------------------------------------------------------- /Examples/example05.php: -------------------------------------------------------------------------------- 1 | __DIR__ . "/cache", 9 | ]; 10 | 11 | $Cacheer = new Cacheer($options); 12 | 13 | // URL da API e chave de cache 14 | $apiUrl = 'https://jsonplaceholder.typicode.com/posts'; 15 | $cacheKey = 'api_response_' . md5($apiUrl); 16 | 17 | // Verificando se a resposta da API já está no cache 18 | $cachedResponse = $Cacheer->getCache($cacheKey); 19 | 20 | if ($Cacheer->isSuccess()) { 21 | // Use a resposta do cache 22 | $response = $cachedResponse; 23 | } else { 24 | // Faça a chamada à API e armazene a resposta no cache 25 | $response = file_get_contents($apiUrl); 26 | $Cacheer->putCache($cacheKey, $response); 27 | } 28 | 29 | // Usando a resposta da API (do cache ou da chamada) 30 | $data = json_decode($response, true); 31 | print_r($data); 32 | -------------------------------------------------------------------------------- /Examples/example08.php: -------------------------------------------------------------------------------- 1 | setDriver()->useRedisDriver(); 9 | 10 | // Dados a serem armazenados no cache 11 | $cacheKey = 'user_profile_01'; 12 | $userProfile = [ 13 | 'id' => 1, 14 | 'name' => 'Sílvio Silva', 15 | 'email' => 'gasparsilvio7@gmail.com', 16 | ]; 17 | 18 | // Armazenando dados no cache 19 | $Cacheer->putCache($cacheKey, $userProfile, ttl: 300); 20 | 21 | // Recuperando dados do cache 22 | if($Cacheer->isSuccess()){ 23 | echo "Cache Found: "; 24 | print_r($Cacheer->getCache($cacheKey)); 25 | } else { 26 | echo $Cacheer->getMessage(); 27 | } 28 | 29 | // Renovando os dados do cache 30 | $Cacheer->renewCache($cacheKey, 3600); 31 | 32 | if($Cacheer->isSuccess()){ 33 | echo $Cacheer->getMessage() . PHP_EOL; 34 | } else { 35 | echo $Cacheer->getMessage() . PHP_EOL; 36 | 37 | } -------------------------------------------------------------------------------- /Examples/example06.php: -------------------------------------------------------------------------------- 1 | setDriver()->useRedisDriver(); 9 | 10 | // Dados a serem armazenados no cache 11 | $cacheKey = 'user_profile_1234'; 12 | $userProfile = [ 13 | 'id' => 1, 14 | 'name' => 'Sílvio Silva', 15 | 'email' => 'gasparsilvio7@gmail.com', 16 | 'role' => 'Developer' 17 | ]; 18 | $cacheNamespace = 'userData'; 19 | 20 | // Armazenando dados no cache 21 | //$Cacheer->putCache($cacheKey, $userProfile, $cacheNamespace); 22 | 23 | $Cacheer->has($cacheKey, $cacheNamespace); 24 | 25 | // Verificando se o cache existe e recuperando os dados 26 | if ($Cacheer->isSuccess()) { 27 | $cachedProfile = $Cacheer->getCache($cacheKey, $cacheNamespace); 28 | echo "Perfil de Usuário Encontrado:\n"; 29 | print_r($cachedProfile); 30 | } else { 31 | echo "Cache não encontrado: " . $Cacheer->getMessage(); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /tests/Unit/BooleanReturnTest.php: -------------------------------------------------------------------------------- 1 | cache = new Cacheer(); 13 | $this->cache->setDriver()->useArrayDriver(); 14 | } 15 | 16 | public function testHasReturnsBoolean() 17 | { 18 | $this->cache->putCache('bool_key', 'value'); 19 | $this->assertTrue($this->cache->has('bool_key')); 20 | $this->assertTrue($this->cache->isSuccess()); 21 | 22 | $this->assertFalse($this->cache->has('unknown_key')); 23 | $this->assertFalse($this->cache->isSuccess()); 24 | } 25 | 26 | public function testMutatingMethodsReturnBoolean() 27 | { 28 | $this->assertTrue($this->cache->putCache('k', 'v')); 29 | $this->assertTrue($this->cache->flushCache()); 30 | $this->assertTrue($this->cache->putCache('k', 'v')); 31 | $this->assertTrue($this->cache->clearCache('k')); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Examples/example07.php: -------------------------------------------------------------------------------- 1 | setDriver()->useRedisDriver(); 9 | 10 | // Dados a serem armazenados no cache 11 | $cacheKey = 'user_profile_1'; 12 | $userProfile = [ 13 | 'id' => 1, 14 | 'name' => 'Sílvio Silva', 15 | 'email' => 'gasparsilvio7@gmail.com', 16 | ]; 17 | 18 | $userProfile02 = [ 19 | 'casaNº' => 2130, 20 | 'telefone' => "(999)999-9999" 21 | ]; 22 | 23 | 24 | // Armazenando dados no cache 25 | $Cacheer->putCache($cacheKey, $userProfile); 26 | 27 | // Recuperando dados do cache 28 | if($Cacheer->isSuccess()){ 29 | echo "Cache Found: "; 30 | print_r($Cacheer->getCache($cacheKey)); 31 | } else { 32 | echo $Cacheer->getMessage(); 33 | } 34 | 35 | 36 | // Mesclando os dados 37 | $Cacheer->appendCache($cacheKey, $userProfile02); 38 | 39 | if($Cacheer->isSuccess()){ 40 | echo $Cacheer->getMessage() . PHP_EOL; 41 | print_r($Cacheer->getCache($cacheKey)); 42 | } else { 43 | echo $Cacheer->getMessage(); 44 | } 45 | 46 | -------------------------------------------------------------------------------- /tests/Feature/RedisOptionBuilderFeatureTest.php: -------------------------------------------------------------------------------- 1 | setNamespace('app:') 11 | ->expirationTime('2 hours') 12 | ->flushAfter('1 day') 13 | ->build(); 14 | 15 | $this->assertArrayHasKey('namespace', $options); 16 | $this->assertArrayHasKey('expirationTime', $options); 17 | $this->assertArrayHasKey('flushAfter', $options); 18 | 19 | $this->assertSame('app:', $options['namespace']); 20 | $this->assertSame('2 hours', $options['expirationTime']); 21 | $this->assertSame('1 day', $options['flushAfter']); 22 | } 23 | 24 | public function test_it_allows_timebuilder_for_redis() 25 | { 26 | $options = OptionBuilder::forRedis() 27 | ->expirationTime()->minute(30) 28 | ->flushAfter()->day(2) 29 | ->build(); 30 | 31 | $this->assertSame('30 minutes', $options['expirationTime']); 32 | $this->assertSame('2 days', $options['flushAfter']); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Feature/DatabaseOptionBuilderFeatureTest.php: -------------------------------------------------------------------------------- 1 | table('cache_items') 11 | ->expirationTime('12 hours') 12 | ->flushAfter('7 days') 13 | ->build(); 14 | 15 | $this->assertArrayHasKey('table', $options); 16 | $this->assertArrayHasKey('expirationTime', $options); 17 | $this->assertArrayHasKey('flushAfter', $options); 18 | 19 | $this->assertSame('cache_items', $options['table']); 20 | $this->assertSame('12 hours', $options['expirationTime']); 21 | $this->assertSame('7 days', $options['flushAfter']); 22 | } 23 | 24 | public function test_it_allows_timebuilder_for_database() 25 | { 26 | $options = OptionBuilder::forDatabase() 27 | ->expirationTime()->hour(6) 28 | ->flushAfter()->week(1) 29 | ->build(); 30 | 31 | $this->assertSame('6 hours', $options['expirationTime']); 32 | $this->assertSame('1 weeks', $options['flushAfter']); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Helpers/EnvHelper.php: -------------------------------------------------------------------------------- 1 | 10 | * @package Silviooosilva\CacheerPhp 11 | */ 12 | class EnvHelper { 13 | 14 | /** 15 | * Gets the root path of the project. 16 | * 17 | * @return string 18 | */ 19 | public static function getRootPath(): string 20 | { 21 | // Try to get the root path from Composer's installed versions 22 | if (class_exists(InstalledVersions::class)) { 23 | $rootPackage = InstalledVersions::getRootPackage(); 24 | if (!empty($rootPackage['install_path'])) { 25 | return rtrim($rootPackage['install_path'], DIRECTORY_SEPARATOR); 26 | } 27 | } 28 | 29 | // Fallback: traverse directories from __DIR__ looking for .env.example 30 | $baseDir = __DIR__; 31 | while (!file_exists($baseDir . DIRECTORY_SEPARATOR . '.env.example') && $baseDir !== dirname($baseDir)) { 32 | $baseDir = dirname($baseDir); 33 | } 34 | 35 | return rtrim($baseDir, DIRECTORY_SEPARATOR); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Helpers/FlushHelper.php: -------------------------------------------------------------------------------- 1 | 11 | * @package Silviooosilva\CacheerPhp 12 | * 13 | * Builds deterministic file paths for last-flush timestamps per store type. 14 | */ 15 | class FlushHelper 16 | { 17 | /** 18 | * Returns a path to store a last-flush timestamp. 19 | * @param CacheStoreType|string $storeType e.g., 'redis' or 'db' 20 | * @param string $identifier e.g., namespace or table name 21 | * @return string 22 | */ 23 | public static function pathFor(CacheStoreType|string $storeType, string $identifier): string 24 | { 25 | $store = $storeType instanceof CacheStoreType ? $storeType->value : $storeType; 26 | $root = EnvHelper::getRootPath(); 27 | $dir = $root . DIRECTORY_SEPARATOR . 'CacheerPHP' . DIRECTORY_SEPARATOR . 'Flush'; 28 | if (!is_dir($dir)) { 29 | @mkdir($dir, 0755, true); 30 | } 31 | $safeId = preg_replace('/[^a-zA-Z0-9_-]+/', '_', $identifier); 32 | return $dir . DIRECTORY_SEPARATOR . $store . '_' . $safeId . '.time'; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "silviooosilva/cacheer-php", 3 | "description": "CacheerPHP is a minimalist package for caching in PHP, offering a simple interface for storing and retrieving cached data using multiple backends.", 4 | "keywords": [ 5 | "cache", 6 | "optimizer", 7 | "performance", 8 | "PHP", 9 | "caching", 10 | "cache-manager", 11 | "Silviooosilva", 12 | "speed", 13 | "optimization", 14 | "file-cache", 15 | "database", 16 | "database-cache", 17 | "mysql", 18 | "sqlite", 19 | "pgsql", 20 | "redis", 21 | "predis", 22 | "nosql" 23 | ], 24 | "homepage": "https://github.com/silviooosilva", 25 | "type": "library", 26 | "license": "MIT", 27 | "version": "v4.7.2", 28 | "autoload": { 29 | "files": [ 30 | "src/Boot/Configs.php" 31 | ], 32 | "psr-4": { 33 | "Silviooosilva\\CacheerPhp\\": "src/" 34 | } 35 | }, 36 | "authors": [ 37 | { 38 | "name": "Sílvio Silva", 39 | "email": "gasparsilvio7@gmail.com", 40 | "role": "Developer" 41 | } 42 | ], 43 | "require": { 44 | "php": ">=8.0", 45 | "vlucas/phpdotenv": "^5.6", 46 | "predis/predis": "^2.3", 47 | "ext-pdo": "*", 48 | "ext-openssl": "*", 49 | "ext-zlib": "*" 50 | }, 51 | "require-dev": { 52 | "phpunit/phpunit": "^11.2" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Config/Option/Builder/OptionBuilder.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Silviooosilva\CacheerPhp 13 | */ 14 | class OptionBuilder 15 | { 16 | 17 | /** 18 | * Creates a FileOptionBuilder instance for file-based cache options. 19 | * 20 | * @return FileOptionBuilder 21 | */ 22 | public static function forFile(): FileOptionBuilder 23 | { 24 | return new FileOptionBuilder(); 25 | } 26 | 27 | /** 28 | * Creates a RedisOptionBuilder instance for Redis cache options. 29 | * 30 | * @return RedisOptionBuilder 31 | */ 32 | public static function forRedis(): RedisOptionBuilder 33 | { 34 | return new RedisOptionBuilder(); 35 | } 36 | 37 | /** 38 | * Creates a DatabaseOptionBuilder instance for database cache options. 39 | * 40 | * @return DatabaseOptionBuilder 41 | */ 42 | public static function forDatabase(): DatabaseOptionBuilder 43 | { 44 | return new DatabaseOptionBuilder(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/CacheStore/Support/FileCacheBatchProcessor.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Silviooosilva\CacheerPhp 13 | */ 14 | class FileCacheBatchProcessor 15 | { 16 | 17 | /** 18 | * FileCacheBatchProcessor constructor. 19 | * 20 | * @param FileCacheStore $store 21 | */ 22 | public function __construct(private FileCacheStore $store) 23 | { 24 | } 25 | 26 | /** 27 | * Processes a batch of cache items and stores them. 28 | * 29 | * @param array $batchItems 30 | * @param string $namespace 31 | * @return void 32 | * @throws CacheFileException 33 | */ 34 | public function process(array $batchItems, string $namespace): void 35 | { 36 | foreach ($batchItems as $item) { 37 | CacheFileHelper::validateCacheItem($item); 38 | $cacheKey = $item['cacheKey']; 39 | $cacheData = $item['cacheData']; 40 | $mergedData = CacheFileHelper::mergeCacheData($cacheData); 41 | $this->store->putCache($cacheKey, $mergedData, $namespace); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Enums/DatabaseDriver.php: -------------------------------------------------------------------------------- 1 | 'MySQL(mysql)', 19 | self::MARIADB => 'MariaDB(mariadb)', 20 | self::SQLITE => 'SQLite(sqlite)', 21 | self::PGSQL => 'PgSQL(pgsql)', 22 | }; 23 | } 24 | 25 | /** 26 | * PDO DSN identifier for the driver. 27 | */ 28 | public function dsnName(): string 29 | { 30 | return match ($this) { 31 | self::MARIADB => self::MYSQL->value, 32 | default => $this->value, 33 | }; 34 | } 35 | 36 | /** 37 | * Whether the driver behaves like MySQL for SQL syntax decisions. 38 | */ 39 | public function isMysqlFamily(): bool 40 | { 41 | return $this === self::MYSQL || $this === self::MARIADB; 42 | } 43 | 44 | /** 45 | * Handy helper for building allow-list messages. 46 | * 47 | * @return array 48 | */ 49 | public static function labels(): array 50 | { 51 | return array_map(static fn (self $driver) => $driver->label(), self::cases()); 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/Helpers/CacheDatabaseHelper.php: -------------------------------------------------------------------------------- 1 | 11 | * @package Silviooosilva\CacheerPhp 12 | */ 13 | class CacheDatabaseHelper 14 | { 15 | /** 16 | * Validates a cache item. 17 | * 18 | * @param array $item 19 | * @return void 20 | */ 21 | public static function validateCacheItem(array $item): void 22 | { 23 | CacheerHelper::validateCacheItem( 24 | $item, 25 | fn($msg) => CacheDatabaseException::create($msg) 26 | ); 27 | } 28 | 29 | /** 30 | * Merges cache data with existing data. 31 | * 32 | * @param $cacheData 33 | * @return array 34 | */ 35 | public static function mergeCacheData($cacheData): array 36 | { 37 | return CacheerHelper::mergeCacheData($cacheData); 38 | } 39 | 40 | /** 41 | * Generates an array identifier for cache data. 42 | * 43 | * @param mixed $currentCacheData 44 | * @param mixed $cacheData 45 | * @return array 46 | */ 47 | public static function arrayIdentifier(mixed $currentCacheData, mixed $cacheData): array 48 | { 49 | return CacheerHelper::arrayIdentifier($currentCacheData, $cacheData); 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /tests/Unit/StaticAccessTest.php: -------------------------------------------------------------------------------- 1 | assertIsBool($result); 13 | } 14 | 15 | public function testFlushCacheDynamic(): void 16 | { 17 | $cache = new Cacheer(); 18 | $this->assertIsBool($cache->flushCache()); 19 | } 20 | 21 | public function testSetUp(): void 22 | { 23 | $cache = new Cacheer(); 24 | $options = [ 25 | 'driver' => 'file', 26 | 'path' => '/tmp/cache', 27 | ]; 28 | $cache->setUp($options); 29 | $this->assertSame($options, $cache->options); 30 | } 31 | 32 | public static function testSetUpStatic(): void 33 | { 34 | $options = [ 35 | 'driver' => 'file', 36 | 'path' => '/tmp/cache', 37 | ]; 38 | Cacheer::setUp($options); 39 | self::assertSame($options, Cacheer::getOptions()); 40 | } 41 | 42 | public function testSetUpStaticWithOptionBuilder(): void 43 | { 44 | $options = OptionBuilder::forFile() 45 | ->dir('/tmp/cache') 46 | ->flushAfter()->hour(2) 47 | ->build(); 48 | 49 | Cacheer::setUp($options); 50 | self::assertSame($options, Cacheer::getOptions()); 51 | } 52 | } -------------------------------------------------------------------------------- /src/Utils/CacheDataFormatter.php: -------------------------------------------------------------------------------- 1 | 8 | * @package Silviooosilva\CacheerPhp 9 | */ 10 | class CacheDataFormatter 11 | { 12 | /** @param mixed $data */ 13 | private mixed $data; 14 | 15 | /** 16 | * CacheDataFormatter constructor. 17 | * 18 | * @param mixed $data 19 | */ 20 | public function __construct(mixed $data) 21 | { 22 | $this->data = $data; 23 | } 24 | 25 | /** 26 | * Converts the data to JSON format. 27 | * 28 | * @return string|false 29 | */ 30 | public function toJson(): bool|string 31 | { 32 | return json_encode( 33 | $this->data, 34 | JSON_PRETTY_PRINT | 35 | JSON_UNESCAPED_UNICODE | 36 | JSON_UNESCAPED_SLASHES 37 | ); 38 | } 39 | 40 | /** 41 | * Converts the data to an array. 42 | * 43 | * @return array 44 | */ 45 | public function toArray(): array 46 | { 47 | return (array)$this->data; 48 | } 49 | 50 | /** 51 | * Converts the data to a string. 52 | * 53 | * @return string 54 | */ 55 | public function toString(): string 56 | { 57 | return (string)$this->data; 58 | } 59 | 60 | /** 61 | * Converts the data to an object. 62 | * 63 | * @return object 64 | */ 65 | public function toObject(): object 66 | { 67 | return (object)$this->data; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/CacheStore/CacheManager/RedisCacheManager.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Silviooosilva\CacheerPhp 13 | */ 14 | class RedisCacheManager 15 | { 16 | 17 | /** @var Predis\Client */ 18 | private static $redis; 19 | 20 | /** @param string $namespace */ 21 | private static $namespace; 22 | 23 | /** 24 | * Connects to the Redis server using the configuration defined in REDIS_CONNECTION_CONFIG. 25 | * 26 | * @return Client 27 | */ 28 | public static function connect() 29 | { 30 | Autoloader::register(); 31 | self::$redis = new Client([ 32 | 'scheme' => 'tcp', 33 | 'host' => REDIS_CONNECTION_CONFIG['REDIS_HOST'], 34 | 'port' => REDIS_CONNECTION_CONFIG['REDIS_PORT'], 35 | 'password' => REDIS_CONNECTION_CONFIG['REDIS_PASSWORD'], 36 | 'database' => 0 37 | ]); 38 | self::auth(); 39 | self::$namespace = REDIS_CONNECTION_CONFIG['REDIS_NAMESPACE'] ?? 'Cache'; 40 | return self::$redis; 41 | } 42 | 43 | /** 44 | * Authenticates the Redis connection if a password is provided in the configuration. 45 | * 46 | * @return void 47 | */ 48 | private static function auth(): void 49 | { 50 | if(is_string(REDIS_CONNECTION_CONFIG['REDIS_PASSWORD']) && REDIS_CONNECTION_CONFIG['REDIS_PASSWORD'] !== '') { 51 | self::$redis->auth(REDIS_CONNECTION_CONFIG['REDIS_PASSWORD']); 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/CacheStore/Support/FileCachePathBuilder.php: -------------------------------------------------------------------------------- 1 | 11 | * @package Silviooosilva\CacheerPhp 12 | */ 13 | class FileCachePathBuilder 14 | { 15 | /** 16 | * FileCachePathBuilder constructor. 17 | * 18 | * @param FileCacheManager $fileManager 19 | * @param string $baseDir 20 | */ 21 | public function __construct(private FileCacheManager $fileManager, private string $baseDir) 22 | { 23 | } 24 | 25 | /** 26 | * Builds the full path for a cache item based on its key and namespace. 27 | * 28 | * @param string $cacheKey 29 | * @param string $namespace 30 | * @return string 31 | * @throws CacheFileException 32 | */ 33 | public function build(string $cacheKey, string $namespace = ''): string 34 | { 35 | $dir = $this->namespaceDir($namespace); 36 | if (!empty($namespace)) { 37 | $this->fileManager->createDirectory($dir); 38 | } 39 | return $dir . md5($cacheKey) . '.cache'; 40 | } 41 | 42 | /** 43 | * Builds the directory path for a given namespace. 44 | * 45 | * @param string $namespace 46 | * @return string 47 | */ 48 | public function namespaceDir(string $namespace = ''): string 49 | { 50 | $namespace = $namespace ? md5($namespace) . '/' : ''; 51 | $cacheDir = rtrim($this->baseDir, '/') . '/'; 52 | return $cacheDir . $namespace; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Unit/RedisOptionBuilderStoreTest.php: -------------------------------------------------------------------------------- 1 | client = new PredisClient([ 19 | 'scheme' => 'tcp', 20 | 'host' => REDIS_CONNECTION_CONFIG['REDIS_HOST'] ?? '127.0.0.1', 21 | 'port' => REDIS_CONNECTION_CONFIG['REDIS_PORT'] ?? 6379, 22 | ]); 23 | $this->client->connect(); 24 | // simple call to verify 25 | $this->client->ping(); 26 | } catch (\Throwable $e) { 27 | $this->markTestSkipped('Redis not available: ' . $e->getMessage()); 28 | } 29 | } 30 | 31 | protected function tearDown(): void 32 | { 33 | if ($this->client) { 34 | $this->client->disconnect(); 35 | } 36 | } 37 | 38 | public function test_redis_store_uses_namespace_from_option_builder() 39 | { 40 | $options = OptionBuilder::forRedis() 41 | ->setNamespace('app:') 42 | ->build(); 43 | 44 | $cache = new Cacheer($options); 45 | $cache->setDriver()->useRedisDriver(); 46 | 47 | $key = 'rb_key'; 48 | $data = ['v' => 1]; 49 | 50 | $cache->putCache($key, $data); 51 | $this->assertTrue($cache->isSuccess()); 52 | 53 | // Should be stored with prefix 'app:' 54 | $this->assertTrue((bool)$this->client->exists('app:' . $key)); 55 | 56 | $read = $cache->getCache($key); 57 | $this->assertEquals($data, $read); 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/Helpers/CacheRedisHelper.php: -------------------------------------------------------------------------------- 1 | 11 | * @package Silviooosilva\CacheerPhp 12 | */ 13 | class CacheRedisHelper 14 | { 15 | 16 | /** 17 | * serializes or unserializes data based on the $serialize flag. 18 | * 19 | * @param mixed $data 20 | * @param bool $serialize 21 | * @return mixed 22 | */ 23 | public static function serialize(mixed $data, bool $serialize = true): mixed 24 | { 25 | if($serialize) { 26 | return serialize($data); 27 | } 28 | 29 | return unserialize($data); 30 | 31 | } 32 | 33 | /** 34 | * Validates a cache item. 35 | * 36 | * @param array $item 37 | * @return void 38 | */ 39 | public static function validateCacheItem(array $item): void 40 | { 41 | CacheerHelper::validateCacheItem( 42 | $item, 43 | fn($msg) => CacheRedisException::create($msg) 44 | ); 45 | } 46 | 47 | /** 48 | * Merges cache data with existing data. 49 | * 50 | * @param $cacheData 51 | * @return array 52 | */ 53 | public static function mergeCacheData($cacheData): array 54 | { 55 | return CacheerHelper::mergeCacheData($cacheData); 56 | } 57 | 58 | /** 59 | * Generates an array identifier for cache data. 60 | * 61 | * @param mixed $currentCacheData 62 | * @param mixed $cacheData 63 | * @return array 64 | */ 65 | public static function arrayIdentifier(mixed $currentCacheData, mixed $cacheData): array 66 | { 67 | return CacheerHelper::arrayIdentifier($currentCacheData, $cacheData); 68 | } 69 | 70 | } 71 | 72 | -------------------------------------------------------------------------------- /tests/Unit/SecurityFeatureTest.php: -------------------------------------------------------------------------------- 1 | cacheDir = __DIR__ . '/cache'; 14 | if (!is_dir($this->cacheDir)) { 15 | mkdir($this->cacheDir, 0755, true); 16 | } 17 | 18 | $this->cache = new Cacheer(['cacheDir' => $this->cacheDir]); 19 | } 20 | 21 | protected function tearDown(): void 22 | { 23 | $this->cache->flushCache(); 24 | } 25 | 26 | public function testCompressionFeature() 27 | { 28 | $this->cache->useCompression(); 29 | $data = ['foo' => 'bar']; 30 | 31 | $this->cache->putCache('compression_key', $data); 32 | $this->assertTrue($this->cache->isSuccess()); 33 | 34 | $cached = $this->cache->getCache('compression_key'); 35 | $this->assertEquals($data, $cached); 36 | } 37 | 38 | public function testEncryptionFeature() 39 | { 40 | $this->cache->useEncryption('secret'); 41 | $data = ['foo' => 'bar']; 42 | 43 | $this->cache->putCache('encryption_key', $data); 44 | $this->assertTrue($this->cache->isSuccess()); 45 | 46 | $cached = $this->cache->getCache('encryption_key'); 47 | $this->assertEquals($data, $cached); 48 | } 49 | 50 | public function testCompressionAndEncryptionTogether() 51 | { 52 | $this->cache->useCompression(); 53 | $this->cache->useEncryption('secret'); 54 | $data = ['foo' => 'bar']; 55 | 56 | $this->cache->putCache('secure_key', $data); 57 | $this->assertTrue($this->cache->isSuccess()); 58 | 59 | $cached = $this->cache->getCache('secure_key'); 60 | $this->assertEquals($data, $cached); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Exceptions/CacheDatabaseException.php: -------------------------------------------------------------------------------- 1 | "; 12 | 13 | /** 14 | * Creates a new instance of CacheDatabaseException. 15 | * 16 | * @param string $message 17 | * @param int $code 18 | * @param Exception|null $previous 19 | * @param array $details 20 | * @return self 21 | */ 22 | public static function create(string $message = "", int $code = 0, ?Exception $previous = null, array $details = []) 23 | { 24 | return new self(self::getBefore() . ": " .$message, $code, $previous, $details); 25 | } 26 | 27 | 28 | /** 29 | * Gets the static text that will be prepended to the exception message. 30 | * 31 | * @return string 32 | */ 33 | public static function getBefore(): string 34 | { 35 | return self::$before; 36 | } 37 | 38 | /** 39 | * Sets the static text that will be prepended to the exception message. 40 | * 41 | * @return void 42 | */ 43 | public static function setBefore(string $text) 44 | { 45 | self::$before = $text; 46 | } 47 | 48 | /* 49 | * Converts the exception to an array representation. 50 | * 51 | * @return array 52 | */ 53 | public function toArray() 54 | { 55 | return parent::toArray(); 56 | } 57 | 58 | /** 59 | * Converts the exception to a JSON serializable format. 60 | * 61 | * @return string 62 | */ 63 | public function jsonSerialize(): array 64 | { 65 | return parent::jsonSerialize(); 66 | } 67 | 68 | /** 69 | * Converts the exception to a JSON string. 70 | * 71 | * @param int $options 72 | * @return string 73 | */ 74 | public function toJson(int $options = 0) 75 | { 76 | return parent::toJson($options); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Exceptions/CacheFileException.php: -------------------------------------------------------------------------------- 1 | "; 10 | 11 | /** 12 | * Creates a new instance of CacheFileException. 13 | * 14 | * @param string $message 15 | * @param int $code 16 | * @param Exception|null $previous 17 | * @param array $details 18 | * @return self 19 | */ 20 | public static function create(string $message = "", int $code = 0, ?Exception $previous = null, array $details = []) 21 | { 22 | return new self(self::getBefore() . ": " . $message, $code, $previous, $details); 23 | } 24 | 25 | /** 26 | * Gets the static text that will be prepended to the exception message. 27 | * 28 | * @return string 29 | */ 30 | public static function getBefore() 31 | { 32 | return self::$before; 33 | } 34 | 35 | /** 36 | * Sets the static text that will be prepended to the exception message. 37 | * 38 | * @param string $text 39 | */ 40 | public static function setBefore(string $text) 41 | { 42 | self::$before = $text; 43 | } 44 | 45 | /* 46 | * Converts the exception to an array representation. 47 | * 48 | * @return array 49 | */ 50 | public function toArray() 51 | { 52 | return parent::toArray(); 53 | } 54 | 55 | /** 56 | * Converts the exception to a JSON serializable format. 57 | * 58 | * @return string 59 | */ 60 | public function jsonSerialize(): array 61 | { 62 | return parent::jsonSerialize(); 63 | } 64 | 65 | /** 66 | * Converts the exception to a JSON string. 67 | * 68 | * @param int $options 69 | * @return string 70 | */ 71 | public function toJson(int $options = 0) 72 | { 73 | return parent::toJson($options); 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /src/Exceptions/ConnectionException.php: -------------------------------------------------------------------------------- 1 | "; 10 | 11 | /** 12 | * Creates a new instance of ConnectionException. 13 | * 14 | * @param string $message 15 | * @param int $code 16 | * @param Exception|null $previous 17 | * @param array $details 18 | * @return self 19 | */ 20 | public static function create(string $message = "", int $code = 0, ?Exception $previous = null, array $details = []) 21 | { 22 | return new self(self::getBefore() . ": " . $message, $code, $previous, $details); 23 | } 24 | 25 | /** 26 | * Gets the static text that will be prepended to the exception message. 27 | * 28 | * @return string 29 | */ 30 | public static function getBefore(): string 31 | { 32 | return self::$before; 33 | } 34 | 35 | /** 36 | * Sets the static text that will be prepended to the exception message. 37 | * 38 | * @param string $text 39 | */ 40 | public static function setBefore(string $text): void 41 | { 42 | self::$before = $text; 43 | } 44 | 45 | /* 46 | * Converts the exception to an array representation. 47 | * 48 | * @return array 49 | */ 50 | public function toArray() 51 | { 52 | return parent::toArray(); 53 | } 54 | 55 | /** 56 | * Converts the exception to a JSON serializable format. 57 | * 58 | * @return string 59 | */ 60 | public function jsonSerialize(): array 61 | { 62 | return parent::jsonSerialize(); 63 | } 64 | 65 | /** 66 | * Converts the exception to a JSON string. 67 | * 68 | * @param int $options 69 | * @return string 70 | */ 71 | public function toJson(int $options = 0) 72 | { 73 | return parent::toJson($options); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Exceptions/CacheRedisException.php: -------------------------------------------------------------------------------- 1 | "; 10 | 11 | /** 12 | * Creates a new instance of CacheRedisException. 13 | * 14 | * @param string $message 15 | * @param int $code 16 | * @param Exception|null $previous 17 | * @param array $details 18 | * @return self 19 | */ 20 | public static function create(string $message = "", int $code = 0, ?Exception $previous = null, array $details = []) 21 | { 22 | return new self(self::getBefore() . ": " . $message, $code, $previous, $details); 23 | } 24 | 25 | /** 26 | * Gets the static text that will be prepended to the exception message. 27 | * 28 | * @return string 29 | */ 30 | public static function getBefore(): string 31 | { 32 | return self::$before; 33 | } 34 | 35 | /** 36 | * Sets the static text that will be prepended to the exception message. 37 | * 38 | * @param string $text 39 | */ 40 | public static function setBefore(string $text): void 41 | { 42 | self::$before = $text; 43 | } 44 | 45 | /* 46 | * Converts the exception to an array representation. 47 | * 48 | * @return array 49 | */ 50 | public function toArray() 51 | { 52 | return parent::toArray(); 53 | } 54 | 55 | /** 56 | * Converts the exception to a JSON serializable format. 57 | * 58 | * @return string 59 | */ 60 | public function jsonSerialize(): array 61 | { 62 | return parent::jsonSerialize(); 63 | } 64 | 65 | /** 66 | * Converts the exception to a JSON string. 67 | * 68 | * @param int $options 69 | * @return string 70 | */ 71 | public function toJson(int $options = 0) 72 | { 73 | return parent::toJson($options); 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /src/Core/ConnectionFactory.php: -------------------------------------------------------------------------------- 1 | 13 | * @package Silviooosilva\CacheerPhp 14 | */ 15 | class ConnectionFactory 16 | { 17 | 18 | /** 19 | * Creates a new PDO instance based on the specified database configuration. 20 | * 21 | * @param array|null $database 22 | * @return PDO|null 23 | * @throws ConnectionException 24 | */ 25 | public static function createConnection(?array $database = null): ?PDO 26 | { 27 | $connection = Connect::getConnection(); 28 | $dbConf = $database ?? CACHEER_DATABASE_CONFIG[$connection->value]; 29 | 30 | $driver = null; 31 | if (isset($dbConf['adapter'])) { 32 | $driver = DatabaseDriver::tryFrom($dbConf['adapter']); 33 | } 34 | if ($driver === null && isset($dbConf['driver'])) { 35 | $driver = DatabaseDriver::tryFrom($dbConf['driver']); 36 | } 37 | $driver ??= $connection; 38 | 39 | $dsnDriver = $driver->dsnName(); 40 | $dbConf['driver'] = $dsnDriver; 41 | 42 | if ($driver === DatabaseDriver::SQLITE) { 43 | $dbName = $dbConf['dbname']; 44 | $dbDsn = $dsnDriver . ':' . $dbName; 45 | } else { 46 | $dbName = "{$dsnDriver}-{$dbConf['dbname']}@{$dbConf['host']}"; 47 | $dbDsn = "{$dsnDriver}:host={$dbConf['host']};dbname={$dbConf['dbname']};port={$dbConf['port']}"; 48 | } 49 | 50 | try { 51 | $options = $dbConf['options'] ?? []; 52 | foreach ($options as $key => $value) { 53 | if (is_string($value) && defined($value)) { 54 | $options[$key] = constant($value); 55 | } 56 | } 57 | return new PDO($dbDsn, $dbConf['username'] ?? null, $dbConf['passwd'] ?? null, $options); 58 | } catch (PDOException $exception) { 59 | throw ConnectionException::create($exception->getMessage(), $exception->getCode(), $exception->getPrevious()); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/CacheStore/CacheManager/GenericFlusher.php: -------------------------------------------------------------------------------- 1 | 11 | * @package Silviooosilva\CacheerPhp 12 | * 13 | * Lightweight flusher that stores last flush timestamp in a file and invokes a provided callback. 14 | */ 15 | class GenericFlusher 16 | { 17 | /** @var string */ 18 | private string $lastFlushTimeFile; 19 | 20 | /** @var callable */ 21 | private $flushCallback; 22 | 23 | /** 24 | * @param string $lastFlushTimeFile 25 | * @param callable $flushCallback Callback that performs the actual flush 26 | */ 27 | public function __construct(string $lastFlushTimeFile, callable $flushCallback) 28 | { 29 | $this->lastFlushTimeFile = $lastFlushTimeFile; 30 | $this->flushCallback = $flushCallback; 31 | } 32 | 33 | /** 34 | * Performs a flush and records the time. 35 | * @return void 36 | */ 37 | public function flushCache(): void 38 | { 39 | ($this->flushCallback)(); 40 | @file_put_contents($this->lastFlushTimeFile, (string) time()); 41 | } 42 | 43 | /** 44 | * Handles auto-flush if 'flushAfter' is present in options. 45 | * @param array $options 46 | * @return void 47 | */ 48 | public function handleAutoFlush(array $options): void 49 | { 50 | if (!isset($options['flushAfter'])) { 51 | return; 52 | } 53 | $this->scheduleFlush((string) $options['flushAfter']); 54 | } 55 | 56 | /** 57 | * @param string $flushAfter 58 | * @return void 59 | */ 60 | private function scheduleFlush(string $flushAfter): void 61 | { 62 | $flushAfterSeconds = (int) CacheFileHelper::convertExpirationToSeconds($flushAfter); 63 | 64 | if (!file_exists($this->lastFlushTimeFile)) { 65 | @file_put_contents($this->lastFlushTimeFile, (string) time()); 66 | return; 67 | } 68 | 69 | $lastFlushTime = (int) @file_get_contents($this->lastFlushTimeFile); 70 | 71 | if ((time() - $lastFlushTime) >= $flushAfterSeconds) { 72 | $this->flushCache(); 73 | @file_put_contents($this->lastFlushTimeFile, (string) time()); 74 | } 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /tests/Unit/MigrationManagerDynamicTableTest.php: -------------------------------------------------------------------------------- 1 | pdo = Connect::getInstance(); 16 | $this->table = uniqid('mm_custom_table_'); 17 | // Ensure clean start 18 | $this->pdo->exec("DROP TABLE IF EXISTS {$this->table}"); 19 | } 20 | 21 | protected function tearDown(): void 22 | { 23 | if ($this->pdo) { 24 | $this->pdo->exec("DROP TABLE IF EXISTS {$this->table}"); 25 | } 26 | } 27 | 28 | public function test_migrate_creates_custom_table() 29 | { 30 | MigrationManager::migrate($this->pdo, $this->table); 31 | 32 | // Verify table exists (SQLite check) 33 | $stmt = $this->pdo->prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = :t"); 34 | $stmt->bindValue(':t', $this->table); 35 | $stmt->execute(); 36 | $row = $stmt->fetch(PDO::FETCH_ASSOC); 37 | $this->assertNotFalse($row); 38 | 39 | // Verify basic insert/select using PDO 40 | $expiration = date('Y-m-d H:i:s', time() + 60); 41 | $stmt = $this->pdo->prepare("INSERT INTO {$this->table} (cacheKey, cacheData, cacheNamespace, expirationTime, created_at) VALUES (:k, :d, '', :e, :c)"); 42 | $stmt->bindValue(':k', 'mk'); 43 | $stmt->bindValue(':d', serialize(['a' => 1])); 44 | $stmt->bindValue(':e', $expiration); 45 | $stmt->bindValue(':c', date('Y-m-d H:i:s')); 46 | $this->assertTrue($stmt->execute()); 47 | 48 | $stmt = $this->pdo->prepare("SELECT cacheData FROM {$this->table} WHERE cacheKey = :k LIMIT 1"); 49 | $stmt->bindValue(':k', 'mk'); 50 | $stmt->execute(); 51 | $row = $stmt->fetch(PDO::FETCH_ASSOC); 52 | $this->assertEquals(['a' => 1], unserialize($row['cacheData'])); 53 | } 54 | 55 | public function test_default_constant_table_exists() 56 | { 57 | // With boot autoload, the default CACHEER_TABLE should be created via Connect::getInstance() 58 | $stmt = $this->pdo->query("SELECT name FROM sqlite_master WHERE type='table' AND name = 'cacheer_table'"); 59 | $row = $stmt->fetch(PDO::FETCH_ASSOC); 60 | $this->assertNotFalse($row); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Unit/RedisOptionBuilderTTLAndFlushTest.php: -------------------------------------------------------------------------------- 1 | client = new PredisClient([ 19 | 'scheme' => 'tcp', 20 | 'host' => REDIS_CONNECTION_CONFIG['REDIS_HOST'] ?? '127.0.0.1', 21 | 'port' => REDIS_CONNECTION_CONFIG['REDIS_PORT'] ?? 6379, 22 | ]); 23 | $this->client->connect(); 24 | $this->client->ping(); 25 | } catch (\Throwable $e) { 26 | $this->markTestSkipped('Redis not available: ' . $e->getMessage()); 27 | } 28 | } 29 | 30 | protected function tearDown(): void 31 | { 32 | if ($this->client) { 33 | $this->client->disconnect(); 34 | } 35 | } 36 | 37 | public function test_expiration_time_from_options_sets_default_ttl() 38 | { 39 | $options = OptionBuilder::forRedis() 40 | ->setNamespace('app:') 41 | ->expirationTime('1 seconds') 42 | ->build(); 43 | 44 | $cache = new Cacheer($options); 45 | $cache->setDriver()->useRedisDriver(); 46 | 47 | $key = 'redis_opt_ttl_key'; 48 | $cache->putCache($key, 'v'); 49 | $this->assertTrue($cache->isSuccess()); 50 | 51 | sleep(2); 52 | $this->assertNull($cache->getCache($key)); 53 | } 54 | 55 | public function test_flush_after_from_options_triggers_auto_flush() 56 | { 57 | $options = OptionBuilder::forRedis() 58 | ->setNamespace('app:') 59 | ->flushAfter('1 seconds') 60 | ->build(); 61 | 62 | $flushFile = FlushHelper::pathFor('redis', 'app:'); 63 | file_put_contents($flushFile, (string) (time() - 3600)); 64 | 65 | // seed 66 | $seed = new Cacheer(OptionBuilder::forRedis()->setNamespace('app:')->build()); 67 | $seed->setDriver()->useRedisDriver(); 68 | $seed->putCache('to_be_flushed', '1'); 69 | 70 | // new instance should auto-flush on init 71 | $cache = new Cacheer($options); 72 | $cache->setDriver()->useRedisDriver(); 73 | $this->assertFalse((bool)$this->client->exists('app:to_be_flushed')); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Support/TimeBuilder.php: -------------------------------------------------------------------------------- 1 | 10 | * @package Silviooosilva\CacheerPhp 11 | */ 12 | class TimeBuilder 13 | { 14 | 15 | /** @param Closure $callback */ 16 | private Closure $callback; 17 | 18 | /** @var mixed */ 19 | private $builder = null; 20 | 21 | /** 22 | * TimeBuilder constructor. 23 | * @param Closure $callback 24 | * @param mixed $builder 25 | * 26 | * @return void 27 | */ 28 | public function __construct(Closure $callback, $builder) 29 | { 30 | $this->callback = $callback; 31 | $this->builder = $builder; 32 | } 33 | 34 | /** 35 | * Sets the time in seconds. 36 | * 37 | * @param int $seconds 38 | * @return mixed 39 | */ 40 | public function second(int $seconds) 41 | { 42 | return $this->setTime($seconds, "seconds"); 43 | } 44 | 45 | /** 46 | * Sets the time in minutes. 47 | * 48 | * @param int $minutes 49 | * @return mixed 50 | */ 51 | public function minute(int $minutes) 52 | { 53 | return $this->setTime($minutes, "minutes"); 54 | } 55 | 56 | /** 57 | * Sets the time in hours. 58 | * 59 | * @param int $hours 60 | * @return mixed 61 | */ 62 | public function hour(int $hours) 63 | { 64 | return $this->setTime($hours, "hours"); 65 | } 66 | 67 | /** 68 | * Sets the time in days. 69 | * 70 | * @param int $days 71 | * @return mixed 72 | */ 73 | public function day(int $days) 74 | { 75 | return $this->setTime($days, "days"); 76 | } 77 | 78 | /** 79 | * Sets the time in weeks. 80 | * 81 | * @param int $weeks 82 | * @return mixed 83 | */ 84 | public function week(int $weeks) 85 | { 86 | return $this->setTime($weeks, "weeks"); 87 | } 88 | 89 | /** 90 | * Sets the time in months. 91 | * 92 | * @param int $months 93 | * @return mixed 94 | */ 95 | public function month(int $months) 96 | { 97 | return $this->setTime($months, "months"); 98 | } 99 | 100 | 101 | /** 102 | * This method sets the time value and unit, and invokes the callback with the formatted string. 103 | * 104 | * @param int $value 105 | * @param string $unit 106 | * @return mixed 107 | */ 108 | private function setTime(int $value, string $unit) 109 | { 110 | ($this->callback)("{$value} {$unit}"); 111 | return $this->builder; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/Helpers/SqliteHelper.php: -------------------------------------------------------------------------------- 1 | 9 | * @package Silviooosilva\CacheerPhp 10 | */ 11 | class SqliteHelper 12 | { 13 | 14 | /** 15 | * Gets the path to the SQLite database file. 16 | * 17 | * @param string $database 18 | * @param ?string $path 19 | * @return string 20 | */ 21 | public static function database(string $database = 'database.sqlite', ?string $path = null): string 22 | { 23 | return self::getDynamicSqliteDbPath($database, $path); 24 | } 25 | 26 | /** 27 | * Gets the path to the SQLite database file dynamically. 28 | * 29 | * @param string $database 30 | * @param ?string $path 31 | * @return string 32 | */ 33 | private static function getDynamicSqliteDbPath(string $database, ?string $path = null): string 34 | { 35 | $rootPath = EnvHelper::getRootPath(); 36 | $databaseDir = is_null($path) ? $rootPath . '/database' : $rootPath . '/' . $path; 37 | $dbFile = $databaseDir . '/' . self::checkExtension($database); 38 | 39 | if (!is_dir($databaseDir)) { 40 | self::createDatabaseDir($databaseDir); 41 | } 42 | if (!file_exists($dbFile)) { 43 | self::createDatabaseFile($dbFile); 44 | } 45 | 46 | return $dbFile; 47 | } 48 | 49 | /** 50 | * Creates the database directory if it does not exist. 51 | * 52 | * @param string $databaseDir 53 | * @return void 54 | */ 55 | private static function createDatabaseDir(string $databaseDir): void 56 | { 57 | if (!is_dir($databaseDir)) { 58 | mkdir($databaseDir, 0755, true); 59 | } 60 | } 61 | 62 | /** 63 | * Creates the SQLite database file if it does not exist. 64 | * 65 | * @param string $dbFile 66 | * @return void 67 | */ 68 | private static function createDatabaseFile(string $dbFile): void 69 | { 70 | if (!file_exists($dbFile)) { 71 | file_put_contents($dbFile, ''); 72 | } 73 | } 74 | 75 | /** 76 | * Checks if the database name has the correct extension. 77 | * If not, appends '.sqlite' to the name. 78 | * 79 | * @param string $database 80 | * @return string 81 | */ 82 | private static function checkExtension(string $database): string 83 | { 84 | if (!str_contains($database, '.sqlite')) { 85 | return $database . '.sqlite'; 86 | } 87 | return $database; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/Exceptions/BaseException.php: -------------------------------------------------------------------------------- 1 | details = $details; 23 | } 24 | 25 | /** 26 | * Get the details of the exception. 27 | * 28 | * @return array 29 | **/ 30 | public function getDetails() 31 | { 32 | return $this->details; 33 | } 34 | 35 | /** 36 | * Set the details of the exception. 37 | * 38 | * @param array $details 39 | * */ 40 | public function setDetails(array $details) 41 | { 42 | $this->details = $details; 43 | } 44 | 45 | /** 46 | * Convert the exception to an array representation. 47 | * 48 | * @return array 49 | * */ 50 | public function toArray() 51 | { 52 | return [ 53 | 'type' => static::class, 54 | 'message' => $this->getMessage(), 55 | 'code' => $this->getCode(), 56 | 'details' => $this->getDetails(), 57 | 'file' => $this->getFile(), 58 | 'line' => $this->getLine(), 59 | 'trace' => $this->getTrace() 60 | ]; 61 | } 62 | 63 | /** 64 | * Convert the exception to a JSON serializable format. 65 | * 66 | * @return array 67 | * */ 68 | public function jsonSerialize(): array 69 | { 70 | return $this->toArray(); 71 | } 72 | 73 | /** 74 | * Convert the exception to a JSON string. 75 | * 76 | * @return string 77 | * */ 78 | public function toJson(int $options = 0) 79 | { 80 | return json_encode($this->toArray(), $options | JSON_THROW_ON_ERROR); 81 | } 82 | 83 | /** 84 | * Convert the exception to a string representation. 85 | * 86 | * @return string 87 | * */ 88 | public function __toString() 89 | { 90 | return sprintf( 91 | "[%s] %s in %s on line %d\nDetails: %s", 92 | $this->getCode(), 93 | $this->getMessage(), 94 | $this->getFile(), 95 | $this->getLine(), 96 | json_encode($this->getDetails(), JSON_PRETTY_PRINT) 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/CacheStore/CacheManager/FileCacheFlusher.php: -------------------------------------------------------------------------------- 1 | 11 | * @package Silviooosilva\CacheerPhp 12 | */ 13 | class FileCacheFlusher 14 | { 15 | /** 16 | * @var FileCacheManager 17 | */ 18 | private FileCacheManager $fileManager; 19 | 20 | /** 21 | * @var string $cacheDir 22 | */ 23 | private string $cacheDir; 24 | 25 | /** 26 | * @var string $lastFlushTimeFile 27 | */ 28 | private string $lastFlushTimeFile; 29 | 30 | /** 31 | * FileCacheFlusher constructor. 32 | * 33 | * @param FileCacheManager $fileManager 34 | * @param string $cacheDir 35 | */ 36 | public function __construct(FileCacheManager $fileManager, string $cacheDir) 37 | { 38 | $this->fileManager = $fileManager; 39 | $this->cacheDir = $cacheDir; 40 | $this->lastFlushTimeFile = "$cacheDir/last_flush_time"; 41 | } 42 | 43 | /** 44 | * Flushes all cache items and updates the last flush timestamp. 45 | * 46 | * @return void 47 | */ 48 | public function flushCache(): void 49 | { 50 | $this->fileManager->clearDirectory($this->cacheDir); 51 | file_put_contents($this->lastFlushTimeFile, time()); 52 | } 53 | 54 | /** 55 | * Handles the auto-flush functionality based on options. 56 | * 57 | * @param array $options 58 | * @return void 59 | */ 60 | public function handleAutoFlush(array $options): void 61 | { 62 | if (isset($options['flushAfter'])) { 63 | $this->scheduleFlush($options['flushAfter']); 64 | } 65 | } 66 | 67 | /** 68 | * Schedules a flush operation based on the provided interval. 69 | * 70 | * @param string $flushAfter 71 | * @return void 72 | * @throws CacheFileException 73 | */ 74 | private function scheduleFlush(string $flushAfter): void 75 | { 76 | $flushAfterSeconds = CacheFileHelper::convertExpirationToSeconds($flushAfter); 77 | 78 | if (!$this->fileManager->fileExists($this->lastFlushTimeFile)) { 79 | $this->fileManager->writeFile($this->lastFlushTimeFile, time()); 80 | return; 81 | } 82 | 83 | $lastFlushTime = (int) $this->fileManager->readFile($this->lastFlushTimeFile); 84 | 85 | if ((time() - $lastFlushTime) >= $flushAfterSeconds) { 86 | $this->flushCache(); 87 | $this->fileManager->writeFile($this->lastFlushTimeFile, time()); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/Unit/DatabaseOptionBuilderStoreTest.php: -------------------------------------------------------------------------------- 1 | pdo = Connect::getInstance(); 17 | 18 | // Create table compatible with SQLite 19 | $this->pdo->exec("CREATE TABLE IF NOT EXISTS {$this->table} ( 20 | id INTEGER PRIMARY KEY AUTOINCREMENT, 21 | cacheKey VARCHAR(255) NOT NULL, 22 | cacheData TEXT NOT NULL, 23 | cacheNamespace VARCHAR(255), 24 | expirationTime DATETIME NOT NULL, 25 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 26 | UNIQUE(cacheKey, cacheNamespace) 27 | ); 28 | CREATE INDEX IF NOT EXISTS idx_{$this->table}_cacheKey ON {$this->table} (cacheKey); 29 | CREATE INDEX IF NOT EXISTS idx_{$this->table}_cacheNamespace ON {$this->table} (cacheNamespace); 30 | CREATE INDEX IF NOT EXISTS idx_{$this->table}_expirationTime ON {$this->table} (expirationTime); 31 | CREATE INDEX IF NOT EXISTS idx_{$this->table}_key_namespace ON {$this->table} (cacheKey, cacheNamespace); 32 | "); 33 | } 34 | 35 | protected function tearDown(): void 36 | { 37 | if ($this->pdo instanceof PDO) { 38 | $this->pdo->exec("DROP TABLE IF EXISTS {$this->table}"); 39 | } 40 | } 41 | 42 | public function test_database_store_uses_custom_table_from_option_builder() 43 | { 44 | $options = OptionBuilder::forDatabase() 45 | ->table($this->table) 46 | ->build(); 47 | 48 | $cache = new Cacheer($options); 49 | $cache->setDriver()->useDatabaseDriver(); 50 | 51 | $key = 'opt_table_key'; 52 | $data = ['x' => 1]; 53 | 54 | $cache->putCache($key, $data, '', 3600); 55 | $this->assertTrue($cache->isSuccess()); 56 | 57 | // Validate via direct SQL against the custom table 58 | $nowFunction = "DATETIME('now', 'localtime')"; 59 | $stmt = $this->pdo->prepare("SELECT cacheData FROM {$this->table} WHERE cacheKey = :k AND cacheNamespace = '' AND expirationTime > $nowFunction LIMIT 1"); 60 | $stmt->bindValue(':k', $key); 61 | $stmt->execute(); 62 | $row = $stmt->fetch(PDO::FETCH_ASSOC); 63 | 64 | $this->assertNotFalse($row); 65 | $this->assertEquals($data, unserialize($row['cacheData'])); 66 | 67 | // And ensure Cacheer can read it back through the store 68 | $read = $cache->getCache($key); 69 | $this->assertEquals($data, $read); 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/CacheStore/CacheManager/OptionBuilders/RedisOptionBuilder.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Silviooosilva\CacheerPhp 13 | */ 14 | class RedisOptionBuilder 15 | { 16 | /** @var ?string */ 17 | private ?string $namespace = null; 18 | 19 | /** @var ?string */ 20 | private ?string $expirationTime = null; 21 | 22 | /** @var ?string */ 23 | private ?string $flushAfter = null; 24 | 25 | /** @var array */ 26 | private array $options = []; 27 | 28 | /** 29 | * Sets the Redis key namespace prefix. 30 | * 31 | * @param string $namespace 32 | * @return $this 33 | */ 34 | public function setNamespace(string $namespace): self 35 | { 36 | $this->namespace = $namespace; 37 | return $this; 38 | } 39 | 40 | /** 41 | * Sets the default expiration time for keys. 42 | * 43 | * @param ?string $expirationTime 44 | * @return $this|TimeBuilder 45 | */ 46 | public function expirationTime(?string $expirationTime = null) 47 | { 48 | if (!is_null($expirationTime)) { 49 | $this->expirationTime = $expirationTime; 50 | return $this; 51 | } 52 | 53 | return new TimeBuilder(function ($formattedTime) { 54 | $this->expirationTime = $formattedTime; 55 | }, $this); 56 | } 57 | 58 | /** 59 | * Sets the auto-flush interval. 60 | * 61 | * @param ?string $flushAfter 62 | * @return $this|TimeBuilder 63 | */ 64 | public function flushAfter(?string $flushAfter = null) 65 | { 66 | if (!is_null($flushAfter)) { 67 | $this->flushAfter = mb_strtolower($flushAfter, 'UTF-8'); 68 | return $this; 69 | } 70 | 71 | return new TimeBuilder(function ($formattedTime) { 72 | $this->flushAfter = $formattedTime; 73 | }, $this); 74 | } 75 | 76 | /** 77 | * Builds the options array. 78 | * 79 | * @return array 80 | */ 81 | public function build(): array 82 | { 83 | return $this->validated(); 84 | } 85 | 86 | /** 87 | * Validate and assemble options. 88 | * @return array 89 | */ 90 | private function validated(): array 91 | { 92 | foreach ($this->properties() as $key => $value) { 93 | if (!empty($value)) { 94 | $this->options[$key] = $value; 95 | } 96 | } 97 | return $this->options; 98 | } 99 | 100 | /** 101 | * Returns current properties. 102 | * @return array 103 | */ 104 | private function properties(): array 105 | { 106 | return [ 107 | 'namespace' => $this->namespace, 108 | 'expirationTime' => $this->expirationTime, 109 | 'flushAfter' => $this->flushAfter, 110 | ]; 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /src/CacheStore/CacheManager/OptionBuilders/DatabaseOptionBuilder.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Silviooosilva\CacheerPhp 13 | */ 14 | class DatabaseOptionBuilder 15 | { 16 | /** @var ?string */ 17 | private ?string $table = null; 18 | 19 | /** @var ?string */ 20 | private ?string $expirationTime = null; 21 | 22 | /** @var ?string */ 23 | private ?string $flushAfter = null; 24 | 25 | /** @var array */ 26 | private array $options = []; 27 | 28 | /** 29 | * Sets the database table used for cache storage. 30 | * 31 | * @param string $table 32 | * @return $this 33 | */ 34 | public function table(string $table): self 35 | { 36 | $this->table = $table; 37 | return $this; 38 | } 39 | 40 | /** 41 | * Sets the default expiration time for records. 42 | * 43 | * @param ?string $expirationTime 44 | * @return $this|TimeBuilder 45 | */ 46 | public function expirationTime(?string $expirationTime = null) 47 | { 48 | if (!is_null($expirationTime)) { 49 | $this->expirationTime = $expirationTime; 50 | return $this; 51 | } 52 | 53 | return new TimeBuilder(function ($formattedTime) { 54 | $this->expirationTime = $formattedTime; 55 | }, $this); 56 | } 57 | 58 | /** 59 | * Sets an auto-flush interval for database cache. 60 | * 61 | * @param ?string $flushAfter 62 | * @return $this|TimeBuilder 63 | */ 64 | public function flushAfter(?string $flushAfter = null) 65 | { 66 | if (!is_null($flushAfter)) { 67 | $this->flushAfter = mb_strtolower($flushAfter, 'UTF-8'); 68 | return $this; 69 | } 70 | 71 | return new TimeBuilder(function ($formattedTime) { 72 | $this->flushAfter = $formattedTime; 73 | }, $this); 74 | } 75 | 76 | /** 77 | * Builds the options array. 78 | * 79 | * @return array 80 | */ 81 | public function build(): array 82 | { 83 | return $this->validated(); 84 | } 85 | 86 | /** 87 | * Validate and assemble options. 88 | * @return array 89 | */ 90 | private function validated(): array 91 | { 92 | foreach ($this->properties() as $key => $value) { 93 | if (!empty($value)) { 94 | $this->options[$key] = $value; 95 | } 96 | } 97 | return $this->options; 98 | } 99 | 100 | /** 101 | * Returns current properties. 102 | * @return array 103 | */ 104 | private function properties(): array 105 | { 106 | return [ 107 | 'table' => $this->table, 108 | 'expirationTime' => $this->expirationTime, 109 | 'flushAfter' => $this->flushAfter, 110 | ]; 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /src/Utils/CacheLogger.php: -------------------------------------------------------------------------------- 1 | 9 | * @package Silviooosilva\CacheerPhp 10 | */ 11 | class CacheLogger 12 | { 13 | private mixed $logFile; 14 | private mixed $maxFileSize; // 5 MB by default 15 | private string $logLevel; 16 | private array $logLevels = ['DEBUG', 'INFO', 'WARNING', 'ERROR']; 17 | 18 | public function __construct($logFile = 'cacheer.log', $maxFileSize = 5 * 1024 * 1024, $logLevel = 'DEBUG') 19 | { 20 | $this->logFile = $logFile; 21 | $this->maxFileSize = $maxFileSize; 22 | $this->logLevel = strtoupper($logLevel); 23 | } 24 | 25 | /** 26 | * Logs a info message. 27 | * 28 | * @return void 29 | */ 30 | public function info($message): void 31 | { 32 | $this->log('INFO', $message); 33 | } 34 | 35 | /** 36 | * Logs a warning message. 37 | * 38 | * @return void 39 | */ 40 | public function warning($message): void 41 | { 42 | $this->log('WARNING', $message); 43 | } 44 | 45 | /** 46 | * Logs an error message. 47 | * 48 | * @return void 49 | */ 50 | public function error($message): void 51 | { 52 | $this->log('ERROR', $message); 53 | } 54 | 55 | /** 56 | * Logs a debug message. 57 | * 58 | * @return void 59 | */ 60 | public function debug($message): void 61 | { 62 | $this->log('DEBUG', $message); 63 | } 64 | 65 | /** 66 | * Checks if the log level is sufficient to log the message. 67 | * 68 | * @param mixed $level 69 | * @return bool 70 | */ 71 | private function shouldLog(mixed $level): bool 72 | { 73 | return array_search($level, $this->logLevels) >= array_search($this->logLevel, $this->logLevels); 74 | } 75 | 76 | /** 77 | * Rotates the log file if it exceeds the maximum size. 78 | * 79 | * @return void 80 | */ 81 | private function rotateLog(): void 82 | { 83 | if (file_exists($this->logFile) && filesize($this->logFile) >= $this->maxFileSize) { 84 | $date = date('Y-m-d_H-i-s'); 85 | rename($this->logFile, "cacheer_$date.log"); 86 | } 87 | } 88 | 89 | /** 90 | * Logs a message to the log file. 91 | * 92 | * @param mixed $level 93 | * @param string $message 94 | * @return void 95 | */ 96 | private function log(mixed $level, string $message): void 97 | { 98 | if (!$this->shouldLog($level)) { 99 | return; 100 | } 101 | 102 | $this->rotateLog(); 103 | 104 | $date = date('Y-m-d H:i:s'); 105 | $logMessage = "[$date] [$level] $message" . PHP_EOL; 106 | file_put_contents($this->logFile, $logMessage, FILE_APPEND); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Core/Connect.php: -------------------------------------------------------------------------------- 1 | 13 | * @package Silviooosilva\CacheerPhp 14 | */ 15 | class Connect 16 | { 17 | /** 18 | * Active database driver for new connections. 19 | */ 20 | public static DatabaseDriver $connection = DatabaseDriver::SQLITE; 21 | 22 | /** 23 | * Holds the last error encountered during connection attempts. 24 | * 25 | * @var PDOException|null 26 | */ 27 | private static ?PDOException $error = null; 28 | 29 | 30 | /** 31 | * Creates a new PDO instance based on the specified database configuration. 32 | * 33 | * @param array|null $database 34 | * @return PDO|null 35 | * @throws ConnectionException 36 | */ 37 | public static function getInstance(?array $database = null): ?PDO 38 | { 39 | $pdo = ConnectionFactory::createConnection($database); 40 | if ($pdo) { 41 | MigrationManager::migrate($pdo); 42 | } 43 | return $pdo; 44 | } 45 | 46 | /** 47 | * Sets the connection type for the database. 48 | * 49 | * @param DatabaseDriver|string $connection 50 | * @return void 51 | * @throws ConnectionException 52 | */ 53 | public static function setConnection(DatabaseDriver|string $connection): void 54 | { 55 | $driver = $connection instanceof DatabaseDriver 56 | ? $connection 57 | : DatabaseDriver::tryFrom(strtolower($connection)); 58 | 59 | if ($driver === null) { 60 | $labels = DatabaseDriver::labels(); 61 | throw ConnectionException::create('Only [' . implode(', ', $labels) . '] are available at the moment...'); 62 | } 63 | 64 | self::$connection = $driver; 65 | } 66 | 67 | /** 68 | * Gets the current connection type. 69 | * 70 | * @return DatabaseDriver 71 | */ 72 | public static function getConnection(): DatabaseDriver 73 | { 74 | return self::$connection; 75 | } 76 | 77 | /** 78 | * Returns the last error encountered during connection attempts.\ 79 | * 80 | * @return PDOException|null 81 | */ 82 | public static function getError(): ?PDOException 83 | { 84 | return self::$error; 85 | } 86 | 87 | /** 88 | * Prevents instantiation of the Connect class. 89 | * This class is designed to be used statically, so it cannot be instantiated. 90 | * 91 | * @return void 92 | */ 93 | private function __construct() {} 94 | 95 | /** 96 | * Prevents cloning of the Connect instance. 97 | * 98 | * @return void 99 | */ 100 | private function __clone() {} 101 | } 102 | -------------------------------------------------------------------------------- /src/Helpers/CacheFileHelper.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Silviooosilva\CacheerPhp 13 | */ 14 | class CacheFileHelper 15 | { 16 | 17 | /** 18 | * Converts a string expiration format to seconds. 19 | * 20 | * @param string $expiration 21 | * @return float|int 22 | * @throws CacheFileException 23 | */ 24 | public static function convertExpirationToSeconds(string $expiration): float|int 25 | { 26 | $units = [ 27 | 'second' => CacheTimeConstants::SECOND->value, 28 | 'minute' => CacheTimeConstants::MINUTE->value, 29 | 'hour' => CacheTimeConstants::HOUR->value, 30 | 'day' => CacheTimeConstants::DAY->value, 31 | 'week' => CacheTimeConstants::WEEK->value, 32 | 'month' => CacheTimeConstants::MONTH->value, 33 | 'year' => CacheTimeConstants::YEAR->value, 34 | ]; 35 | foreach ($units as $unit => $value) { 36 | if (str_contains($expiration, $unit)) { 37 | return (int)$expiration * $value; 38 | } 39 | } 40 | throw CacheFileException::create("Invalid expiration format"); 41 | } 42 | 43 | /** 44 | * Merges cache data with existing data. 45 | * 46 | * @param $cacheData 47 | * @return array 48 | */ 49 | public static function mergeCacheData($cacheData): array 50 | { 51 | return CacheerHelper::mergeCacheData($cacheData); 52 | } 53 | 54 | /** 55 | * Validates a cache item. 56 | * 57 | * @param array $item 58 | * @return void 59 | */ 60 | public static function validateCacheItem(array $item): void 61 | { 62 | CacheerHelper::validateCacheItem( 63 | $item, 64 | fn($msg) => CacheFileException::create($msg) 65 | ); 66 | } 67 | 68 | /** 69 | * Calculates the TTL (Time To Live) for cache items. 70 | * 71 | * @param null $ttl 72 | * @param int|null $defaultTTL 73 | * @return mixed 74 | * @throws CacheFileException 75 | */ 76 | public static function ttl($ttl = null, ?int $defaultTTL = null): mixed 77 | { 78 | if ($ttl) { 79 | $ttl = is_string($ttl) ? self::convertExpirationToSeconds($ttl) : $ttl; 80 | } else { 81 | $ttl = $defaultTTL; 82 | } 83 | return $ttl; 84 | } 85 | 86 | /** 87 | * Generates an array identifier for cache data. 88 | * 89 | * @param mixed $currentCacheData 90 | * @param mixed $cacheData 91 | * @return array 92 | */ 93 | public static function arrayIdentifier(mixed $currentCacheData, mixed $cacheData): array 94 | { 95 | return CacheerHelper::arrayIdentifier($currentCacheData, $cacheData); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Boot/Configs.php: -------------------------------------------------------------------------------- 1 | load(); 13 | 14 | $connectionValue = strtolower($_ENV['DB_CONNECTION'] ?? DatabaseDriver::MYSQL->value); 15 | $connectionDriver = DatabaseDriver::tryFrom($connectionValue) ?? DatabaseDriver::MYSQL; 16 | $Host = $_ENV['DB_HOST'] ?? 'localhost'; 17 | $Port = $_ENV['DB_PORT'] ?? '3306'; 18 | $DBName = $_ENV['DB_DATABASE'] ?? 'cacheer_db'; 19 | $User = $_ENV['DB_USERNAME'] ?? 'root'; 20 | $Password = $_ENV['DB_PASSWORD'] ?? ''; 21 | 22 | // Retrieve Redis environment variables 23 | $redisClient = $_ENV['REDIS_CLIENT'] ?? ''; 24 | $redisHost = $_ENV['REDIS_HOST'] ?? 'localhost'; 25 | $redisPassword = $_ENV['REDIS_PASSWORD'] ?? ''; 26 | $redisPort = $_ENV['REDIS_PORT'] ?? '6379'; 27 | $redisNamespace = $_ENV['REDIS_NAMESPACE'] ?? ''; 28 | $cacheTable = $_ENV['CACHEER_TABLE'] ?? 'cacheer_table'; 29 | 30 | Connect::setConnection($connectionDriver); 31 | 32 | $commonPdoOptions = [ 33 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 34 | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, 35 | PDO::ATTR_CASE => PDO::CASE_NATURAL, 36 | ]; 37 | 38 | $mysqlConfig = [ 39 | 'adapter' => DatabaseDriver::MYSQL->value, 40 | 'driver' => DatabaseDriver::MYSQL->dsnName(), 41 | 'host' => $Host, 42 | 'port' => $Port, 43 | 'dbname' => $DBName, 44 | 'username' => $User, 45 | 'passwd' => $Password, 46 | 'options' => array_replace( 47 | [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'], 48 | $commonPdoOptions 49 | ), 50 | ]; 51 | 52 | $mariaDbConfig = $mysqlConfig; 53 | $mariaDbConfig['adapter'] = DatabaseDriver::MARIADB->value; 54 | $mariaDbConfig['driver'] = DatabaseDriver::MARIADB->dsnName(); 55 | 56 | // Database configuration array 57 | define('CACHEER_DATABASE_CONFIG', [ 58 | DatabaseDriver::MYSQL->value => $mysqlConfig, 59 | DatabaseDriver::MARIADB->value => $mariaDbConfig, 60 | DatabaseDriver::SQLITE->value => [ 61 | 'adapter' => DatabaseDriver::SQLITE->value, 62 | 'driver' => DatabaseDriver::SQLITE->dsnName(), 63 | 'dbname' => SqliteHelper::database(), 64 | 'options' => $commonPdoOptions, 65 | ], 66 | DatabaseDriver::PGSQL->value => [ 67 | 'adapter' => DatabaseDriver::PGSQL->value, 68 | 'driver' => DatabaseDriver::PGSQL->dsnName(), 69 | 'host' => $Host, 70 | 'port' => $Port, 71 | 'dbname' => $DBName, 72 | 'username' => $User, 73 | 'passwd' => $Password, 74 | 'options' => $commonPdoOptions, 75 | ], 76 | ]); 77 | 78 | // Redis configuration array 79 | define('REDIS_CONNECTION_CONFIG', [ 80 | 'REDIS_CLIENT' => $redisClient, 81 | 'REDIS_HOST' => $redisHost, 82 | 'REDIS_PASSWORD' => $redisPassword, 83 | 'REDIS_PORT' => $redisPort, 84 | 'REDIS_NAMESPACE'=> $redisNamespace 85 | ]); 86 | 87 | // Cache table name for database driver 88 | if (!defined('CACHEER_TABLE')) { 89 | define('CACHEER_TABLE', $cacheTable); 90 | } 91 | -------------------------------------------------------------------------------- /src/Utils/CacheDriver.php: -------------------------------------------------------------------------------- 1 | 17 | * @package Silviooosilva\CacheerPhp 18 | */ 19 | class CacheDriver 20 | { 21 | 22 | /** 23 | * @var Cacheer 24 | */ 25 | protected Cacheer $cacheer; 26 | 27 | /** @param string $logPath */ 28 | public string $logPath = 'cacheer.log'; 29 | 30 | /** 31 | * CacheDriver constructor. 32 | * 33 | * @param Cacheer $cacheer 34 | */ 35 | public function __construct(Cacheer $cacheer) 36 | { 37 | $this->cacheer = $cacheer; 38 | } 39 | 40 | /** 41 | * Uses the database driver for caching. 42 | * 43 | * @return Cacheer 44 | */ 45 | public function useDatabaseDriver(): Cacheer 46 | { 47 | $this->cacheer->cacheStore = new DatabaseCacheStore($this->logPath, $this->cacheer->options); 48 | return $this->cacheer; 49 | } 50 | 51 | /** 52 | * Uses the file driver for caching. 53 | * 54 | * @return Cacheer 55 | */ 56 | public function useFileDriver(): Cacheer 57 | { 58 | $this->cacheer->options['loggerPath'] = $this->logPath; 59 | $this->cacheer->cacheStore = new FileCacheStore($this->cacheer->options); 60 | return $this->cacheer; 61 | } 62 | 63 | /** 64 | * Uses the Redis driver for caching. 65 | * 66 | * @return Cacheer 67 | */ 68 | public function useRedisDriver(): Cacheer 69 | { 70 | $this->cacheer->cacheStore = new RedisCacheStore($this->logPath, $this->cacheer->options); 71 | return $this->cacheer; 72 | } 73 | 74 | /** 75 | * Uses the array driver for caching. 76 | * 77 | * @return Cacheer 78 | */ 79 | public function useArrayDriver(): Cacheer 80 | { 81 | $this->cacheer->cacheStore = new ArrayCacheStore($this->logPath); 82 | return $this->cacheer; 83 | } 84 | 85 | /** 86 | * Uses the default driver for caching. 87 | * 88 | * @return Cacheer 89 | */ 90 | public function useDefaultDriver(): Cacheer 91 | { 92 | if (!isset($this->cacheer->options['cacheDir'])) { 93 | $projectRoot = EnvHelper::getRootPath(); 94 | $cacheDir = $projectRoot . DIRECTORY_SEPARATOR . "CacheerPHP" . DIRECTORY_SEPARATOR . "Cache"; 95 | if ($this->isDir($cacheDir)) { 96 | $this->cacheer->options['cacheDir'] = $cacheDir; 97 | } else { 98 | throw CacheFileException::create("Failed to create cache directory: " . $cacheDir); 99 | } 100 | } 101 | $this->useFileDriver(); 102 | return $this->cacheer; 103 | } 104 | 105 | /** 106 | * Checks if the directory exists or creates it. 107 | * 108 | * @param mixed $dirName 109 | * @return bool 110 | */ 111 | private function isDir(mixed $dirName): bool 112 | { 113 | if (is_dir($dirName)) { 114 | return true; 115 | } 116 | return mkdir($dirName, 0755, true); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/Unit/DatabaseOptionBuilderTTLAndFlushTest.php: -------------------------------------------------------------------------------- 1 | exec("CREATE TABLE IF NOT EXISTS {$this->table} ( 17 | id INTEGER PRIMARY KEY AUTOINCREMENT, 18 | cacheKey VARCHAR(255) NOT NULL, 19 | cacheData TEXT NOT NULL, 20 | cacheNamespace VARCHAR(255), 21 | expirationTime DATETIME NOT NULL, 22 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 23 | UNIQUE(cacheKey, cacheNamespace) 24 | ); 25 | CREATE INDEX IF NOT EXISTS idx_{$this->table}_cacheKey ON {$this->table} (cacheKey); 26 | CREATE INDEX IF NOT EXISTS idx_{$this->table}_cacheNamespace ON {$this->table} (cacheNamespace); 27 | CREATE INDEX IF NOT EXISTS idx_{$this->table}_expirationTime ON {$this->table} (expirationTime); 28 | CREATE INDEX IF NOT EXISTS idx_{$this->table}_key_namespace ON {$this->table} (cacheKey, cacheNamespace); 29 | "); 30 | } 31 | 32 | protected function tearDown(): void 33 | { 34 | // clean up flush file 35 | $path = FlushHelper::pathFor('db', $this->table); 36 | if (file_exists($path)) { 37 | @unlink($path); 38 | } 39 | $pdo = Silviooosilva\CacheerPhp\Core\Connect::getInstance(); 40 | $pdo->exec("DROP TABLE IF EXISTS {$this->table}"); 41 | } 42 | 43 | public function test_expiration_time_from_options_sets_default_ttl() 44 | { 45 | $options = OptionBuilder::forDatabase() 46 | ->table($this->table) 47 | ->expirationTime('-1 seconds') 48 | ->build(); 49 | 50 | $cache = new Cacheer($options); 51 | $cache->setDriver()->useDatabaseDriver(); 52 | 53 | $key = 'db_opt_ttl_key'; 54 | $data = ['a' => 1]; 55 | $cache->putCache($key, $data); // default ttl should be overridden to past time 56 | $this->assertTrue($cache->isSuccess()); 57 | 58 | $pdo = Silviooosilva\CacheerPhp\Core\Connect::getInstance(); 59 | $nowFunction = "DATETIME('now', 'localtime')"; 60 | $stmt = $pdo->prepare("SELECT expirationTime FROM {$this->table} WHERE cacheKey = :k LIMIT 1"); 61 | $stmt->bindValue(':k', $key); 62 | $stmt->execute(); 63 | $row = $stmt->fetch(PDO::FETCH_ASSOC); 64 | $this->assertNotFalse($row); 65 | $this->assertLessThanOrEqual(time(), strtotime($row['expirationTime'])); 66 | } 67 | 68 | public function test_flush_after_from_options_triggers_auto_flush() 69 | { 70 | $options = OptionBuilder::forDatabase() 71 | ->table($this->table) 72 | ->flushAfter('1 seconds') 73 | ->build(); 74 | 75 | // Pre-create an old last flush file to force a flush on init 76 | $flushFile = FlushHelper::pathFor('db', $this->table); 77 | file_put_contents($flushFile, (string) (time() - 3600)); 78 | 79 | // Seed data using a cache without flushAfter 80 | $seed = new Cacheer(OptionBuilder::forDatabase()->table($this->table)->build()); 81 | $seed->setDriver()->useDatabaseDriver(); 82 | $seed->putCache('to_be_flushed', 'x'); 83 | $this->assertTrue($seed->isSuccess()); 84 | 85 | // New instance with auto-flush should clear table on construct 86 | $cache = new Cacheer($options); 87 | $cache->setDriver()->useDatabaseDriver(); 88 | 89 | $this->assertEmpty($cache->getAll()); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Helpers/CacheConfig.php: -------------------------------------------------------------------------------- 1 | 17 | * @package Silviooosilva\CacheerPhp 18 | */ 19 | class CacheConfig 20 | { 21 | 22 | /** 23 | * @var Cacheer 24 | */ 25 | protected Cacheer $cacheer; 26 | 27 | /** 28 | * CacheConfig constructor. 29 | * 30 | * @param Cacheer $cacheer 31 | */ 32 | public function __construct(Cacheer $cacheer) 33 | { 34 | $this->cacheer = $cacheer; 35 | $this->setTimeZone(date_default_timezone_get()); 36 | } 37 | 38 | /** 39 | * Sets the default timezone for the application. 40 | * 41 | * @param string $timezone 42 | * @return $this 43 | */ 44 | public function setTimeZone(string $timezone): CacheConfig 45 | { 46 | /** 47 | * Make sure the provided timezone is valid 48 | * 49 | * https://www.php.net/manual/en/timezones.php 50 | * */ 51 | 52 | if (in_array($timezone, timezone_identifiers_list())) { 53 | date_default_timezone_set($timezone); 54 | } 55 | return $this; 56 | } 57 | 58 | /** 59 | * Sets the cache driver for the application. 60 | * 61 | * @return CacheDriver 62 | */ 63 | public function setDriver(): CacheDriver 64 | { 65 | return new CacheDriver($this->cacheer); 66 | } 67 | 68 | /** 69 | * Sets the logger path for the cache driver. 70 | * 71 | * @param string $path 72 | * @return Cacheer 73 | */ 74 | public function setLoggerPath(string $path): Cacheer 75 | { 76 | 77 | $cacheDriver = $this->setDriver(); 78 | $cacheDriver->logPath = $path; 79 | 80 | $cacheDriverInstance = $this->cacheer->cacheStore; 81 | 82 | return match (get_class($cacheDriverInstance)) { 83 | FileCacheStore::class => $cacheDriver->useFileDriver(), 84 | RedisCacheStore::class => $cacheDriver->useRedisDriver(), 85 | ArrayCacheStore::class => $cacheDriver->useArrayDriver(), 86 | default => $cacheDriver->useDatabaseDriver(), 87 | }; 88 | } 89 | 90 | /** 91 | * Sets the database connection type for the application. 92 | * 93 | * @param DatabaseDriver|string $driver 94 | * @return void 95 | * @throws ConnectionException 96 | */ 97 | public function setDatabaseConnection(DatabaseDriver|string $driver): void 98 | { 99 | Connect::setConnection($driver); 100 | } 101 | 102 | /** 103 | * Sets up the Cacheer instance with the provided options. 104 | * 105 | * @param array $options 106 | * @return void 107 | */ 108 | public function setUp(array $options): void 109 | { 110 | $this->cacheer->options = $options; 111 | } 112 | 113 | /** 114 | * Gets the options for the Cacheer instance. 115 | * 116 | * @return array 117 | */ 118 | public function getOptions(): array 119 | { 120 | return $this->cacheer->options; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /tests/Feature/OptionBuildTest.php: -------------------------------------------------------------------------------- 1 | cacheDir = __DIR__ . '/cache'; 16 | if (!file_exists($this->cacheDir) || !is_dir($this->cacheDir)) { 17 | mkdir($this->cacheDir, 0755, true); 18 | } 19 | 20 | $this->cache = new Cacheer(); 21 | } 22 | 23 | protected function tearDown(): void 24 | { 25 | $this->cache->flushCache(); 26 | } 27 | 28 | public function test_it_can_set_cache_directory() 29 | { 30 | $cacheDir = __DIR__ . "/cache"; 31 | 32 | $options = OptionBuilder::forFile() 33 | ->dir($cacheDir) 34 | ->build(); 35 | 36 | $this->assertArrayHasKey('cacheDir', $options); 37 | $this->assertEquals($cacheDir, $options['cacheDir']); 38 | } 39 | 40 | 41 | public function test_it_can_set_expiration_time() 42 | { 43 | 44 | $options = OptionBuilder::forFile() 45 | ->expirationTime('2 hours') 46 | ->build(); 47 | 48 | $this->assertArrayHasKey('expirationTime', $options); 49 | $this->assertEquals('2 hours', $options['expirationTime']); 50 | } 51 | 52 | public function test_it_can_set_flush_after() 53 | { 54 | $options = OptionBuilder::forFile() 55 | ->flushAfter('11 seconds') 56 | ->build(); 57 | 58 | $this->assertArrayHasKey('flushAfter', $options); 59 | $this->assertEquals('11 seconds', $options['flushAfter']); 60 | } 61 | 62 | public function test_it_can_set_multiple_options_together() 63 | { 64 | $cacheDir = __DIR__ . "/cache"; 65 | 66 | $options = OptionBuilder::forFile() 67 | ->dir($cacheDir) 68 | ->expirationTime('1 day') 69 | ->flushAfter('30 minutes') 70 | ->build(); 71 | 72 | $this->assertEquals([ 73 | 'cacheDir' => $cacheDir, 74 | 'expirationTime' => '1 day', 75 | 'flushAfter' => '30 minutes', 76 | ], $options); 77 | } 78 | 79 | public function test_it_allows_setting_expiration_time_with_timebuilder() 80 | { 81 | $options = OptionBuilder::forFile()->expirationTime()->week(1)->build(); 82 | $this->assertArrayHasKey('expirationTime', $options); 83 | $this->assertEquals('1 weeks', $options['expirationTime']); 84 | } 85 | 86 | public function test_it_allows_setting_flush_after_with_timebuilder() 87 | { 88 | $options = OptionBuilder::forFile()->flushAfter()->second(10)->build(); 89 | $this->assertArrayHasKey('flushAfter', $options); 90 | $this->assertEquals('10 seconds', $options['flushAfter']); 91 | } 92 | 93 | public function test_it_can_set_multiple_options_together_with_timebuilder() 94 | { 95 | $cacheDir = __DIR__ . "/cache"; 96 | $options = OptionBuilder::forFile() 97 | ->dir($cacheDir) 98 | ->expirationTime()->week(1) 99 | ->flushAfter()->minute(10) 100 | ->build(); 101 | 102 | $this->assertEquals([ 103 | 'cacheDir' => $cacheDir, 104 | 'expirationTime' => '1 weeks', 105 | 'flushAfter' => '10 minutes', 106 | ], $options); 107 | } 108 | 109 | public function test_it_returns_empty_array_when_no_options_are_set() 110 | { 111 | $options = OptionBuilder::forFile()->build(); 112 | $this->assertIsArray($options); 113 | $this->assertEmpty($options); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/CacheStore/CacheManager/OptionBuilders/FileOptionBuilder.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Silviooosilva\CacheerPhp 13 | */ 14 | class FileOptionBuilder 15 | { 16 | /** @param null|string $cacheDir */ 17 | private ?string $cacheDir = null; 18 | 19 | /** @param null|string $expirationTime */ 20 | private ?string $expirationTime = null; 21 | 22 | /** @param null|string $flushAfter */ 23 | private ?string $flushAfter = null; 24 | 25 | /** @param array $options */ 26 | private array $options = []; 27 | 28 | /** 29 | * Directory where cache files will be stored. 30 | * 31 | * @param string $cacheDir 32 | * @return $this 33 | */ 34 | public function dir(string $cacheDir) 35 | { 36 | $this->cacheDir = $cacheDir; 37 | return $this; 38 | } 39 | 40 | /** 41 | * Sets the expiration time for cache items. 42 | * @param ?string $expirationTime 43 | * @return $this|TimeBuilder 44 | */ 45 | public function expirationTime(?string $expirationTime = null) 46 | { 47 | 48 | if (!is_null($expirationTime)) { 49 | $this->expirationTime = $expirationTime; 50 | return $this; 51 | } 52 | 53 | return new TimeBuilder(function ($formattedTime){ 54 | $this->expirationTime = $formattedTime; 55 | }, $this); 56 | } 57 | 58 | /** 59 | * Sets the flush time for cache items. 60 | * This is the time after which the cache will be flushed. 61 | * 62 | * @param ?string $flushAfter 63 | * @return $this|TimeBuilder 64 | */ 65 | public function flushAfter(?string $flushAfter = null) 66 | { 67 | 68 | if (!is_null($flushAfter)) { 69 | $this->flushAfter = mb_strtolower($flushAfter, 'UTF-8'); 70 | return $this; 71 | } 72 | 73 | return new TimeBuilder(function ($formattedTime){ 74 | $this->flushAfter = $formattedTime; 75 | }, $this); 76 | } 77 | 78 | /** 79 | * Builds the options array for file cache configuration. 80 | * @return array 81 | */ 82 | public function build() 83 | { 84 | return $this->validated(); 85 | } 86 | 87 | /** 88 | * Validates the properties and returns an array of options. 89 | * It checks if each property is valid and not null, then adds it to the options 90 | * 91 | * @return array 92 | */ 93 | private function validated() 94 | { 95 | foreach ($this->properties() as $key => $value) { 96 | if ($this->isValidAndNotNull($value)) { 97 | $this->options[$key] = $value; 98 | } 99 | } 100 | return $this->options; 101 | } 102 | 103 | /** 104 | * Checks if the provided data is valid and not null. 105 | * This is used to ensure that only valid options are included in the final configuration. 106 | * 107 | * @param mixed $data 108 | * @return bool 109 | */ 110 | private function isValidAndNotNull(mixed $data) 111 | { 112 | return !empty($data) ? true : false; 113 | } 114 | 115 | /** 116 | * Returns the properties of the FileOptionBuilder instance. 117 | * This method is used to gather the current state of the instance's properties. 118 | * 119 | * @return array 120 | */ 121 | private function properties() 122 | { 123 | $properties = [ 124 | 'cacheDir' => $this->cacheDir, 125 | 'expirationTime' => $this->expirationTime, 126 | 'flushAfter' => $this->flushAfter 127 | ]; 128 | 129 | return $properties; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/CacheStore/CacheManager/FileCacheManager.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Silviooosilva\CacheerPhp 13 | */ 14 | class FileCacheManager 15 | { 16 | 17 | /** 18 | * @param string $dir 19 | * @return void 20 | * @throws CacheFileException 21 | */ 22 | public function createDirectory(string $dir): void 23 | { 24 | if ((!file_exists($dir) || !is_dir($dir)) && !mkdir($dir, 0755, true)) { 25 | throw CacheFileException::create("Could not create directory: {$dir}"); 26 | } 27 | } 28 | 29 | /** 30 | * @param string $filename 31 | * @param string $data 32 | * @return void 33 | * @throws CacheFileException 34 | */ 35 | public function writeFile(string $filename, string $data): void 36 | { 37 | if (!@file_put_contents($filename, $data, LOCK_EX)) { 38 | throw CacheFileException::create("Could not write file: {$filename}"); 39 | } 40 | } 41 | 42 | /** 43 | * @param string $filename 44 | * @return string|bool 45 | * @throws CacheFileException 46 | */ 47 | public function readFile(string $filename): string|bool 48 | { 49 | if (!$this->fileExists($filename)) { 50 | throw CacheFileException::create("File not found: {$filename}"); 51 | } 52 | return file_get_contents($filename); 53 | } 54 | 55 | /** 56 | * @param string $filename 57 | * @return bool 58 | */ 59 | public function fileExists(string $filename): bool 60 | { 61 | return file_exists($filename); 62 | } 63 | 64 | /** 65 | * @param string $filename 66 | * @return void 67 | */ 68 | public function removeFile(string $filename): void 69 | { 70 | if (file_exists($filename)) { 71 | unlink($filename); 72 | } 73 | } 74 | 75 | /** 76 | * @param string $dir 77 | * @return void 78 | */ 79 | public function clearDirectory(string $dir): void 80 | { 81 | $iterator = new RecursiveIteratorIterator( 82 | new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), 83 | RecursiveIteratorIterator::CHILD_FIRST 84 | ); 85 | foreach ($iterator as $file) { 86 | $path = $file->getPathname(); 87 | $file->isDir() ? rmdir($path) : unlink($path); 88 | } 89 | } 90 | 91 | /** 92 | * @param mixed $data 93 | * @param bool $serialize 94 | * @return mixed|string 95 | */ 96 | public function serialize(mixed $data, bool $serialize = true): mixed 97 | { 98 | if($serialize) { 99 | return serialize($data); 100 | } 101 | return unserialize($data); 102 | } 103 | 104 | /** 105 | * @param string $dir 106 | * @return array 107 | * @throws CacheFileException 108 | */ 109 | public function getFilesInDirectory(string $dir): array 110 | { 111 | if (!is_dir($dir)) { 112 | throw CacheFileException::create("Directory does not exist: {$dir}"); 113 | } 114 | 115 | $files = []; 116 | $iterator = new RecursiveIteratorIterator( 117 | new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS) 118 | ); 119 | 120 | foreach ($iterator as $file) { 121 | if ($file->isFile()) { 122 | $files[] = $file->getPathname(); 123 | } 124 | } 125 | 126 | return $files; 127 | } 128 | 129 | /** 130 | * @param string $dir 131 | * @return bool 132 | */ 133 | public function directoryExists(string $dir): bool 134 | { 135 | return is_dir($dir); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Helpers/CacheerHelper.php: -------------------------------------------------------------------------------- 1 | 4 | CacheerPHP Logo 5 |

6 | 7 | [![Maintainer](https://img.shields.io/badge/maintainer-@silviooosilva-blue.svg?style=for-the-badge&color=blue)](https://github.com/silviooosilva) 8 | ![Packagist Dependency Version](https://img.shields.io/packagist/dependency-v/silviooosilva/cacheer-php/PHP?style=for-the-badge&color=blue) 9 | [![Latest Version](https://img.shields.io/github/release/silviooosilva/CacheerPHP.svg?style=for-the-badge&color=blue)](https://github.com/silviooosilva/CacheerPHP/releases) 10 | [![Quality Score](https://img.shields.io/scrutinizer/g/silviooosilva/CacheerPHP.svg?style=for-the-badge&color=blue)](https://scrutinizer-ci.com/g/silviooosilva/CacheerPHP) 11 | ![Packagist Downloads](https://img.shields.io/packagist/dt/silviooosilva/cacheer-php?style=for-the-badge&color=blue) 12 | 13 | CacheerPHP is a minimalist PHP caching library. Version 4 brings a more robust API, optional compression and encryption and support for multiple backends including files, databases and Redis. 14 | 15 | --- 16 | 17 | ## Features 18 | 19 | - **Multiple storage drivers:** file system, databases (MySQL, PostgreSQL, SQLite), Redis and in-memory arrays. 20 | - **Customizable expiration:** define precise TTL (Time To Live) values. 21 | - **Automatic and manual flushing:** clean cache directories with `flushAfter` or on demand. 22 | - **Namespaces:** group entries by category for easier management. 23 | - **Flexible output formatting:** return cached data as JSON, arrays, strings or objects. 24 | - **Compression & encryption:** reduce storage footprint and secure cache contents. 25 | - **OptionBuilder:** fluent builders to configure File, Redis and Database drivers without typos (supports default TTL via `expirationTime` and auto-flush via `flushAfter`). 26 | - **Advanced logging and statistics:** monitor hits/misses and average times (*coming soon*). 27 | 28 | ## Requirements 29 | 30 | - PHP 8.0+ 31 | - Optional extensions: PDO drivers for MySQL, PostgreSQL or SQLite 32 | - Redis server and `predis/predis` if using the Redis driver 33 | 34 | ## Installation 35 | 36 | Install via [Composer](https://getcomposer.org): 37 | 38 | ```sh 39 | composer require silviooosilva/cacheer-php 40 | ``` 41 | 42 | ## Configuration 43 | 44 | Copy the example environment file and adjust the settings for your environment: 45 | 46 | ```sh 47 | cp .env.example .env 48 | ``` 49 | 50 | Environment variables control database and Redis connections. See [Configuration](https://github.com/CacheerPHP/docs/blob/main/en/guides/configuration.md) for the full list. 51 | 52 | ## Quick start 53 | 54 | ```php 55 | require_once __DIR__ . '/vendor/autoload.php'; 56 | 57 | use Silviooosilva\CacheerPhp\Cacheer; 58 | 59 | $key = 'user_profile_1234'; 60 | $value = ['id' => 123, 'name' => 'John Doe']; 61 | 62 | // Configure cache and driver statically 63 | Cacheer::setConfig()->setTimeZone('UTC'); 64 | Cacheer::setDriver()->useArrayDriver(); 65 | 66 | // Static usage with boolean return 67 | Cacheer::putCache($key, $value); 68 | if (Cacheer::has($key)) { 69 | $cached = Cacheer::getCache($key); 70 | var_dump($cached); 71 | } 72 | 73 | // Dynamic usage and isSuccess() 74 | $cache = new Cacheer([ 75 | 'cacheDir' => __DIR__ . '/cache', 76 | ]); 77 | $cache->has($key); 78 | if ($cache->isSuccess()) { 79 | $cached = $cache->getCache($key); 80 | var_dump($cached); 81 | } else { 82 | echo $cache->getMessage(); 83 | } 84 | 85 | // Alternatively, check the state via isSuccess() 86 | $cache->has($key); 87 | if ($cache->isSuccess()) { 88 | $cached = $cache->getCache($key); 89 | var_dump($cached); 90 | } 91 | ``` 92 | 93 | ## Documentation 94 | 95 | Full documentation is available at [CacheerPHP Documentation](https://github.com/CacheerPHP/docs). 96 | 97 | 98 | ## Testing 99 | 100 | After installing dependencies, run the test suite with: 101 | 102 | ```sh 103 | vendor/bin/phpunit 104 | ``` 105 | 106 | ## Contributing 107 | 108 | Contributions are welcome! Please open an issue or submit a pull request on GitHub. 109 | 110 | ## License 111 | 112 | CacheerPHP is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). 113 | 114 | ## Support 115 | 116 | If this project helps you, consider [buying the maintainer a coffee](https://buymeacoffee.com/silviooosilva). 117 |

silviooosilva



118 | -------------------------------------------------------------------------------- /src/Interface/CacheerInterface.php: -------------------------------------------------------------------------------- 1 | 8 | * @package Silviooosilva\CacheerPhp 9 | */ 10 | interface CacheerInterface 11 | { 12 | 13 | /** 14 | * Appends data to an existing cache item. 15 | * 16 | * @param string $cacheKey Unique item key 17 | * @param mixed $cacheData Data to be appended (serializable) 18 | * @param string $namespace Namespace for organization 19 | * @return mixed True on success, false on failure 20 | */ 21 | public function appendCache(string $cacheKey, mixed $cacheData, string $namespace = ''); 22 | 23 | /** 24 | * Clears a specific cache item. 25 | * 26 | * @param string $cacheKey Unique item key 27 | * @param string $namespace Namespace for organization 28 | * @return void 29 | */ 30 | public function clearCache(string $cacheKey, string $namespace = ''); 31 | 32 | /** 33 | * Flushes all cache items. 34 | * 35 | * @return void 36 | */ 37 | public function flushCache(); 38 | 39 | /** 40 | * Gets all items in a specific namespace. 41 | * 42 | * @param string $namespace Namespace for organization 43 | * @return CacheDataFormatter|mixed Returns a formatter with all items in the namespace 44 | */ 45 | public function getAll(string $namespace); 46 | 47 | /** 48 | * Retrieves a single cache item. 49 | * 50 | * @param string $cacheKey Unique item key 51 | * @param string $namespace Namespace for organization 52 | * @param string|int $ttl Lifetime in seconds (default: 3600) 53 | * @return mixed Returns the cached data or null if not found 54 | */ 55 | public function getCache(string $cacheKey, string $namespace = '', string|int $ttl = 3600); 56 | 57 | /** 58 | * Retrieves multiple cache items by their keys. 59 | * 60 | * @param array $cacheKeys Array of item keys 61 | * @param string $namespace Namespace for organization 62 | * @param string|int $ttl Lifetime in seconds (default: 3600) 63 | * @return CacheDataFormatter|mixed Returns a formatter with the retrieved items 64 | */ 65 | public function getMany(array $cacheKeys, string $namespace, string|int $ttl = 3600); 66 | 67 | /** 68 | * Checks if a cache item exists. 69 | * 70 | * @param string $cacheKey Unique item key 71 | * @param string $namespace Namespace for organization 72 | * @return bool True if the item exists, false otherwise 73 | */ 74 | public function has(string $cacheKey, string $namespace = ''): bool; 75 | 76 | /** 77 | * Stores an item in the cache with a specific TTL. 78 | * 79 | * @param string $cacheKey Unique item key 80 | * @param mixed $cacheData Data to be stored (serializable) 81 | * @param string $namespace Namespace for organization 82 | * @param string|int $ttl Lifetime in seconds (default: 3600) 83 | * @return bool True on success, false on failure 84 | */ 85 | public function putCache(string $cacheKey, mixed $cacheData, string $namespace = '', int|string $ttl = 3600); 86 | 87 | /** 88 | * Stores multiple items in the cache. 89 | * 90 | * @param array $items Array of items to be stored, where keys are cache keys and values are cache data 91 | * @param string $namespace Namespace for organization 92 | * @param int $batchSize Number of items to store in each batch (default: 100) 93 | * @return bool True on success, false on failure 94 | */ 95 | public function putMany(array $items, string $namespace = '', int $batchSize = 100); 96 | 97 | /** 98 | * Renews the cache for a specific key with a new TTL. 99 | * 100 | * @param string $cacheKey Unique item key 101 | * @param int|string $ttl Lifetime in seconds (default: 3600) 102 | * @param string $namespace Namespace for organization 103 | * @return bool True on success, false on failure 104 | */ 105 | public function renewCache(string $cacheKey, int | string $ttl, string $namespace = ''); 106 | 107 | /** 108 | * Associates one or more cache keys to a tag. 109 | * 110 | * @param string $tag 111 | * @param string ...$keys One or more cache keys 112 | * @return mixed 113 | */ 114 | public function tag(string $tag, string ...$keys); 115 | 116 | /** 117 | * Flushes all cache items associated with a tag. 118 | * 119 | * @param string $tag 120 | * @return mixed 121 | */ 122 | public function flushTag(string $tag); 123 | } 124 | -------------------------------------------------------------------------------- /src/Core/MigrationManager.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Silviooosilva\CacheerPhp 13 | */ 14 | class MigrationManager 15 | { 16 | /** 17 | * Executes the migration process for the database. 18 | * 19 | * @param PDO $connection 20 | * @return void 21 | */ 22 | public static function migrate(PDO $connection, ?string $tableName = null): void 23 | { 24 | try { 25 | self::prepareDatabase($connection); 26 | $queries = self::getMigrationQueries($connection, $tableName); 27 | foreach ($queries as $query) { 28 | if (trim($query)) { 29 | $connection->exec($query); 30 | } 31 | } 32 | } catch (PDOException $exception) { 33 | throw new PDOException($exception->getMessage(), $exception->getCode()); 34 | } 35 | } 36 | 37 | /** 38 | * Prepares the database connection for migration. 39 | * 40 | * @param PDO $connection 41 | * @return void 42 | */ 43 | private static function prepareDatabase(PDO $connection): void 44 | { 45 | $driver = DatabaseDriver::tryFrom($connection->getAttribute(PDO::ATTR_DRIVER_NAME)); 46 | if ($driver !== DatabaseDriver::SQLITE) { 47 | $dbname = CACHEER_DATABASE_CONFIG[Connect::getConnection()->value]['dbname']; 48 | $connection->exec("USE $dbname"); 49 | } 50 | } 51 | 52 | /** 53 | * Generates the SQL queries needed for the migration based on the database driver. 54 | * 55 | * @param PDO $connection 56 | * @return array 57 | */ 58 | private static function getMigrationQueries(PDO $connection, ?string $tableName = null): array 59 | { 60 | $driver = DatabaseDriver::tryFrom($connection->getAttribute(PDO::ATTR_DRIVER_NAME)); 61 | $createdAtDefault = ($driver === DatabaseDriver::PGSQL) ? 'DEFAULT NOW()' : 'DEFAULT CURRENT_TIMESTAMP'; 62 | $table = $tableName ?: (defined('CACHEER_TABLE') ? CACHEER_TABLE : 'cacheer_table'); 63 | 64 | if ($driver === DatabaseDriver::SQLITE) { 65 | $query = " 66 | CREATE TABLE IF NOT EXISTS {$table} ( 67 | id INTEGER PRIMARY KEY AUTOINCREMENT, 68 | cacheKey VARCHAR(255) NOT NULL, 69 | cacheData TEXT NOT NULL, 70 | cacheNamespace VARCHAR(255), 71 | expirationTime DATETIME NOT NULL, 72 | created_at DATETIME $createdAtDefault, 73 | UNIQUE(cacheKey, cacheNamespace) 74 | ); 75 | CREATE INDEX IF NOT EXISTS idx_{$table}_cacheKey ON {$table} (cacheKey); 76 | CREATE INDEX IF NOT EXISTS idx_{$table}_cacheNamespace ON {$table} (cacheNamespace); 77 | CREATE INDEX IF NOT EXISTS idx_{$table}_expirationTime ON {$table} (expirationTime); 78 | CREATE INDEX IF NOT EXISTS idx_{$table}_key_namespace ON {$table} (cacheKey, cacheNamespace); 79 | "; 80 | } elseif ($driver === DatabaseDriver::PGSQL) { 81 | $query = " 82 | CREATE TABLE IF NOT EXISTS {$table} ( 83 | id SERIAL PRIMARY KEY, 84 | cacheKey VARCHAR(255) NOT NULL, 85 | cacheData TEXT NOT NULL, 86 | cacheNamespace VARCHAR(255), 87 | expirationTime TIMESTAMP NOT NULL, 88 | created_at TIMESTAMP $createdAtDefault, 89 | UNIQUE(cacheKey, cacheNamespace) 90 | ); 91 | CREATE INDEX IF NOT EXISTS idx_{$table}_cacheKey ON {$table} (cacheKey); 92 | CREATE INDEX IF NOT EXISTS idx_{$table}_cacheNamespace ON {$table} (cacheNamespace); 93 | CREATE INDEX IF NOT EXISTS idx_{$table}_expirationTime ON {$table} (expirationTime); 94 | CREATE INDEX IF NOT EXISTS idx_{$table}_key_namespace ON {$table} (cacheKey, cacheNamespace); 95 | "; 96 | } else { 97 | $query = " 98 | CREATE TABLE IF NOT EXISTS {$table} ( 99 | id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, 100 | cacheKey VARCHAR(255) NOT NULL, 101 | cacheData LONGTEXT NOT NULL, 102 | cacheNamespace VARCHAR(255) NULL, 103 | expirationTime DATETIME NOT NULL, 104 | created_at TIMESTAMP $createdAtDefault, 105 | UNIQUE KEY unique_cache_key_namespace (cacheKey, cacheNamespace), 106 | KEY idx_{$table}_cacheKey (cacheKey), 107 | KEY idx_{$table}_cacheNamespace (cacheNamespace), 108 | KEY idx_{$table}_expirationTime (expirationTime), 109 | KEY idx_{$table}_key_namespace (cacheKey, cacheNamespace) 110 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 111 | "; 112 | } 113 | return array_filter(array_map('trim', explode(';', $query))); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tests/Feature/FileCacheStoreFeatureTest.php: -------------------------------------------------------------------------------- 1 | cacheDir = __DIR__ . '/cache'; 22 | if (!file_exists($this->cacheDir) || !is_dir($this->cacheDir)) { 23 | mkdir($this->cacheDir, 0755, true); 24 | } 25 | $this->cache = new Cacheer(['cacheDir' => $this->cacheDir]); 26 | } 27 | 28 | protected function tearDown(): void 29 | { 30 | $this->cache->flushCache(); 31 | } 32 | 33 | public function testAutoFlush() 34 | { 35 | $options = [ 36 | 'cacheDir' => $this->cacheDir, 37 | 'flushAfter' => '10 seconds' 38 | ]; 39 | 40 | $this->cache = new Cacheer($options); 41 | $this->cache->putCache('test_key', 'test_data'); 42 | 43 | $this->assertEquals('test_data', $this->cache->getCache('test_key')); 44 | $this->assertTrue($this->cache->isSuccess()); 45 | 46 | sleep(11); 47 | 48 | $this->cache = new Cacheer($options); 49 | $this->cache->getCache('test_key'); 50 | $this->assertFalse($this->cache->isSuccess()); 51 | } 52 | 53 | public function testDataOutputShouldBeOfTypeJson() 54 | { 55 | $this->cache = new Cacheer(['cacheDir' => $this->cacheDir], true); 56 | $this->cache->putCache('key_json', 'data_json'); 57 | $output = $this->cache->getCache('key_json')->toJson(); 58 | $this->assertJson($output); 59 | } 60 | 61 | public function testDataOutputShouldBeOfTypeArray() 62 | { 63 | $this->cache = new Cacheer(['cacheDir' => $this->cacheDir], true); 64 | $this->cache->putCache('key_array', 'data_array'); 65 | $output = $this->cache->getCache('key_array')->toArray(); 66 | $this->assertIsArray($output); 67 | } 68 | 69 | public function testDataOutputShouldBeOfTypeObject() 70 | { 71 | $this->cache = new Cacheer(['cacheDir' => $this->cacheDir], true); 72 | $this->cache->putCache('key_object', ['id' => 123]); 73 | $output = $this->cache->getCache('key_object')->toObject(); 74 | $this->assertIsObject($output); 75 | } 76 | 77 | public function testPutMany() 78 | { 79 | $cacheer = new Cacheer(['cacheDir' => __DIR__ . '/cache']); 80 | $items = [ 81 | [ 82 | 'cacheKey' => 'user_1_profile', 83 | 'cacheData' => ['name' => 'John Doe', 'email' => 'john@example.com'] 84 | ], 85 | [ 86 | 'cacheKey' => 'user_2_profile', 87 | 'cacheData' => ['name' => 'Jane Doe', 'email' => 'jane@example.com'] 88 | ], 89 | ]; 90 | 91 | $cacheer->putMany($items); 92 | 93 | foreach ($items as $item) { 94 | $this->assertEquals($item['cacheData'], $cacheer->getCache($item['cacheKey'])); 95 | } 96 | } 97 | 98 | public function test_remember_saves_and_recover_values() 99 | { 100 | $this->cache->flushCache(); 101 | $value = $this->cache->remember('remember_test_key', 60, function () { 102 | return 'valor_teste'; 103 | }); 104 | $this->assertEquals('valor_teste', $value); 105 | $cachedValue = $this->cache->remember('remember_test_key', 60, function(){ 106 | return 'novo_valor'; 107 | }); 108 | $this->assertEquals('valor_teste', $cachedValue); 109 | } 110 | 111 | public function test_remember_forever_saves_value_indefinitely() 112 | { 113 | $this->cache->flushCache(); 114 | $value = $this->cache->rememberForever('remember_forever_key', function () { 115 | return 'valor_eterno'; 116 | }); 117 | $this->assertEquals('valor_eterno', $value); 118 | $cachedValue = $this->cache->rememberForever('remember_forever_key', function () { 119 | return 'novo_valor'; 120 | }); 121 | $this->assertEquals('valor_eterno', $cachedValue); 122 | } 123 | 124 | public function test_get_and_forget() 125 | { 126 | $this->cache->putCache('cache_key_test', 10); 127 | $data = $this->cache->getAndForget('cache_key_test'); 128 | $this->assertEquals(10, $data); 129 | $this->assertNull($this->cache->getAndForget('cache_key_test')); 130 | } 131 | 132 | public function test_store_if_not_present_with_add_function() 133 | { 134 | $this->cache->putCache('cache_key_test', 'existent_data'); 135 | $this->assertTrue($this->cache->add('cache_key_test', 100)); 136 | $this->assertFalse($this->cache->add('non_existent_key', 'non_existent_data')); 137 | } 138 | 139 | public function test_increment_and_decrement_functions() 140 | { 141 | $this->cache->putCache('num_key', 2025); 142 | $this->cache->increment('num_key', 2); 143 | $this->assertEquals(2027, $this->cache->getCache('num_key')); 144 | $this->cache->decrement('num_key', 2); 145 | $this->assertEquals(2025, $this->cache->getCache('num_key')); 146 | } 147 | 148 | public function test_get_many_cache_items() 149 | { 150 | $items = ['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3']; 151 | foreach ($items as $k => $v) { $this->cache->putCache($k, $v); } 152 | $retrieved = $this->cache->getMany(array_keys($items)); 153 | $this->assertEquals($items, $retrieved); 154 | } 155 | 156 | public function test_get_all_cache_items() 157 | { 158 | $items = ['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3']; 159 | foreach ($items as $k => $v) { $this->cache->putCache($k, $v); } 160 | $retrieved = $this->cache->getAll(); 161 | $this->assertCount(3, $retrieved); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/Service/CacheRetriever.php: -------------------------------------------------------------------------------- 1 | 15 | * @package Silviooosilva\CacheerPhp 16 | */ 17 | class CacheRetriever 18 | { 19 | /** 20 | * @var Cacheer 21 | */ 22 | private Cacheer $cacheer; 23 | 24 | /** 25 | * @var int 26 | */ 27 | private int $foreverTTL = CacheTimeConstants::CACHE_FOREVER_TTL->value; 28 | 29 | /** 30 | * CacheRetriever constructor. 31 | * 32 | * @param Cacheer $cacheer 33 | */ 34 | public function __construct(Cacheer $cacheer) 35 | { 36 | $this->cacheer = $cacheer; 37 | } 38 | 39 | /** 40 | * Retrieves a cache item by its key. 41 | * 42 | * @param string $cacheKey 43 | * @param string $namespace 44 | * @param int|string $ttl 45 | * @return mixed 46 | * @throws CacheFileException 47 | */ 48 | public function getCache(string $cacheKey, string $namespace = '', int|string $ttl = 3600): mixed 49 | { 50 | $cacheData = $this->cacheer->cacheStore->getCache($cacheKey, $namespace, $ttl); 51 | $this->cacheer->syncState(); 52 | 53 | if ($this->cacheer->isSuccess() && ($this->cacheer->isCompressionEnabled() || $this->cacheer->getEncryptionKey() !== null)) { 54 | $cacheData = CacheerHelper::recoverFromStorage($cacheData, $this->cacheer->isCompressionEnabled(), $this->cacheer->getEncryptionKey()); 55 | } 56 | 57 | return $this->cacheer->isFormatted() ? new CacheDataFormatter($cacheData) : $cacheData; 58 | } 59 | 60 | /** 61 | * Retrieves multiple cache items by their keys. 62 | * 63 | * @param array $cacheKeys 64 | * @param string $namespace 65 | * @param int|string $ttl 66 | * @return array|CacheDataFormatter 67 | * @throws CacheFileException 68 | */ 69 | public function getMany(array $cacheKeys, string $namespace = '', int|string $ttl = 3600): array|CacheDataFormatter 70 | { 71 | $cachedData = $this->cacheer->cacheStore->getMany($cacheKeys, $namespace, $ttl); 72 | return $this->getCachedDatum($cachedData); 73 | } 74 | 75 | /** 76 | * Retrieves all cache items in a namespace. 77 | * 78 | * @param string $namespace 79 | * @return CacheDataFormatter|mixed 80 | * @throws CacheFileException 81 | */ 82 | public function getAll(string $namespace = ''): mixed 83 | { 84 | $cachedData = $this->cacheer->cacheStore->getAll($namespace); 85 | return $this->getCachedDatum($cachedData); 86 | } 87 | 88 | /** 89 | * Retrieves a cache item, deletes it, and returns its data. 90 | * 91 | * @param string $cacheKey 92 | * @param string $namespace 93 | * @return mixed|null 94 | * @throws CacheFileException 95 | */ 96 | public function getAndForget(string $cacheKey, string $namespace = ''): mixed 97 | { 98 | $cachedData = $this->getCache($cacheKey, $namespace); 99 | 100 | if (!empty($cachedData)) { 101 | $this->cacheer->setInternalState("Cache retrieved and deleted successfully!", true); 102 | $this->cacheer->clearCache($cacheKey, $namespace); 103 | return $cachedData; 104 | } 105 | 106 | return null; 107 | } 108 | 109 | /** 110 | * Retrieves a cache item, or executes a callback to store it if not found. 111 | * 112 | * @param string $cacheKey 113 | * @param int|string $ttl 114 | * @param Closure $callback 115 | * @return mixed 116 | * @throws CacheFileException 117 | */ 118 | public function remember(string $cacheKey, int|string $ttl, Closure $callback): mixed 119 | { 120 | $cachedData = $this->getCache($cacheKey, ttl: $ttl); 121 | 122 | if (!empty($cachedData)) { 123 | return $cachedData; 124 | } 125 | 126 | $cacheData = $callback(); 127 | $this->cacheer->putCache($cacheKey, $cacheData, ttl: $ttl); 128 | return $cacheData; 129 | } 130 | 131 | /** 132 | * Retrieves a cache item indefinitely, or executes a callback to store it if not found. 133 | * 134 | * @param string $cacheKey 135 | * @param Closure $callback 136 | * @return mixed 137 | * @throws CacheFileException 138 | */ 139 | public function rememberForever(string $cacheKey, Closure $callback): mixed 140 | { 141 | return $this->remember($cacheKey, $this->foreverTTL, $callback); 142 | } 143 | 144 | /** 145 | * Checks if a cache item exists. 146 | * 147 | * @param string $cacheKey 148 | * @param string $namespace 149 | * @return bool 150 | * @throws CacheFileException 151 | */ 152 | public function has(string $cacheKey, string $namespace = ''): bool 153 | { 154 | $result = $this->cacheer->cacheStore->has($cacheKey, $namespace); 155 | $this->cacheer->syncState(); 156 | 157 | return $result; 158 | } 159 | 160 | /** 161 | * Processes cached data for retrieval. 162 | * 163 | * @param mixed $cachedData 164 | * @return mixed|CacheDataFormatter 165 | */ 166 | public function getCachedDatum(mixed $cachedData): mixed 167 | { 168 | $this->cacheer->syncState(); 169 | 170 | if ($this->cacheer->isSuccess() && ($this->cacheer->isCompressionEnabled() || $this->cacheer->getEncryptionKey() !== null)) { 171 | foreach ($cachedData as &$data) { 172 | $data = CacheerHelper::recoverFromStorage($data, $this->cacheer->isCompressionEnabled(), $this->cacheer->getEncryptionKey()); 173 | } 174 | } 175 | 176 | return $this->cacheer->isFormatted() ? new CacheDataFormatter($cachedData) : $cachedData; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/Service/CacheMutator.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Silviooosilva\CacheerPhp 13 | */ 14 | class CacheMutator 15 | { 16 | /** 17 | * @var Cacheer 18 | */ 19 | private Cacheer $cacheer; 20 | 21 | /** 22 | * @var int 23 | */ 24 | private int $foreverTTL = CacheTimeConstants::CACHE_FOREVER_TTL->value; 25 | 26 | /** 27 | * CacheMutator constructor. 28 | * 29 | * @param Cacheer $cacheer 30 | */ 31 | public function __construct(Cacheer $cacheer) 32 | { 33 | $this->cacheer = $cacheer; 34 | } 35 | 36 | /** 37 | * Adds a cache item if it does not already exist. 38 | * 39 | * @param string $cacheKey 40 | * @param mixed $cacheData 41 | * @param string $namespace 42 | * @param int|string $ttl 43 | * @return bool 44 | */ 45 | public function add(string $cacheKey, mixed $cacheData, string $namespace = '', int|string $ttl = 3600): bool 46 | { 47 | if (!empty($this->cacheer->getCache($cacheKey, $namespace))) { 48 | return true; 49 | } 50 | 51 | $this->putCache($cacheKey, $cacheData, $namespace, $ttl); 52 | $this->cacheer->setInternalState($this->cacheer->getMessage(), $this->cacheer->isSuccess()); 53 | 54 | return false; 55 | } 56 | 57 | /** 58 | * Appends data to an existing cache item. 59 | * 60 | * @param string $cacheKey 61 | * @param mixed $cacheData 62 | * @param string $namespace 63 | * @return bool 64 | */ 65 | public function appendCache(string $cacheKey, mixed $cacheData, string $namespace = ''): bool 66 | { 67 | $this->cacheer->cacheStore->appendCache($cacheKey, $cacheData, $namespace); 68 | $this->cacheer->syncState(); 69 | 70 | return $this->cacheer->isSuccess(); 71 | } 72 | 73 | /** 74 | * Clears a specific cache item. 75 | * 76 | * @param string $cacheKey 77 | * @param string $namespace 78 | * @return bool 79 | */ 80 | public function clearCache(string $cacheKey, string $namespace = ''): bool 81 | { 82 | $this->cacheer->cacheStore->clearCache($cacheKey, $namespace); 83 | $this->cacheer->syncState(); 84 | 85 | return $this->cacheer->isSuccess(); 86 | } 87 | 88 | /** 89 | * Decrements a numeric cache item by a specified amount. 90 | * 91 | * @param string $cacheKey 92 | * @param int $amount 93 | * @param string $namespace 94 | * @return bool 95 | */ 96 | public function decrement(string $cacheKey, int $amount = 1, string $namespace = ''): bool 97 | { 98 | return $this->increment($cacheKey, ($amount * -1), $namespace); 99 | } 100 | 101 | /** 102 | * Stores a cache item forever. 103 | * 104 | * @param string $cacheKey 105 | * @param mixed $cacheData 106 | * @return bool 107 | */ 108 | public function forever(string $cacheKey, mixed $cacheData): bool 109 | { 110 | $this->putCache($cacheKey, $cacheData, ttl: $this->foreverTTL); 111 | $this->cacheer->setInternalState($this->cacheer->getMessage(), $this->cacheer->isSuccess()); 112 | 113 | return $this->cacheer->isSuccess(); 114 | } 115 | 116 | /** 117 | * Flushes the entire cache. 118 | * 119 | * @return bool 120 | */ 121 | public function flushCache(): bool 122 | { 123 | $this->cacheer->cacheStore->flushCache(); 124 | $this->cacheer->syncState(); 125 | 126 | return $this->cacheer->isSuccess(); 127 | } 128 | 129 | /** 130 | * Increments a numeric cache item by a specified amount. 131 | * 132 | * @param string $cacheKey 133 | * @param int $amount 134 | * @param string $namespace 135 | * @return bool 136 | */ 137 | public function increment(string $cacheKey, int $amount = 1, string $namespace = ''): bool 138 | { 139 | $cacheData = $this->cacheer->getCache($cacheKey, $namespace); 140 | 141 | if (!empty($cacheData) && is_numeric($cacheData)) { 142 | $this->putCache($cacheKey, (int)($cacheData + $amount), $namespace); 143 | $this->cacheer->setInternalState($this->cacheer->getMessage(), $this->cacheer->isSuccess()); 144 | return true; 145 | } 146 | 147 | return false; 148 | } 149 | 150 | /** 151 | * Puts a cache item into the cache store. 152 | * 153 | * @param string $cacheKey 154 | * @param mixed $cacheData 155 | * @param string $namespace 156 | * @param int|string $ttl 157 | * @return bool 158 | */ 159 | public function putCache(string $cacheKey, mixed $cacheData, string $namespace = '', int|string $ttl = 3600): bool 160 | { 161 | $data = CacheerHelper::prepareForStorage($cacheData, $this->cacheer->isCompressionEnabled(), $this->cacheer->getEncryptionKey()); 162 | $this->cacheer->cacheStore->putCache($cacheKey, $data, $namespace, $ttl); 163 | $this->cacheer->syncState(); 164 | 165 | return $this->cacheer->isSuccess(); 166 | } 167 | 168 | /** 169 | * Puts multiple cache items in a batch. 170 | * 171 | * @param array $items 172 | * @param string $namespace 173 | * @param int $batchSize 174 | * @return bool 175 | */ 176 | public function putMany(array $items, string $namespace = '', int $batchSize = 100): bool 177 | { 178 | $this->cacheer->cacheStore->putMany($items, $namespace, $batchSize); 179 | $this->cacheer->syncState(); 180 | 181 | return $this->cacheer->isSuccess(); 182 | } 183 | 184 | /** 185 | * Renews the cache item with a new TTL. 186 | * 187 | * @param string $cacheKey 188 | * @param int|string $ttl 189 | * @param string $namespace 190 | * @return bool 191 | */ 192 | public function renewCache(string $cacheKey, int|string $ttl = 3600, string $namespace = ''): bool 193 | { 194 | $this->cacheer->cacheStore->renewCache($cacheKey, $ttl, $namespace); 195 | $this->cacheer->syncState(); 196 | 197 | return $this->cacheer->isSuccess(); 198 | } 199 | 200 | /** 201 | * Associates keys to a tag in the current driver. 202 | * 203 | * @param string $tag 204 | * @param string ...$keys 205 | * @return bool 206 | */ 207 | public function tag(string $tag, string ...$keys): bool 208 | { 209 | $this->cacheer->cacheStore->tag($tag, ...$keys); 210 | $this->cacheer->syncState(); 211 | return $this->cacheer->isSuccess(); 212 | } 213 | 214 | /** 215 | * Flushes all keys associated with a tag in the current driver. 216 | * 217 | * @param string $tag 218 | * @return bool 219 | */ 220 | public function flushTag(string $tag): bool 221 | { 222 | $this->cacheer->cacheStore->flushTag($tag); 223 | $this->cacheer->syncState(); 224 | return $this->cacheer->isSuccess(); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /tests/Unit/FileCacheStoreTest.php: -------------------------------------------------------------------------------- 1 | cacheDir = __DIR__ . '/cache'; 16 | if (!file_exists($this->cacheDir) || !is_dir($this->cacheDir)) { 17 | mkdir($this->cacheDir, 0755, true); 18 | } 19 | 20 | $options = [ 21 | 'cacheDir' => $this->cacheDir, 22 | ]; 23 | 24 | $this->cache = new Cacheer($options); 25 | } 26 | 27 | protected function tearDown(): void 28 | { 29 | $this->cache->flushCache(); 30 | } 31 | 32 | public function testPutCache() 33 | { 34 | $cacheKey = 'test_key'; 35 | $data = 'test_data'; 36 | 37 | $this->cache->putCache($cacheKey, $data); 38 | $this->assertTrue($this->cache->isSuccess()); 39 | $this->assertEquals('Cache file created successfully', $this->cache->getMessage()); 40 | 41 | $cacheFile = $this->cacheDir . '/' . md5($cacheKey) . '.cache'; 42 | $this->assertFileExists($cacheFile); 43 | $this->assertEquals($data, $this->cache->getCache($cacheKey)); 44 | } 45 | 46 | public function testGetCache() 47 | { 48 | $cacheKey = 'test_key'; 49 | $data = 'test_data'; 50 | 51 | $this->cache->putCache($cacheKey, $data); 52 | 53 | $cachedData = $this->cache->getCache($cacheKey); 54 | $this->assertTrue($this->cache->isSuccess()); 55 | $this->assertEquals($data, $cachedData); 56 | 57 | // Recuperar cache fora do período de expiração 58 | sleep(2); 59 | $this->cache->getCache($cacheKey, '', '2 seconds'); 60 | $this->assertFalse($this->cache->isSuccess()); 61 | $this->assertEquals('cacheFile not found, does not exists or expired', $this->cache->getMessage()); 62 | } 63 | 64 | public function testClearCache() 65 | { 66 | $cacheKey = 'test_key'; 67 | $data = 'test_data'; 68 | 69 | $this->cache->putCache($cacheKey, $data); 70 | $this->cache->clearCache($cacheKey); 71 | 72 | $this->assertTrue($this->cache->isSuccess()); 73 | $this->assertEquals('Cache file deleted successfully!', $this->cache->getMessage()); 74 | 75 | $cacheFile = $this->cacheDir . '/' . md5($cacheKey) . '.cache'; 76 | $this->assertFileDoesNotExist($cacheFile); 77 | } 78 | 79 | public function testFlushCache() 80 | { 81 | $key1 = 'test_key1'; 82 | $data1 = 'test_data1'; 83 | 84 | $key2 = 'test_key2'; 85 | $data2 = 'test_data2'; 86 | 87 | $this->cache->putCache($key1, $data1); 88 | $this->cache->putCache($key2, $data2); 89 | $this->cache->flushCache(); 90 | 91 | $cacheFile1 = $this->cacheDir . '/' . md5($key1) . '.cache'; 92 | $cacheFile2 = $this->cacheDir . '/' . md5($key2) . '.cache'; 93 | 94 | $this->assertFileDoesNotExist($cacheFile1); 95 | $this->assertFileDoesNotExist($cacheFile2); 96 | } 97 | 98 | 99 | private function removeDirectoryRecursively($dir) 100 | { 101 | if (!is_dir($dir)) { 102 | return; 103 | } 104 | $items = scandir($dir); 105 | foreach ($items as $item) { 106 | if ($item === '.' || $item === '..') { 107 | continue; 108 | } 109 | $path = $dir . DIRECTORY_SEPARATOR . $item; 110 | if (is_dir($path)) { 111 | $this->removeDirectoryRecursively($path); 112 | } else { 113 | unlink($path); 114 | } 115 | } 116 | rmdir($dir); 117 | } 118 | 119 | public function testUseDefaultDriverCreatesCacheDirInProjectRoot() 120 | { 121 | $cacheer = new Cacheer(); 122 | $driver = new CacheDriver($cacheer); 123 | 124 | $projectRoot = EnvHelper::getRootPath(); 125 | $expectedCacheDir = $projectRoot . DIRECTORY_SEPARATOR . "CacheerPHP" . DIRECTORY_SEPARATOR . "Cache"; 126 | 127 | if (is_dir($expectedCacheDir)) { 128 | $this->removeDirectoryRecursively($expectedCacheDir); 129 | } 130 | 131 | $driver->useDefaultDriver(); 132 | 133 | $this->assertDirectoryExists($expectedCacheDir); 134 | 135 | if (is_dir($expectedCacheDir)) { 136 | $this->removeDirectoryRecursively($expectedCacheDir); 137 | } 138 | } 139 | 140 | public function testPutCacheWithNamespace() 141 | { 142 | $cacheKey = 'namespace_key'; 143 | $data = 'namespace_data'; 144 | $namespace = 'my_namespace'; 145 | 146 | $this->cache->putCache($cacheKey, $data, $namespace); 147 | $this->assertTrue($this->cache->isSuccess()); 148 | 149 | $cachedData = $this->cache->getCache($cacheKey, $namespace); 150 | $this->assertEquals($data, $cachedData); 151 | } 152 | 153 | public function testClearCacheWithNamespace() 154 | { 155 | $cacheKey = 'namespace_key_clear'; 156 | $data = 'namespace_data_clear'; 157 | $namespace = 'clear_namespace'; 158 | 159 | $this->cache->putCache($cacheKey, $data, $namespace); 160 | $this->assertTrue($this->cache->isSuccess()); 161 | 162 | $this->cache->clearCache($cacheKey, $namespace); 163 | $this->assertTrue($this->cache->isSuccess()); 164 | 165 | $cachedData = $this->cache->getCache($cacheKey, $namespace); 166 | $this->assertFalse($this->cache->isSuccess()); 167 | $this->assertNull($cachedData); 168 | } 169 | 170 | public function testFlushCacheRemovesNamespacedFiles() 171 | { 172 | $cacheKey = 'ns_flush_key'; 173 | $data = 'ns_flush_data'; 174 | $namespace = 'flush_namespace'; 175 | 176 | $this->cache->putCache($cacheKey, $data, $namespace); 177 | $this->assertTrue($this->cache->isSuccess()); 178 | 179 | $this->cache->flushCache(); 180 | 181 | $cachedData = $this->cache->getCache($cacheKey, $namespace); 182 | $this->assertFalse($this->cache->isSuccess()); 183 | $this->assertNull($cachedData); 184 | } 185 | 186 | public function testAppendCacheWithDifferentTypes() 187 | { 188 | $cacheKey = 'append_type_key'; 189 | $initialData = ['a' => 1]; 190 | $additionalData = ['b' => 2]; 191 | $expectedData = ['a' => 1, 'b' => 2]; 192 | 193 | $this->cache->putCache($cacheKey, $initialData); 194 | $this->cache->appendCache($cacheKey, $additionalData); 195 | $this->assertEquals($expectedData, $this->cache->getCache($cacheKey)); 196 | 197 | $this->cache->appendCache($cacheKey, ['c' => 'string']); 198 | $expectedData['c'] = 'string'; 199 | $this->assertEquals($expectedData, $this->cache->getCache($cacheKey)); 200 | } 201 | 202 | public function test_tag_and_flush_tag_in_file_driver() 203 | { 204 | $k1 = 'tag_key_1'; 205 | $k2 = 'tag_key_2'; 206 | $this->cache->putCache($k1, 'v1'); 207 | $this->cache->putCache($k2, 'v2'); 208 | 209 | $ok = $this->cache->tag('groupA', $k1, $k2); 210 | $this->assertTrue($ok); 211 | $this->assertTrue($this->cache->isSuccess()); 212 | 213 | $this->cache->flushTag('groupA'); 214 | $this->assertTrue($this->cache->isSuccess()); 215 | 216 | $this->assertNull($this->cache->getCache($k1)); 217 | $this->assertNull($this->cache->getCache($k2)); 218 | } 219 | 220 | public function test_tag_with_namespace_and_flush_tag_in_file_driver() 221 | { 222 | $ns = 'nsA'; 223 | $k1 = 'k1'; 224 | $k2 = 'k2'; 225 | $this->cache->putCache($k1, 'v1', $ns); 226 | $this->cache->putCache($k2, 'v2', $ns); 227 | 228 | $ok = $this->cache->tag('groupNS', $ns . ':' . $k1, $ns . ':' . $k2); 229 | $this->assertTrue($ok); 230 | 231 | $this->cache->flushTag('groupNS'); 232 | $this->assertNull($this->cache->getCache($k1, $ns)); 233 | $this->assertNull($this->cache->getCache($k2, $ns)); 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /src/Repositories/CacheDatabaseRepository.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Silviooosilva\CacheerPhp 13 | */ 14 | class CacheDatabaseRepository 15 | { 16 | 17 | /** @var ?PDO */ 18 | private ?PDO $connection = null; 19 | 20 | /** @var string */ 21 | private string $table = 'cacheer_table'; 22 | 23 | 24 | /** 25 | * CacheDatabaseRepository constructor. 26 | * Initializes the database connection using the Connect class. 27 | * 28 | */ 29 | public function __construct(string $table = 'cacheer_table') 30 | { 31 | $this->connection = Connect::getInstance(); 32 | $this->table = $table ?: 'cacheer_table'; 33 | } 34 | 35 | 36 | /** 37 | * Stores cache data in the database. 38 | * 39 | * @param string $cacheKey 40 | * @param mixed $cacheData 41 | * @param string $namespace 42 | * @param string|int $ttl 43 | * @return bool 44 | */ 45 | public function store(string $cacheKey, mixed $cacheData, string $namespace, string|int $ttl = 3600): bool 46 | { 47 | if (!empty($this->retrieve($cacheKey, $namespace))) { 48 | return $this->update($cacheKey, $cacheData, $namespace); 49 | } 50 | 51 | $expirationTime = date('Y-m-d H:i:s', time() + $ttl); 52 | $createdAt = date('Y-m-d H:i:s'); 53 | 54 | $stmt = $this->connection->prepare( 55 | "INSERT INTO {$this->table} (cacheKey, cacheData, cacheNamespace, expirationTime, created_at) 56 | VALUES (:cacheKey, :cacheData, :namespace, :expirationTime, :createdAt)" 57 | ); 58 | $stmt->bindValue(':cacheKey', $cacheKey); 59 | $stmt->bindValue(':cacheData', $this->serialize($cacheData)); 60 | $stmt->bindValue(':namespace', $namespace); 61 | $stmt->bindValue(':expirationTime', $expirationTime); 62 | $stmt->bindValue(':createdAt', $createdAt); 63 | 64 | return $stmt->execute() && $stmt->rowCount() > 0; 65 | } 66 | 67 | /** 68 | * Retrieves cache data from the database. 69 | * 70 | * @param string $cacheKey 71 | * @param string $namespace 72 | * @return mixed 73 | */ 74 | public function retrieve(string $cacheKey, string $namespace = ''): mixed 75 | { 76 | $driver = $this->getDriver(); 77 | $nowFunction = $this->getCurrentDateTime($driver); 78 | 79 | $stmt = $this->connection->prepare( 80 | "SELECT cacheData FROM {$this->table} 81 | WHERE cacheKey = :cacheKey AND cacheNamespace = :namespace AND expirationTime > $nowFunction 82 | LIMIT 1" 83 | ); 84 | $stmt->bindValue(':cacheKey', $cacheKey); 85 | $stmt->bindValue(':namespace', $namespace); 86 | $stmt->execute(); 87 | 88 | $data = $stmt->fetch(PDO::FETCH_ASSOC); 89 | return (!empty($data)) ? $this->serialize($data['cacheData'], false) : null; 90 | } 91 | 92 | /** 93 | * Retrieves multiple cache items by their keys. 94 | * @param string $namespace 95 | * @return array 96 | */ 97 | public function getAll(string $namespace = ''): array 98 | { 99 | $driver = $this->getDriver(); 100 | $nowFunction = $this->getCurrentDateTime($driver); 101 | 102 | $stmt = $this->connection->prepare( 103 | "SELECT cacheKey, cacheData FROM {$this->table} 104 | WHERE cacheNamespace = :namespace AND expirationTime > $nowFunction" 105 | ); 106 | $stmt->bindValue(':namespace', $namespace); 107 | $stmt->execute(); 108 | 109 | $results = []; 110 | while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { 111 | $results[$row['cacheKey']] = $this->serialize($row['cacheData'], false); 112 | } 113 | return $results; 114 | } 115 | 116 | /** 117 | * Get Update query based on the database driver. 118 | * 119 | * @return string 120 | */ 121 | private function getUpdateQueryWithDriver(): string 122 | { 123 | $driver = $this->getDriver(); 124 | if ($driver?->isMysqlFamily()) { 125 | return "UPDATE {$this->table} SET cacheData = :cacheData, cacheNamespace = :namespace WHERE cacheKey = :cacheKey LIMIT 1"; 126 | } 127 | return "UPDATE {$this->table} SET cacheData = :cacheData, cacheNamespace = :namespace WHERE cacheKey = :cacheKey"; 128 | } 129 | 130 | /** 131 | * Get Delete query based on the database driver. 132 | * 133 | * @return string 134 | */ 135 | private function getDeleteQueryWithDriver(): string 136 | { 137 | $driver = $this->getDriver(); 138 | if ($driver?->isMysqlFamily()) { 139 | return "DELETE FROM {$this->table} WHERE cacheKey = :cacheKey AND cacheNamespace = :namespace LIMIT 1"; 140 | } 141 | return "DELETE FROM {$this->table} WHERE cacheKey = :cacheKey AND cacheNamespace = :namespace"; 142 | } 143 | 144 | /** 145 | * Updates an existing cache item in the database. 146 | * 147 | * @param string $cacheKey 148 | * @param mixed $cacheData 149 | * @param string $namespace 150 | * @return bool 151 | */ 152 | public function update(string $cacheKey, mixed $cacheData, string $namespace = ''): bool 153 | { 154 | $query = $this->getUpdateQueryWithDriver(); 155 | $stmt = $this->connection->prepare($query); 156 | $stmt->bindValue(':cacheData', $this->serialize($cacheData)); 157 | $stmt->bindValue(':namespace', $namespace); 158 | $stmt->bindValue(':cacheKey', $cacheKey); 159 | $stmt->execute(); 160 | 161 | return $stmt->rowCount() > 0; 162 | } 163 | 164 | /** 165 | * Clears a specific cache item from the database. 166 | * 167 | * @param string $cacheKey 168 | * @param string $namespace 169 | * @return bool 170 | */ 171 | public function clear(string $cacheKey, string $namespace = ''): bool 172 | { 173 | $query = $this->getDeleteQueryWithDriver(); 174 | $stmt = $this->connection->prepare($query); 175 | $stmt->bindValue(':cacheKey', $cacheKey); 176 | $stmt->bindValue(':namespace', $namespace); 177 | $stmt->execute(); 178 | 179 | return $stmt->rowCount() > 0; 180 | } 181 | 182 | /** 183 | * Gets the query to renew the expiration time of a cache item based on the database driver. 184 | * 185 | * @return string 186 | */ 187 | private function getRenewExpirationQueryWithDriver(): string 188 | { 189 | $driver = $this->getDriver(); 190 | if ($driver === DatabaseDriver::SQLITE) { 191 | return "UPDATE {$this->table} 192 | SET expirationTime = DATETIME(expirationTime, '+' || :ttl || ' seconds') 193 | WHERE cacheKey = :cacheKey AND cacheNamespace = :namespace AND expirationTime > :currentTime"; 194 | } 195 | return "UPDATE {$this->table} 196 | SET expirationTime = DATE_ADD(expirationTime, INTERVAL :ttl SECOND) 197 | WHERE cacheKey = :cacheKey AND cacheNamespace = :namespace AND expirationTime > :currentTime"; 198 | } 199 | 200 | /** 201 | * Checks if a cache item is valid based on its key, namespace, and current time. 202 | * 203 | * @param string $cacheKey 204 | * @param string $namespace 205 | * @param string $currentTime 206 | * @return bool 207 | */ 208 | private function hasValidCache(string $cacheKey, string $namespace, string $currentTime): bool 209 | { 210 | $stmt = $this->connection->prepare( 211 | "SELECT 1 FROM {$this->table} 212 | WHERE cacheKey = :cacheKey AND cacheNamespace = :namespace AND expirationTime > :currentTime 213 | LIMIT 1" 214 | ); 215 | $stmt->bindValue(':cacheKey', $cacheKey); 216 | $stmt->bindValue(':namespace', $namespace); 217 | $stmt->bindValue(':currentTime', $currentTime); 218 | $stmt->execute(); 219 | return $stmt->fetchColumn() !== false; 220 | } 221 | 222 | /** 223 | * Renews the expiration time of a cache item. 224 | * 225 | * @param string $cacheKey 226 | * @param string|int $ttl 227 | * @param string $namespace 228 | * @return bool 229 | */ 230 | public function renew(string $cacheKey, string|int $ttl, string $namespace = ''): bool 231 | { 232 | $currentTime = date('Y-m-d H:i:s'); 233 | if (!$this->hasValidCache($cacheKey, $namespace, $currentTime)) { 234 | return false; 235 | } 236 | 237 | $query = $this->getRenewExpirationQueryWithDriver(); 238 | $stmt = $this->connection->prepare($query); 239 | $stmt->bindValue(':ttl', (int) $ttl, PDO::PARAM_INT); 240 | $stmt->bindValue(':cacheKey', $cacheKey); 241 | $stmt->bindValue(':namespace', $namespace); 242 | $stmt->bindValue(':currentTime', $currentTime); 243 | $stmt->execute(); 244 | 245 | return $stmt->rowCount() > 0; 246 | } 247 | 248 | /** 249 | * Flushes all cache items from the database. 250 | * 251 | * @return bool 252 | */ 253 | public function flush(): bool 254 | { 255 | return $this->connection->exec("DELETE FROM {$this->table}") !== false; 256 | } 257 | 258 | /** 259 | * Serializes or unserializes data based on the provided flag. 260 | * 261 | * @param mixed $data 262 | * @param bool $serialize 263 | * @return mixed 264 | */ 265 | private function serialize(mixed $data, bool $serialize = true): mixed 266 | { 267 | return $serialize ? serialize($data) : unserialize($data); 268 | } 269 | 270 | /** 271 | * Gets the current date and time based on the database driver. 272 | * 273 | * @param string $driver 274 | * @return string 275 | */ 276 | private function getCurrentDateTime(?DatabaseDriver $driver): string 277 | { 278 | return ($driver === DatabaseDriver::SQLITE) ? "DATETIME('now', 'localtime')" : "NOW()"; 279 | } 280 | 281 | /** 282 | * Resolve the current PDO driver as an enum instance when possible. 283 | */ 284 | private function getDriver(): ?DatabaseDriver 285 | { 286 | $driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME); 287 | return is_string($driverName) ? DatabaseDriver::tryFrom($driverName) : null; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/Cacheer.php: -------------------------------------------------------------------------------- 1 | 22 | * @package Silviooosilva\CacheerPhp 23 | * 24 | * @method static bool add(string $cacheKey, mixed $cacheData, string $namespace = '', int|string $ttl = 3600) 25 | * @method bool add(string $cacheKey, mixed $cacheData, string $namespace = '', int|string $ttl = 3600) 26 | * @method static bool appendCache(string $cacheKey, mixed $cacheData, string $namespace = '') 27 | * @method bool appendCache(string $cacheKey, mixed $cacheData, string $namespace = '') 28 | * @method static bool clearCache(string $cacheKey, string $namespace = '') 29 | * @method bool clearCache(string $cacheKey, string $namespace = '') 30 | * @method static bool decrement(string $cacheKey, int $amount = 1, string $namespace = '') 31 | * @method bool decrement(string $cacheKey, int $amount = 1, string $namespace = '') 32 | * @method static bool flushCache() 33 | * @method bool flushCache() 34 | * @method static bool forever(string $cacheKey, mixed $cacheData) 35 | * @method bool forever(string $cacheKey, mixed $cacheData) 36 | * @method static mixed getAndForget(string $cacheKey, string $namespace = '') 37 | * @method mixed getAndForget(string $cacheKey, string $namespace = '') 38 | * @method static CacheDataFormatter|mixed getAll(string $namespace = '') 39 | * @method CacheDataFormatter|mixed getAll(string $namespace = '') 40 | * @method static mixed getCache(string $cacheKey, string $namespace = '', int|string $ttl = 3600) 41 | * @method mixed getCache(string $cacheKey, string $namespace = '', int|string $ttl = 3600) 42 | * @method static array|CacheDataFormatter getMany(array $cacheKeys, string $namespace = '', int|string $ttl = 3600) 43 | * @method array|CacheDataFormatter getMany(array $cacheKeys, string $namespace = '', int|string $ttl = 3600) 44 | * @method static getOptions(): array 45 | * @method getOptions(): array 46 | * @method static bool has(string $cacheKey, string $namespace = '') 47 | * @method bool has(string $cacheKey, string $namespace = '') 48 | * @method static bool increment(string $cacheKey, int $amount = 1, string $namespace = '') 49 | * @method bool increment(string $cacheKey, int $amount = 1, string $namespace = '') 50 | * @method static bool putCache(string $cacheKey, mixed $cacheData, string $namespace = '', int|string $ttl = 3600) 51 | * @method bool putCache(string $cacheKey, mixed $cacheData, string $namespace = '', int|string $ttl = 3600) 52 | * @method static bool putMany(array $items, string $namespace = '', int $batchSize = 100) 53 | * @method bool putMany(array $items, string $namespace = '', int $batchSize = 100) 54 | * @method static bool tag(string $tag, string ...$keys) 55 | * @method bool tag(string $tag, string ...$keys) 56 | * @method static bool flushTag(string $tag) 57 | * @method bool flushTag(string $tag) 58 | * @method static mixed remember(string $cacheKey, int|string $ttl, Closure $callback) 59 | * @method mixed remember(string $cacheKey, int|string $ttl, Closure $callback) 60 | * @method static mixed rememberForever(string $cacheKey, Closure $callback) 61 | * @method mixed rememberForever(string $cacheKey, Closure $callback) 62 | * @method static bool renewCache(string $cacheKey, int|string $ttl = 3600, string $namespace = '') 63 | * @method bool renewCache(string $cacheKey, int|string $ttl = 3600, string $namespace = '') 64 | * @method static setConfig(): CacheConfig 65 | * @method setConfig(): CacheConfig 66 | * @method static setDriver(): CacheDriver 67 | * @method setDriver(): CacheDriver 68 | * @method static setUp(array $options): void 69 | * @method setUp(array $options): void 70 | */ 71 | final class Cacheer 72 | { 73 | /** 74 | * @var string 75 | */ 76 | private string $message; 77 | 78 | /** 79 | * @var boolean 80 | */ 81 | private bool $success; 82 | 83 | /** 84 | * @var boolean 85 | */ 86 | private bool $formatted = false; 87 | 88 | /** 89 | * @var bool 90 | */ 91 | private bool $compression = false; 92 | 93 | /** 94 | * @var string|null 95 | */ 96 | private ?string $encryptionKey = null; 97 | 98 | /** 99 | * @var FileCacheStore|DatabaseCacheStore|RedisCacheStore|ArrayCacheStore 100 | */ 101 | public RedisCacheStore|DatabaseCacheStore|ArrayCacheStore|FileCacheStore $cacheStore; 102 | 103 | /** 104 | * @var array 105 | */ 106 | public array $options = []; 107 | 108 | /** 109 | * @var CacheRetriever 110 | */ 111 | private CacheRetriever $retriever; 112 | 113 | /** 114 | * @var CacheMutator 115 | */ 116 | private CacheMutator $mutator; 117 | 118 | /** 119 | * @var CacheConfig 120 | */ 121 | private CacheConfig $config; 122 | 123 | /** 124 | * @var Cacheer|null 125 | */ 126 | private static ?Cacheer $staticInstance = null; 127 | 128 | /** 129 | * Cacheer constructor. 130 | * 131 | * @param array $options 132 | * @param bool $formatted 133 | * @throws RuntimeException|Exceptions\CacheFileException 134 | */ 135 | public function __construct(array $options = [], bool $formatted = false) 136 | { 137 | $this->formatted = $formatted; 138 | $this->validateOptions($options); 139 | $this->retriever = new CacheRetriever($this); 140 | $this->mutator = new CacheMutator($this); 141 | $this->config = new CacheConfig($this); 142 | $this->setDriver()->useDefaultDriver(); 143 | } 144 | 145 | /** 146 | * Dynamically handle calls to missing instance methods. 147 | * 148 | * @param string $method 149 | * @param array $parameters 150 | * @return mixed 151 | * @throws BadMethodCallException 152 | */ 153 | public function __call(string $method, array $parameters): mixed 154 | { 155 | if ($method === 'setConfig') { 156 | return new CacheConfig($this); 157 | } 158 | 159 | if ($method === 'setDriver') { 160 | return new CacheDriver($this); 161 | } 162 | 163 | $delegates = [$this->mutator, $this->retriever, $this->config]; 164 | 165 | foreach ($delegates as $delegate) { 166 | if (method_exists($delegate, $method)) { 167 | return $delegate->{$method}(...$parameters); 168 | } 169 | } 170 | 171 | throw new BadMethodCallException("Method {$method} does not exist"); 172 | } 173 | 174 | /** 175 | * Handle dynamic static calls by routing them through an instance. 176 | * 177 | * @param string $method 178 | * @param array $parameters 179 | * @return mixed 180 | */ 181 | public static function __callStatic(string $method, array $parameters): mixed 182 | { 183 | $instance = self::instance(); 184 | 185 | if ($instance === null) { 186 | throw new \RuntimeException("Cacheer static instance is not initialized."); 187 | } 188 | 189 | return $instance->__call($method, $parameters); 190 | } 191 | 192 | /** 193 | * Enable encryption for cached data 194 | * 195 | * @param string $key 196 | * @return $this 197 | */ 198 | public function useEncryption(string $key): Cacheer 199 | { 200 | $this->encryptionKey = $key; 201 | return $this; 202 | } 203 | 204 | /** 205 | * Enable or disable data compression 206 | * 207 | * @param bool $status 208 | * @return $this 209 | */ 210 | public function useCompression(bool $status = true): Cacheer 211 | { 212 | $this->compression = $status; 213 | return $this; 214 | } 215 | 216 | /** 217 | * Enables or disables the formatter for cache data. 218 | * 219 | * @return void 220 | */ 221 | public function useFormatter(): void 222 | { 223 | $this->formatted = !$this->formatted; 224 | } 225 | 226 | /** 227 | * Validates the options provided for the Cacheer instance. 228 | * 229 | * @param array $options 230 | * @return void 231 | */ 232 | private function validateOptions(array $options): void 233 | { 234 | $this->options = $options; 235 | } 236 | 237 | /** 238 | * Checks if the last operation was successful. 239 | * 240 | * @return bool 241 | */ 242 | public function isSuccess(): bool 243 | { 244 | return $this->success; 245 | } 246 | 247 | /** 248 | * Sets a message for the cache operation. 249 | * 250 | * @param string $message 251 | * @param boolean $success 252 | * @return void 253 | */ 254 | private function setMessage(string $message, bool $success): void 255 | { 256 | $this->message = $message; 257 | $this->success = $success; 258 | } 259 | 260 | /** 261 | * Retrieves the message from the last operation. 262 | * 263 | * @return string 264 | */ 265 | public function getMessage(): string 266 | { 267 | return $this->message; 268 | } 269 | 270 | /** 271 | * @return void 272 | */ 273 | public function syncState(): void 274 | { 275 | $this->setMessage($this->cacheStore->getMessage(), $this->cacheStore->isSuccess()); 276 | } 277 | 278 | /** 279 | * @param string $message 280 | * @param bool $success 281 | * @return void 282 | */ 283 | public function setInternalState(string $message, bool $success): void 284 | { 285 | $this->setMessage($message, $success); 286 | } 287 | 288 | /** 289 | * @return bool 290 | */ 291 | public function isFormatted(): bool 292 | { 293 | return $this->formatted; 294 | } 295 | 296 | /** 297 | * @return bool 298 | */ 299 | public function isCompressionEnabled(): bool 300 | { 301 | return $this->compression; 302 | } 303 | 304 | /** 305 | * @return string|null 306 | */ 307 | public function getEncryptionKey(): ?string 308 | { 309 | return $this->encryptionKey; 310 | } 311 | 312 | /** 313 | * Get or create the shared Cacheer instance for static calls. 314 | * 315 | * @return Cacheer 316 | */ 317 | private static function instance(): Cacheer 318 | { 319 | if (self::$staticInstance === null) { 320 | self::$staticInstance = new self(); 321 | } 322 | return self::$staticInstance; 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /tests/Unit/ArrayCacheStoreTest.php: -------------------------------------------------------------------------------- 1 | cache = new Cacheer(); 15 | $this->cache->setDriver()->useArrayDriver(); 16 | $this->cache->setConfig()->setTimeZone('America/Toronto'); 17 | } 18 | 19 | protected function tearDown(): void 20 | { 21 | $this->cache->flushCache(); 22 | } 23 | 24 | public function testUsingArrayDriverSetsProperInstance() 25 | { 26 | $this->assertInstanceOf(ArrayCacheStore::class, $this->cache->cacheStore); 27 | } 28 | 29 | public function testPutAndGetCacheInArray() 30 | { 31 | $cacheKey = 'test_key'; 32 | $cacheData = ['name' => 'John Doe', 'email' => 'john@example.com']; 33 | 34 | $this->cache->putCache($cacheKey, $cacheData, '', 3600); 35 | 36 | $result = $this->cache->getCache($cacheKey); 37 | 38 | $this->assertNotEmpty($result); 39 | $this->assertEquals($cacheData, $result); 40 | } 41 | 42 | public function testExpiredCacheInArray() 43 | { 44 | $cacheKey = 'expired_key'; 45 | $cacheData = ['name' => 'Expired User', 'email' => 'expired@example.com']; 46 | 47 | $this->cache->putCache($cacheKey, $cacheData, '', 1); 48 | sleep(3); 49 | 50 | $this->assertEquals("Cache stored successfully", $this->cache->getMessage()); 51 | $this->assertEmpty($this->cache->getCache($cacheKey)); 52 | $this->assertFalse($this->cache->isSuccess()); 53 | } 54 | 55 | 56 | public function testOverwriteExistingCacheInArray() 57 | { 58 | $cacheKey = 'overwrite_key'; 59 | $initialCacheData = ['name' => 'Initial Data', 'email' => 'initial@example.com']; 60 | $newCacheData = ['name' => 'New Data', 'email' => 'new@example.com']; 61 | 62 | $this->cache->putCache($cacheKey, $initialCacheData); 63 | $this->assertEquals("Cache stored successfully", $this->cache->getMessage()); 64 | 65 | $this->cache->appendCache($cacheKey, $newCacheData); 66 | $this->assertEquals("Cache appended successfully", $this->cache->getMessage()); 67 | $this->assertEquals($newCacheData, $this->cache->getCache($cacheKey)); 68 | } 69 | 70 | 71 | public function testPutManyCacheItemsInArray() 72 | { 73 | $items = [ 74 | [ 75 | 'cacheKey' => 'user_1_profile', 76 | 'cacheData' => [ 77 | ['name' => 'John Doe', 'email' => 'john@example.com'], 78 | ['name' => 'John Doe', 'email' => 'john@example.com'], 79 | ['name' => 'John Doe', 'email' => 'john@example.com'], 80 | ['name' => 'John Doe', 'email' => 'john@example.com'] 81 | ] 82 | ], 83 | [ 84 | 'cacheKey' => 'user_2_profile', 85 | 'cacheData' => [ 86 | ['name' => 'Jane Doe', 'email' => 'jane@example.com'], 87 | ['name' => 'Jane Doe', 'email' => 'jane@example.com'], 88 | ['name' => 'Jane Doe', 'email' => 'jane@example.com'], 89 | ['name' => 'Jane Doe', 'email' => 'jane@example.com'] 90 | ] 91 | ] 92 | ]; 93 | 94 | $this->cache->putMany($items); 95 | foreach ($items as $item) { 96 | 97 | $this->assertEquals($item['cacheData'], $this->cache->getCache($item['cacheKey'])); 98 | } 99 | } 100 | 101 | public function testHasCacheFromArray() 102 | { 103 | $cacheKey = 'test_key'; 104 | $cacheData = ['name' => 'Sílvio Silva', 'role' => 'Developer']; 105 | 106 | $this->cache->putCache($cacheKey, $cacheData); 107 | 108 | $this->assertEquals("Cache stored successfully", $this->cache->getMessage()); 109 | $this->assertTrue($this->cache->isSuccess()); 110 | 111 | $this->cache->has($cacheKey); 112 | $this->assertTrue($this->cache->isSuccess()); 113 | } 114 | 115 | public function testRenewCacheFromArray() 116 | { 117 | $cacheKey = 'expired_key'; 118 | $cacheData = ['name' => 'Expired User', 'email' => 'expired@example.com']; 119 | 120 | // Define TTL de 10 seg para que a chave ainda exista quando renovarmos 121 | $this->cache->putCache($cacheKey, $cacheData, '', 120); 122 | $this->assertEquals("Cache stored successfully", $this->cache->getMessage()); 123 | sleep(2); 124 | 125 | // Verifica que a chave existe antes de renovar 126 | $this->assertNotEmpty($this->cache->getCache($cacheKey)); 127 | 128 | $this->cache->renewCache($cacheKey, 7200); 129 | $this->assertTrue($this->cache->isSuccess()); 130 | $this->assertNotEmpty($this->cache->getCache($cacheKey)); 131 | } 132 | 133 | public function testRenewCacheWithNamespaceFromArray() 134 | { 135 | $cacheKey = 'expired_key'; 136 | $namespace = 'expired_namespace'; 137 | $cacheData = ['name' => 'Expired User', 'email' => 'expired@example.com']; 138 | 139 | $this->cache->putCache($cacheKey, $cacheData, $namespace, 120); 140 | $this->assertEquals("Cache stored successfully", $this->cache->getMessage()); 141 | sleep(2); 142 | 143 | $this->assertNotEmpty($this->cache->getCache($cacheKey, $namespace)); 144 | 145 | $this->cache->renewCache($cacheKey, 7200, $namespace); 146 | $this->assertTrue($this->cache->isSuccess()); 147 | $this->assertNotEmpty($this->cache->getCache($cacheKey, $namespace)); 148 | } 149 | 150 | public function testClearCacheDataFromArray() 151 | { 152 | $cacheKey = 'test_key'; 153 | $data = 'test_data'; 154 | 155 | $this->cache->putCache($cacheKey, $data); 156 | $this->assertEquals("Cache stored successfully", $this->cache->getMessage()); 157 | 158 | $this->cache->clearCache($cacheKey); 159 | $this->assertTrue($this->cache->isSuccess()); 160 | $this->assertEquals("Cache cleared successfully", $this->cache->getMessage()); 161 | 162 | $this->assertEmpty($this->cache->getCache($cacheKey)); 163 | } 164 | 165 | public function testFlushCacheDataFromArray() 166 | { 167 | $key1 = 'test_key1'; 168 | $data1 = 'test_data1'; 169 | 170 | $key2 = 'test_key2'; 171 | $data2 = 'test_data2'; 172 | 173 | $this->cache->putCache($key1, $data1); 174 | $this->cache->putCache($key2, $data2); 175 | $this->assertTrue($this->cache->isSuccess()); 176 | $this->assertTrue($this->cache->isSuccess()); 177 | 178 | $this->cache->flushCache(); 179 | 180 | $this->assertTrue($this->cache->isSuccess()); 181 | $this->assertEquals("Cache flushed successfully", $this->cache->getMessage()); 182 | } 183 | 184 | public function test_remember_saves_and_recover_values() 185 | { 186 | $this->cache->flushCache(); 187 | 188 | $value = $this->cache->remember('remember_test_key', 60, function () { 189 | return 'valor_teste'; 190 | }); 191 | 192 | $this->assertEquals('valor_teste', $value); 193 | 194 | $cachedValue = $this->cache->remember('remember_test_key', 60, function (){ 195 | return 'novo_valor'; 196 | }); 197 | 198 | 199 | $this->assertEquals('valor_teste', $cachedValue); 200 | } 201 | 202 | public function test_remember_forever_saves_value_indefinitely() 203 | { 204 | $this->cache->flushCache(); 205 | 206 | $value = $this->cache->rememberForever('remember_forever_key', function () { 207 | return 'valor_eterno'; 208 | }); 209 | $this->assertEquals('valor_eterno', $value); 210 | 211 | $cachedValue = $this->cache->rememberForever('remember_forever_key', function () { 212 | return 'novo_valor'; 213 | }); 214 | 215 | $this->assertEquals('valor_eterno', $cachedValue); 216 | } 217 | 218 | 219 | public function test_get_and_forget() 220 | { 221 | $cacheKey = 'cache_key_test'; 222 | $this->cache->putCache($cacheKey, 10); 223 | 224 | $this->assertTrue($this->cache->isSuccess()); 225 | 226 | $cacheData = $this->cache->getAndForget($cacheKey); 227 | 228 | $this->assertTrue($this->cache->isSuccess()); 229 | $this->assertEquals(10, $cacheData); 230 | 231 | $oldCacheData = $this->cache->getAndForget($cacheKey); 232 | 233 | $this->assertNull($oldCacheData); 234 | $this->assertFalse($this->cache->isSuccess()); 235 | 236 | $noCacheData = $this->cache->getAndForget('non_existent_cache_key'); 237 | $this->assertNull($noCacheData); 238 | } 239 | 240 | public function test_store_if_not_present_with_add_function() 241 | { 242 | $existentKey = 'cache_key_test'; 243 | 244 | $nonExistentKey = 'non_existent_key'; 245 | 246 | $this->cache->putCache($existentKey, 'existent_data'); 247 | 248 | $this->assertTrue($this->cache->isSuccess()); 249 | $this->assertEquals('existent_data', $this->cache->getCache($existentKey)); 250 | 251 | $addCache = $this->cache->add($existentKey, 100); 252 | 253 | $this->assertTrue($addCache); 254 | $this->assertNotEquals(100, 'existent_data'); 255 | 256 | $addNonExistentKey = $this->cache->add($nonExistentKey, 'non_existent_data'); 257 | 258 | $this->assertFalse($addNonExistentKey); 259 | $this->assertEquals('non_existent_data', $this->cache->getCache($nonExistentKey)); 260 | $this->assertTrue($this->cache->isSuccess()); 261 | 262 | } 263 | 264 | public function test_increment_function() { 265 | 266 | $cacheKey = 'test_increment'; 267 | $cacheData = 2025; 268 | 269 | $this->cache->putCache($cacheKey, $cacheData); 270 | 271 | $this->assertTrue($this->cache->isSuccess()); 272 | $this->assertEquals($cacheData, $this->cache->getCache($cacheKey)); 273 | $this->assertIsNumeric($this->cache->getCache($cacheKey)); 274 | 275 | $increment = $this->cache->increment($cacheKey, 2); 276 | $this->assertTrue($increment); 277 | 278 | $this->assertEquals(2027, $this->cache->getCache($cacheKey)); 279 | 280 | } 281 | 282 | public function test_decrement_function() { 283 | 284 | $cacheKey = 'test_decrement'; 285 | $cacheData = 2027; 286 | 287 | $this->cache->putCache($cacheKey, $cacheData); 288 | 289 | $this->assertTrue($this->cache->isSuccess()); 290 | $this->assertEquals($cacheData, $this->cache->getCache($cacheKey)); 291 | $this->assertIsNumeric($this->cache->getCache($cacheKey)); 292 | 293 | $increment = $this->cache->decrement($cacheKey, 2); 294 | $this->assertTrue($increment); 295 | 296 | $this->assertEquals(2025, $this->cache->getCache($cacheKey)); 297 | 298 | } 299 | 300 | public function test_get_many_cache_items() 301 | { 302 | $cacheItems = [ 303 | 'key1' => 'value1', 304 | 'key2' => 'value2', 305 | 'key3' => 'value3' 306 | ]; 307 | foreach ($cacheItems as $key => $value) { 308 | $this->cache->putCache($key, $value); 309 | } 310 | $this->assertTrue($this->cache->isSuccess()); 311 | $retrievedItems = $this->cache->getMany(array_keys($cacheItems)); 312 | $this->assertTrue($this->cache->isSuccess()); 313 | $this->assertCount(3, $retrievedItems); 314 | $this->assertEquals($cacheItems, $retrievedItems); 315 | } 316 | 317 | public function test_get_all_cache_items() 318 | { 319 | $namespace = 'test_namespace'; 320 | $cacheKey = 'test_key'; 321 | $cacheData = ['foo' => 'bar']; 322 | 323 | $this->cache->putCache($cacheKey, $cacheData, $namespace); 324 | $this->assertTrue($this->cache->isSuccess()); 325 | $retrievedItems = $this->cache->getAll($namespace); 326 | $this->assertTrue($this->cache->isSuccess()); 327 | 328 | $expectedKey = $namespace . ':' . $cacheKey; 329 | $this->assertCount(1, $retrievedItems); 330 | $this->assertArrayHasKey($expectedKey, $retrievedItems); 331 | $this->assertEquals($cacheData, $retrievedItems[$expectedKey]); 332 | } 333 | 334 | public function test_tag_and_flush_tag_in_array_driver() 335 | { 336 | $k1 = 'tag_key_1'; 337 | $k2 = 'tag_key_2'; 338 | $this->cache->putCache($k1, 'v1'); 339 | $this->cache->putCache($k2, 'v2'); 340 | 341 | $ok = $this->cache->tag('groupA', $k1, $k2); 342 | $this->assertTrue($ok); 343 | $this->assertTrue($this->cache->isSuccess()); 344 | 345 | $this->cache->flushTag('groupA'); 346 | $this->assertTrue($this->cache->isSuccess()); 347 | 348 | $this->assertEmpty($this->cache->getCache($k1)); 349 | $this->assertEmpty($this->cache->getCache($k2)); 350 | } 351 | 352 | public function test_tag_with_namespace_and_flush_tag_in_array_driver() 353 | { 354 | $ns = 'nsA'; 355 | $k1 = 'k1'; 356 | $k2 = 'k2'; 357 | $this->cache->putCache($k1, 'v1', $ns); 358 | $this->cache->putCache($k2, 'v2', $ns); 359 | 360 | $ok = $this->cache->tag('groupNS', $ns . ':' . $k1, $ns . ':' . $k2); 361 | $this->assertTrue($ok); 362 | 363 | $this->cache->flushTag('groupNS'); 364 | $this->assertEmpty($this->cache->getCache($k1, $ns)); 365 | $this->assertEmpty($this->cache->getCache($k2, $ns)); 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/CacheStore/ArrayCacheStore.php: -------------------------------------------------------------------------------- 1 | 11 | * @package Silviooosilva\CacheerPhp 12 | */ 13 | class ArrayCacheStore implements CacheerInterface 14 | { 15 | 16 | /** 17 | * @param array $arrayStore 18 | */ 19 | private array $arrayStore = []; 20 | 21 | /** 22 | * @var boolean 23 | */ 24 | private bool $success = false; 25 | 26 | /** 27 | * @var string 28 | */ 29 | private string $message = ''; 30 | 31 | /** 32 | * @var ?CacheLogger 33 | */ 34 | private ?CacheLogger $logger = null; 35 | 36 | /** 37 | * @var array> 38 | */ 39 | private array $tags = []; 40 | 41 | /** 42 | * ArrayCacheStore constructor. 43 | * 44 | * @param string $logPath 45 | */ 46 | public function __construct(string $logPath) 47 | { 48 | $this->logger = new CacheLogger($logPath); 49 | } 50 | 51 | /** 52 | * Appends data to an existing cache item. 53 | * 54 | * @param string $cacheKey 55 | * @param mixed $cacheData 56 | * @param string $namespace 57 | * @return bool 58 | */ 59 | public function appendCache(string $cacheKey, mixed $cacheData, string $namespace = ''): bool 60 | { 61 | $arrayStoreKey = $this->buildArrayKey($cacheKey, $namespace); 62 | 63 | if (!$this->has($cacheKey, $namespace)) { 64 | $this->setMessage("cacheData can't be appended, because doesn't exist or expired", false); 65 | $this->logger->debug("{$this->getMessage()} from array driver."); 66 | return false; 67 | } 68 | 69 | $this->arrayStore[$arrayStoreKey]['cacheData'] = serialize($cacheData); 70 | $this->setMessage("Cache appended successfully", true); 71 | return true; 72 | } 73 | 74 | /** 75 | * Builds a unique key for the array store. 76 | * 77 | * @param string $cacheKey 78 | * @param string $namespace 79 | * @return string 80 | */ 81 | private function buildArrayKey(string $cacheKey, string $namespace = ''): string 82 | { 83 | return !empty($namespace) ? ($namespace . ':' . $cacheKey) : $cacheKey; 84 | } 85 | 86 | /** 87 | * Clears a specific cache item. 88 | * 89 | * @param string $cacheKey 90 | * @param string $namespace 91 | * @return void 92 | */ 93 | public function clearCache(string $cacheKey, string $namespace = ''): void 94 | { 95 | $arrayStoreKey = $this->buildArrayKey($cacheKey, $namespace); 96 | unset($this->arrayStore[$arrayStoreKey]); 97 | $this->setMessage("Cache cleared successfully", true); 98 | $this->logger->debug("{$this->getMessage()} from array driver."); 99 | } 100 | 101 | /** 102 | * Decrements a cache item by a specified amount. 103 | * 104 | * @param string $cacheKey 105 | * @param int $amount 106 | * @param string $namespace 107 | * @return bool 108 | */ 109 | public function decrement(string $cacheKey, int $amount = 1, string $namespace = ''): bool 110 | { 111 | return $this->increment($cacheKey, ($amount * -1), $namespace); 112 | } 113 | 114 | /** 115 | * Flushes all cache items. 116 | * 117 | * @return void 118 | */ 119 | public function flushCache(): void 120 | { 121 | unset($this->arrayStore); 122 | $this->arrayStore = []; 123 | $this->tags = []; 124 | $this->setMessage("Cache flushed successfully", true); 125 | $this->logger->debug("{$this->getMessage()} from array driver."); 126 | } 127 | 128 | /** 129 | * Stores a cache item permanently. 130 | * 131 | * @param string $cacheKey 132 | * @param mixed $cacheData 133 | * @return void 134 | */ 135 | public function forever(string $cacheKey, mixed $cacheData): void 136 | { 137 | $this->putCache($cacheKey, $cacheData, ttl: 31536000 * 1000); 138 | $this->setMessage($this->getMessage(), $this->isSuccess()); 139 | } 140 | 141 | /** 142 | * Retrieves a single cache item. 143 | * 144 | * @param string $cacheKey 145 | * @param string $namespace 146 | * @param int|string $ttl 147 | * @return mixed 148 | */ 149 | public function getCache(string $cacheKey, string $namespace = '', string|int $ttl = 3600): mixed 150 | { 151 | $arrayStoreKey = $this->buildArrayKey($cacheKey, $namespace); 152 | 153 | if (!$this->has($cacheKey, $namespace)) { 154 | $this->handleCacheNotFound(); 155 | return false; 156 | } 157 | 158 | $cacheData = $this->arrayStore[$arrayStoreKey]; 159 | if ($this->isExpired($cacheData)) { 160 | $this->handleCacheExpired($arrayStoreKey); 161 | return false; 162 | } 163 | 164 | $this->setMessage("Cache retrieved successfully", true); 165 | $this->logger->debug("{$this->getMessage()} from array driver."); 166 | return $this->serialize($cacheData['cacheData'], false); 167 | } 168 | 169 | /** 170 | * Verify if the cache is expired. 171 | * 172 | * @param array $cacheData 173 | * @return bool 174 | */ 175 | private function isExpired(array $cacheData): bool 176 | { 177 | $expirationTime = $cacheData['expirationTime'] ?? 0; 178 | $now = time(); 179 | return $expirationTime !== 0 && $now >= $expirationTime; 180 | } 181 | 182 | /** 183 | * Handles the case when cache data is not found. 184 | * 185 | * @return void 186 | */ 187 | private function handleCacheNotFound(): void 188 | { 189 | $this->setMessage("cacheData not found, does not exists or expired", false); 190 | $this->logger->debug("{$this->getMessage()} from array driver."); 191 | } 192 | 193 | /** 194 | * Handles the case when cache data has expired. 195 | * 196 | * @param string $arrayStoreKey 197 | * @return void 198 | */ 199 | private function handleCacheExpired(string $arrayStoreKey): void 200 | { 201 | $parts = explode(':', $arrayStoreKey, 2); 202 | if (count($parts) === 2) { 203 | list($np, $key) = $parts; 204 | } else { 205 | $np = ''; 206 | $key = $arrayStoreKey; 207 | } 208 | $this->clearCache($key, $np); 209 | $this->setMessage("cacheKey: {$key} has expired.", false); 210 | $this->logger->debug("{$this->getMessage()} from array driver."); 211 | } 212 | 213 | /** 214 | * Gets all items in a specific namespace. 215 | * 216 | * @param string $namespace 217 | * @return array 218 | */ 219 | public function getAll(string $namespace = ''): array 220 | { 221 | $results = []; 222 | foreach ($this->arrayStore as $key => $data) { 223 | if (str_starts_with($key, $namespace . ':') || empty($namespace)) { 224 | $results[$key] = $this->serialize($data['cacheData'], false); 225 | } 226 | } 227 | return $results; 228 | } 229 | 230 | /** 231 | * Retrieves multiple cache items by their keys. 232 | * 233 | * @param array $cacheKeys 234 | * @param string $namespace 235 | * @param string|int $ttl 236 | * @return array 237 | */ 238 | public function getMany(array $cacheKeys, string $namespace = '', string|int $ttl = 3600): array 239 | { 240 | $results = []; 241 | foreach ($cacheKeys as $cacheKey) { 242 | $results[$cacheKey] = $this->getCache($cacheKey, $namespace, $ttl); 243 | } 244 | return $results; 245 | } 246 | 247 | /** 248 | * Checks if a cache item exists. 249 | * 250 | * @param string $cacheKey 251 | * @param string $namespace 252 | * @return bool 253 | */ 254 | public function has(string $cacheKey, string $namespace = ''): bool 255 | { 256 | $arrayStoreKey = $this->buildArrayKey($cacheKey, $namespace); 257 | $exists = isset($this->arrayStore[$arrayStoreKey]) && time() < $this->arrayStore[$arrayStoreKey]['expirationTime']; 258 | 259 | $this->setMessage( 260 | $exists ? "Cache key: {$cacheKey} exists and it's available!" : "Cache key: {$cacheKey} does not exist or it's expired!", 261 | $exists 262 | ); 263 | $this->logger->debug("{$this->getMessage()} from array driver."); 264 | 265 | return $exists; 266 | } 267 | 268 | /** 269 | * Increments a cache item by a specified amount. 270 | * 271 | * @param string $cacheKey 272 | * @param int $amount 273 | * @param string $namespace 274 | * @return bool 275 | */ 276 | public function increment(string $cacheKey, int $amount = 1, string $namespace = ''): bool 277 | { 278 | $cacheData = $this->getCache($cacheKey, $namespace); 279 | 280 | if(!empty($cacheData) && is_numeric($cacheData)) { 281 | $this->putCache($cacheKey, (int)($cacheData + $amount), $namespace); 282 | $this->setMessage($this->getMessage(), $this->isSuccess()); 283 | return true; 284 | } 285 | 286 | return false; 287 | } 288 | 289 | /** 290 | * Checks if the operation was successful. 291 | * 292 | * @return boolean 293 | */ 294 | public function isSuccess(): bool 295 | { 296 | return $this->success; 297 | } 298 | 299 | /** 300 | * Gets the last message. 301 | * 302 | * @return string 303 | */ 304 | public function getMessage(): string 305 | { 306 | return $this->message; 307 | } 308 | 309 | /** 310 | * Stores an item in the cache with a specific TTL. 311 | * 312 | * @param string $cacheKey 313 | * @param mixed $cacheData 314 | * @param string $namespace 315 | * @param int|string $ttl 316 | * @return bool 317 | */ 318 | public function putCache(string $cacheKey, mixed $cacheData, string $namespace = '', int|string $ttl = 3600): bool 319 | { 320 | $arrayStoreKey = $this->buildArrayKey($cacheKey, $namespace); 321 | 322 | $this->arrayStore[$arrayStoreKey] = [ 323 | 'cacheData' => serialize($cacheData), 324 | 'expirationTime' => time() + $ttl 325 | ]; 326 | 327 | $this->setMessage("Cache stored successfully", true); 328 | $this->logger->debug("{$this->getMessage()} from Array driver."); 329 | return true; 330 | } 331 | 332 | /** 333 | * Stores multiple items in the cache in batches. 334 | * 335 | * @param array $items 336 | * @param string $namespace 337 | * @param int $batchSize 338 | * @return void 339 | */ 340 | public function putMany(array $items, string $namespace = '', int $batchSize = 100): void 341 | { 342 | $chunks = array_chunk($items, $batchSize, true); 343 | 344 | foreach ($chunks as $chunk) { 345 | foreach ($chunk as $key => $data) { 346 | $this->putCache($data['cacheKey'], $data['cacheData'], $namespace); 347 | } 348 | } 349 | $this->setMessage("{$this->getMessage()}", $this->isSuccess()); 350 | $this->logger->debug("{$this->getMessage()} from Array driver."); 351 | } 352 | 353 | /** 354 | * Renews the expiration time of a cache item. 355 | * 356 | * @param string $cacheKey 357 | * @param string|int $ttl 358 | * @param string $namespace 359 | * @return void 360 | */ 361 | public function renewCache(string $cacheKey, int|string $ttl = 3600, string $namespace = ''): void 362 | { 363 | $arrayStoreKey = $this->buildArrayKey($cacheKey, $namespace); 364 | 365 | if (isset($this->arrayStore[$arrayStoreKey])) { 366 | $ttlSeconds = is_numeric($ttl) ? (int) $ttl : strtotime($ttl) - time(); 367 | $this->arrayStore[$arrayStoreKey]['expirationTime'] = time() + $ttlSeconds; 368 | $this->setMessage("cacheKey: {$cacheKey} renewed successfully", true); 369 | $this->logger->debug("{$this->getMessage()} from array driver."); 370 | } 371 | } 372 | 373 | /** 374 | * Sets a message and its success status. 375 | * 376 | * @param string $message 377 | * @param boolean $success 378 | * @return void 379 | */ 380 | private function setMessage(string $message, bool $success): void 381 | { 382 | $this->message = $message; 383 | $this->success = $success; 384 | } 385 | 386 | /** 387 | * Serializes or unserializes data based on the flag. 388 | * 389 | * @param mixed $data 390 | * @param bool $serialize 391 | * @return mixed 392 | */ 393 | private function serialize(mixed $data, bool $serialize = true): mixed 394 | { 395 | return $serialize ? serialize($data) : unserialize($data); 396 | } 397 | 398 | /** 399 | * Associates one or more keys to a tag. 400 | * 401 | * @param string $tag 402 | * @param string ...$keys 403 | * @return bool 404 | */ 405 | public function tag(string $tag, string ...$keys): bool 406 | { 407 | if (!isset($this->tags[$tag])) { 408 | $this->tags[$tag] = []; 409 | } 410 | foreach ($keys as $key) { 411 | // Accept either raw key or "namespace:key" 412 | $arrayStoreKey = (str_contains($key, ':')) ? $key : $this->buildArrayKey($key, ''); 413 | $this->tags[$tag][$arrayStoreKey] = true; 414 | } 415 | $this->setMessage("Tagged successfully", true); 416 | $this->logger?->debug("{$this->getMessage()} from array driver."); 417 | return true; 418 | } 419 | 420 | /** 421 | * Flushes all keys associated with a tag. 422 | * 423 | * @param string $tag 424 | * @return void 425 | */ 426 | public function flushTag(string $tag): void 427 | { 428 | $keys = array_keys($this->tags[$tag] ?? []); 429 | foreach ($keys as $arrayStoreKey) { 430 | // Recover original key/namespace combination 431 | $parts = explode(':', $arrayStoreKey, 2); 432 | if (count($parts) === 2) { 433 | [$np, $key] = $parts; 434 | } else { 435 | $np = ''; 436 | $key = $arrayStoreKey; 437 | } 438 | $this->clearCache($key, $np); 439 | } 440 | unset($this->tags[$tag]); 441 | $this->setMessage("Tag flushed successfully", true); 442 | $this->logger?->debug("{$this->getMessage()} from array driver."); 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /tests/Unit/DatabaseCacheStoreTest.php: -------------------------------------------------------------------------------- 1 | cache = new Cacheer(); 14 | $this->cache->setConfig()->setDatabaseConnection(Connect::getInstance()->getAttribute(PDO::ATTR_DRIVER_NAME)); 15 | $this->cache->setDriver()->useDatabaseDriver(); 16 | $this->cache->setConfig()->setTimeZone('America/Toronto'); 17 | } 18 | 19 | protected function tearDown(): void 20 | { 21 | $this->cache->flushCache(); 22 | } 23 | 24 | public function testPutCacheInDatabase() 25 | { 26 | $cacheKey = 'test_key'; 27 | $cacheData = ['name' => 'John Doe', 'email' => 'john@example.com']; 28 | 29 | $this->cache->putCache($cacheKey, $cacheData, '', 3600); 30 | 31 | $result = $this->cache->getCache($cacheKey); 32 | 33 | $this->assertNotEmpty($result); 34 | $this->assertEquals($cacheData, $result); 35 | } 36 | 37 | public function testGetCacheFromDatabase() 38 | { 39 | $cacheKey = 'test_key02'; 40 | $cacheData = ['name' => 'Jane Doe', 'email' => 'jane@example.com']; 41 | 42 | $this->cache->putCache($cacheKey, $cacheData, '', 3600); 43 | $this->assertEquals("Cache Stored Successfully", $this->cache->getMessage()); 44 | 45 | $result = $this->cache->getCache($cacheKey); 46 | 47 | $this->assertNotEmpty($result); 48 | $this->assertEquals($cacheData, $result); 49 | } 50 | 51 | public function testExpiredCacheInDatabase() 52 | { 53 | $cacheKey = 'expired_key'; 54 | $cacheData = ['name' => 'Expired User', 'email' => 'expired@example.com']; 55 | 56 | $this->cache->putCache($cacheKey, $cacheData, '', -3600); 57 | $this->assertEquals("Cache Stored Successfully", $this->cache->getMessage()); 58 | 59 | $this->assertEmpty($this->cache->getCache($cacheKey)); 60 | $this->assertFalse($this->cache->isSuccess()); 61 | } 62 | public function testOverwriteExistingCacheInDatabase() 63 | { 64 | 65 | $cacheKey = 'overwrite_key'; 66 | $initialCacheData = ['name' => 'Initial Data', 'email' => 'initial@example.com']; 67 | $newCacheData = ['name' => 'New Data', 'email' => 'new@example.com']; 68 | 69 | $expirationTime = date('Y-m-d H:i:s', time() + 3600); 70 | 71 | 72 | $db = Connect::getInstance(); 73 | $query = $db->prepare("INSERT INTO cacheer_table (cacheKey, cacheData, cacheNamespace, expirationTime) VALUES (?, ?, ?, ?)"); 74 | $query->bindValue(1, $cacheKey); 75 | $query->bindValue(2, serialize($initialCacheData)); 76 | $query->bindValue(3, ''); 77 | $query->bindValue(4, $expirationTime); 78 | 79 | $this->assertTrue($query->execute()); 80 | 81 | $this->cache->appendCache($cacheKey, $newCacheData); 82 | $this->assertEquals("Cache updated successfully.", $this->cache->getMessage()); 83 | 84 | $driver = Connect::getInstance()->getAttribute(PDO::ATTR_DRIVER_NAME); 85 | $nowFunction = ($driver === 'sqlite') ? "DATETIME('now', 'localtime')" : "NOW()"; 86 | 87 | $query = $db->prepare("SELECT cacheData FROM cacheer_table WHERE cacheKey = ? AND cacheNamespace = ? AND expirationTime > $nowFunction"); 88 | $query->bindValue(1, $cacheKey); 89 | $query->bindValue(2, ''); 90 | 91 | $this->assertTrue($query->execute()); 92 | 93 | $result = $query->fetch(PDO::FETCH_ASSOC); 94 | 95 | $this->assertEquals($newCacheData, unserialize($result['cacheData'])); 96 | } 97 | 98 | public function testPutManyCacheItemsInDatabase(): void 99 | { 100 | 101 | $items = [ 102 | [ 103 | 'cacheKey' => 'user_1_profile', 104 | 'cacheData' => [ 105 | ['name' => 'John Doe', 'email' => 'john@example.com'], 106 | ['name' => 'John Doe', 'email' => 'john@example.com'], 107 | ['name' => 'John Doe', 'email' => 'john@example.com'], 108 | ['name' => 'John Doe', 'email' => 'john@example.com'] 109 | ] 110 | ], 111 | [ 112 | 'cacheKey' => 'user_2_profile', 113 | 'cacheData' => [ 114 | ['name' => 'Jane Doe', 'email' => 'jane@example.com'], 115 | ['name' => 'Jane Doe', 'email' => 'jane@example.com'], 116 | ['name' => 'Jane Doe', 'email' => 'jane@example.com'], 117 | ['name' => 'Jane Doe', 'email' => 'jane@example.com'] 118 | ] 119 | ] 120 | ]; 121 | 122 | $this->cache->putMany($items); 123 | 124 | foreach ($items as $item) { 125 | $this->assertEquals($item['cacheData'], $this->cache->getCache($item['cacheKey'])); 126 | } 127 | } 128 | 129 | public function testAppendCacheWithNamespaceInDatabase(): void 130 | { 131 | $cacheKey = 'test_append_key_ns'; 132 | $namespace = 'test_namespace'; 133 | 134 | $initialData = ['initial' => 'data']; 135 | $additionalData = ['new' => 'data']; 136 | 137 | $expectedData = array_merge($initialData, $additionalData); 138 | 139 | // Armazena os dados iniciais no cache com namespace 140 | $this->cache->putCache($cacheKey, $initialData, $namespace); 141 | $this->assertTrue($this->cache->isSuccess()); 142 | 143 | // Adiciona novos dados ao cache existente com namespace 144 | $this->cache->appendCache($cacheKey, $additionalData, $namespace); 145 | $this->assertTrue($this->cache->isSuccess()); 146 | 147 | // Verifica se os dados no cache são os esperados 148 | $cachedData = $this->cache->getCache($cacheKey, $namespace); 149 | $this->assertEquals($expectedData, $cachedData); 150 | } 151 | 152 | public function testDataOutputShouldBeOfTypeJson() 153 | { 154 | $this->cache->useFormatter(); 155 | 156 | $cacheKey = "key_json"; 157 | $cacheData = "data_json"; 158 | 159 | $this->cache->putCache($cacheKey, $cacheData); 160 | $this->assertTrue($this->cache->isSuccess()); 161 | 162 | $cacheOutput = $this->cache->getCache($cacheKey)->toJson(); 163 | $this->assertTrue($this->cache->isSuccess()); 164 | $this->assertJson($cacheOutput); 165 | } 166 | 167 | public function testDataOutputShouldBeOfTypeArray() 168 | { 169 | 170 | $this->cache->useFormatter(); 171 | 172 | $cacheKey = "key_array"; 173 | $cacheData = "data_array"; 174 | 175 | $this->cache->putCache($cacheKey, $cacheData); 176 | $this->assertTrue($this->cache->isSuccess()); 177 | 178 | $cacheOutput = $this->cache->getCache($cacheKey)->toArray(); 179 | $this->assertTrue($this->cache->isSuccess()); 180 | $this->assertIsArray($cacheOutput); 181 | } 182 | 183 | public function testDataOutputShouldBeOfTypeObject() 184 | { 185 | $this->cache->useFormatter(); 186 | 187 | $cacheKey = "key_object"; 188 | $cacheData = ["id" => 123]; 189 | 190 | $this->cache->putCache($cacheKey, $cacheData); 191 | $this->assertTrue($this->cache->isSuccess()); 192 | 193 | $cacheOutput = $this->cache->getCache($cacheKey)->toObject(); 194 | $this->assertTrue($this->cache->isSuccess()); 195 | $this->assertIsObject($cacheOutput); 196 | } 197 | 198 | 199 | public function testClearCacheDataFromDatabase(): void 200 | { 201 | $cacheKey = 'test_key'; 202 | $data = 'test_data'; 203 | 204 | $this->cache->putCache($cacheKey, $data); 205 | $this->assertEquals("Cache Stored Successfully", $this->cache->getMessage()); 206 | 207 | $this->cache->clearCache($cacheKey); 208 | $this->assertTrue($this->cache->isSuccess()); 209 | $this->assertEquals("Cache deleted successfully!", $this->cache->getMessage()); 210 | 211 | $this->assertEmpty($this->cache->getCache($cacheKey)); 212 | } 213 | 214 | 215 | public function testFlushCacheDataFromDatabase(): void 216 | { 217 | $key1 = 'test_key1'; 218 | $data1 = 'test_data1'; 219 | 220 | $key2 = 'test_key2'; 221 | $data2 = 'test_data2'; 222 | 223 | $this->cache->putCache($key1, $data1); 224 | $this->cache->putCache($key2, $data2); 225 | $this->assertTrue($this->cache->isSuccess()); 226 | $this->assertTrue($this->cache->isSuccess()); 227 | 228 | $this->cache->flushCache(); 229 | $this->assertTrue($this->cache->isSuccess()); 230 | $this->assertEquals("Flush finished successfully", $this->cache->getMessage()); 231 | } 232 | 233 | public function test_remember_saves_and_recover_values() 234 | { 235 | $value = $this->cache->remember('remember_test_key', 60, function () { 236 | return 'valor_teste'; 237 | }); 238 | 239 | $this->assertEquals('valor_teste', $value); 240 | 241 | $cachedValue = $this->cache->remember('remember_test_key', 60, function (){ 242 | return 'novo_valor'; 243 | }); 244 | 245 | 246 | $this->assertEquals('valor_teste', $cachedValue); 247 | } 248 | 249 | public function test_remember_forever_saves_value_indefinitely() 250 | { 251 | 252 | $value = $this->cache->rememberForever('remember_forever_key', function () { 253 | return 'valor_eterno'; 254 | }); 255 | $this->assertEquals('valor_eterno', $value); 256 | 257 | $cachedValue = $this->cache->rememberForever('remember_forever_key', function () { 258 | return 'novo_valor'; 259 | }); 260 | 261 | $this->assertEquals('valor_eterno', $cachedValue); 262 | } 263 | 264 | public function test_get_and_forget() 265 | { 266 | $cacheKey = 'cache_key_test'; 267 | $this->cache->putCache($cacheKey, 10); 268 | 269 | $this->assertTrue($this->cache->isSuccess()); 270 | 271 | $cacheData = $this->cache->getAndForget($cacheKey); 272 | 273 | $this->assertTrue($this->cache->isSuccess()); 274 | $this->assertEquals(10, $cacheData); 275 | 276 | $oldCacheData = $this->cache->getAndForget($cacheKey); 277 | 278 | $this->assertNull($oldCacheData); 279 | $this->assertFalse($this->cache->isSuccess()); 280 | 281 | $noCacheData = $this->cache->getAndForget('non_existent_cache_key'); 282 | $this->assertNull($noCacheData); 283 | } 284 | 285 | public function test_store_if_not_present_with_add_function() 286 | { 287 | $existentKey = 'cache_key_test'; 288 | 289 | $nonExistentKey = 'non_existent_key'; 290 | 291 | $this->cache->putCache($existentKey, 'existent_data'); 292 | 293 | $this->assertTrue($this->cache->isSuccess()); 294 | $this->assertEquals('existent_data', $this->cache->getCache($existentKey)); 295 | 296 | $addCache = $this->cache->add($existentKey, 100); 297 | 298 | $this->assertTrue($addCache); 299 | $this->assertNotEquals(100, 'existent_data'); 300 | 301 | $addNonExistentKey = $this->cache->add($nonExistentKey, 'non_existent_data'); 302 | 303 | $this->assertFalse($addNonExistentKey); 304 | $this->assertEquals('non_existent_data', $this->cache->getCache($nonExistentKey)); 305 | $this->assertTrue($this->cache->isSuccess()); 306 | } 307 | 308 | 309 | public function test_increment_function() { 310 | 311 | $cacheKey = 'test_increment'; 312 | $cacheData = 2025; 313 | 314 | $this->cache->putCache($cacheKey, $cacheData); 315 | 316 | $this->assertTrue($this->cache->isSuccess()); 317 | $this->assertEquals($cacheData, $this->cache->getCache($cacheKey)); 318 | $this->assertIsNumeric($this->cache->getCache($cacheKey)); 319 | 320 | $increment = $this->cache->increment($cacheKey, 2); 321 | $this->assertTrue($increment); 322 | 323 | $this->assertEquals(2027, $this->cache->getCache($cacheKey)); 324 | 325 | } 326 | 327 | public function test_decrement_function() { 328 | 329 | $cacheKey = 'test_decrement'; 330 | $cacheData = 2027; 331 | 332 | $this->cache->putCache($cacheKey, $cacheData); 333 | 334 | $this->assertTrue($this->cache->isSuccess()); 335 | $this->assertEquals($cacheData, $this->cache->getCache($cacheKey)); 336 | $this->assertIsNumeric($this->cache->getCache($cacheKey)); 337 | 338 | $increment = $this->cache->decrement($cacheKey, 2); 339 | $this->assertTrue($increment); 340 | 341 | $this->assertEquals(2025, $this->cache->getCache($cacheKey)); 342 | 343 | } 344 | 345 | public function test_get_many_cache_items() 346 | { 347 | $cacheItems = [ 348 | 'key1' => 'value1', 349 | 'key2' => 'value2', 350 | 'key3' => 'value3' 351 | ]; 352 | foreach ($cacheItems as $key => $value) { 353 | $this->cache->putCache($key, $value); 354 | } 355 | $this->assertTrue($this->cache->isSuccess()); 356 | $retrievedItems = $this->cache->getMany(array_keys($cacheItems)); 357 | $this->assertTrue($this->cache->isSuccess()); 358 | $this->assertCount(3, $retrievedItems); 359 | $this->assertEquals($cacheItems, $retrievedItems); 360 | } 361 | 362 | public function test_get_all_cache_items() 363 | { 364 | $namespace = 'test_namespace'; 365 | $cacheKey = 'test_key'; 366 | $cacheData = ['foo' => 'bar']; 367 | 368 | $this->cache->putCache($cacheKey, $cacheData, $namespace); 369 | $this->assertTrue($this->cache->isSuccess()); 370 | $retrievedItems = $this->cache->getAll($namespace); 371 | $this->assertTrue($this->cache->isSuccess()); 372 | $this->assertCount(1, $retrievedItems); 373 | $this->assertArrayHasKey($cacheKey, $retrievedItems); 374 | $this->assertEquals($cacheData, $retrievedItems[$cacheKey]); 375 | } 376 | 377 | public function test_tag_and_flush_tag_in_database_driver() 378 | { 379 | $k1 = 'tag_key_1'; 380 | $k2 = 'tag_key_2'; 381 | $this->cache->putCache($k1, 'v1'); 382 | $this->cache->putCache($k2, 'v2'); 383 | 384 | $ok = $this->cache->tag('groupA', $k1, $k2); 385 | $this->assertTrue($ok); 386 | $this->assertTrue($this->cache->isSuccess()); 387 | 388 | $this->cache->flushTag('groupA'); 389 | $this->assertTrue($this->cache->isSuccess()); 390 | 391 | $this->assertEmpty($this->cache->getCache($k1)); 392 | $this->assertEmpty($this->cache->getCache($k2)); 393 | } 394 | 395 | public function test_tag_with_namespace_and_flush_tag_in_database_driver() 396 | { 397 | $ns = 'nsA'; 398 | $k1 = 'k1'; 399 | $k2 = 'k2'; 400 | $this->cache->putCache($k1, 'v1', $ns); 401 | $this->cache->putCache($k2, 'v2', $ns); 402 | 403 | $ok = $this->cache->tag('groupNS', $ns . ':' . $k1, $ns . ':' . $k2); 404 | $this->assertTrue($ok); 405 | 406 | $this->cache->flushTag('groupNS'); 407 | $this->assertEmpty($this->cache->getCache($k1, $ns)); 408 | $this->assertEmpty($this->cache->getCache($k2, $ns)); 409 | } 410 | } 411 | --------------------------------------------------------------------------------