├── 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 |
5 |
6 |
7 | [](https://github.com/silviooosilva)
8 | 
9 | [](https://github.com/silviooosilva/CacheerPHP/releases)
10 | [](https://scrutinizer-ci.com/g/silviooosilva/CacheerPHP)
11 | 
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 | 
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 |
--------------------------------------------------------------------------------