diff --git a/README.md b/README.md index cbed213..a5e79fb 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,27 @@ smart_cache(['products' => $products], 3600); $products = smart_cache('products'); ``` +### Using Different Cache Drivers + +Use different cache drivers while maintaining all SmartCache optimizations: + +```php +// Use Redis with all SmartCache optimizations (compression, chunking, etc.) +SmartCache::store('redis')->put('key', $value, 3600); +SmartCache::store('redis')->get('key'); + +// Use Memcached with optimizations +SmartCache::store('memcached')->remember('users', 3600, fn() => User::all()); + +// Use file cache with optimizations +SmartCache::store('file')->put('config', $config, 86400); + +// For raw access to Laravel's cache (bypasses SmartCache optimizations) +SmartCache::repository('redis')->put('key', $value, 3600); +``` + +> **Full Laravel Compatibility:** SmartCache implements Laravel's `Repository` interface, so it works seamlessly with any code that type-hints `Illuminate\Contracts\Cache\Repository`. The `store()` method returns a SmartCache instance that is also a valid Repository. + ## 💡 Core Features (Automatic Optimization) ### 1. Intelligent Compression diff --git a/docs/index.html b/docs/index.html index ed277ed..bffa22a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -763,6 +763,35 @@

Familiar Laravel API

// Clear all cache SmartCache::flush(); +

Using Different Cache Drivers

+

Use different cache drivers while maintaining all SmartCache optimizations:

+ +
// Use Redis with all SmartCache optimizations (compression, chunking, etc.)
+SmartCache::store('redis')->put('key', $value, 3600);
+SmartCache::store('redis')->get('key');
+
+// Use Memcached with optimizations
+SmartCache::store('memcached')->remember('users', 3600, fn() => User::all());
+
+// Use file cache with optimizations
+SmartCache::store('file')->put('config', $config, 86400);
+
+// Chain multiple operations on a specific store
+$redisCache = SmartCache::store('redis');
+$redisCache->put('users', $users, 3600);
+$redisCache->put('products', $products, 3600);
+
+// For raw access to Laravel's cache (bypasses SmartCache optimizations)
+SmartCache::repository('redis')->put('key', $value, 3600);
+ +
+ Note: The store() method returns a SmartCache instance, so all optimization strategies (compression, chunking, encryption, etc.) continue to work. Use repository() if you need direct access to Laravel's cache without SmartCache optimizations. +
+ +
+ Full Laravel Compatibility: SmartCache implements Laravel's Illuminate\Contracts\Cache\Repository interface, so it works seamlessly with any code that type-hints Repository. The store() method returns a SmartCache instance that is also a valid Repository, ensuring zero breaking changes when migrating from Laravel's Cache facade. +
+

Automatic Optimization

SmartCache automatically optimizes your data when beneficial:

@@ -1712,6 +1741,61 @@

🔧 Basic Cache Operations

+
+
SmartCache::store() Enhanced!
+
SmartCache::store(string|null $name = null): static
+

Get a SmartCache instance for a specific cache driver. All SmartCache optimizations (compression, chunking, encryption, etc.) are preserved. The returned instance also implements Illuminate\Contracts\Cache\Repository for full Laravel compatibility.

+
+ $name (string|null) - The cache store name (redis, file, memcached, etc.) +
+
+ Returns: static - SmartCache instance configured for the specified store (also implements Repository) +
+
+ Examples: +
// Use Redis with all SmartCache optimizations
+SmartCache::store('redis')->put('key', $value, 3600);
+SmartCache::store('redis')->get('key');
+
+// Use Memcached with optimizations
+SmartCache::store('memcached')->remember('users', 3600, fn() => User::all());
+
+// Chain operations on a specific store
+$redisCache = SmartCache::store('redis');
+$redisCache->put('users', $users, 3600);
+$redisCache->put('products', $products, 3600);
+
+// Works with Repository type hints
+function cacheData(\Illuminate\Contracts\Cache\Repository $cache) {
+    $cache->put('key', 'value', 3600);
+}
+cacheData(SmartCache::store('redis')); // ✅ Works!
+
+
+ +
+
SmartCache::repository() New!
+
SmartCache::repository(string|null $name = null): \Illuminate\Contracts\Cache\Repository
+

Get direct access to Laravel's underlying cache repository. This bypasses all SmartCache optimizations.

+
+ $name (string|null) - The cache store name (redis, file, memcached, etc.) +
+
+ Returns: Repository - Laravel's native cache repository +
+
+ Examples: +
// Direct access to Redis cache (no SmartCache optimizations)
+SmartCache::repository('redis')->put('key', $value, 3600);
+
+// Direct access to default cache store
+SmartCache::repository()->get('key');
+
+
+ Note: Use repository() only when you need to bypass SmartCache optimizations. For normal usage, prefer store() to maintain all optimization benefits. +
+
+

🌊 SWR Patterns (Laravel 12+)

diff --git a/src/Console/Commands/ClearCommand.php b/src/Console/Commands/ClearCommand.php index a8e7338..861c1cf 100644 --- a/src/Console/Commands/ClearCommand.php +++ b/src/Console/Commands/ClearCommand.php @@ -112,8 +112,7 @@ protected function clearAllKeys(SmartCache $cache): int protected function clearOrphanedKeys(SmartCache $cache): int { - $repository = $cache->store(); - $store = $repository->getStore(); + $store = $cache->getStore(); $cleared = 0; $managedKeys = $cache->getManagedKeys(); @@ -122,7 +121,7 @@ protected function clearOrphanedKeys(SmartCache $cache): int foreach ($allKeys as $key) { if (!\in_array($key, $managedKeys, true) && !$this->isSmartCacheInternalKey($key)) { - if ($repository->forget($key)) { + if ($cache->store()->forget($key)) { $cleared++; $this->line("Cleared key: {$key}"); } diff --git a/src/Console/Commands/StatusCommand.php b/src/Console/Commands/StatusCommand.php index 36824fa..09ce510 100644 --- a/src/Console/Commands/StatusCommand.php +++ b/src/Console/Commands/StatusCommand.php @@ -123,8 +123,7 @@ protected function displayConfiguration(ConfigRepository $config): void protected function findAllNonManagedKeys(SmartCache $cache): array { - $repository = $cache->store(); - $store = $repository->getStore(); + $store = $cache->getStore(); $managedKeys = $cache->getManagedKeys(); $nonManagedKeys = []; diff --git a/src/Contracts/SmartCache.php b/src/Contracts/SmartCache.php index 0fbe0c0..f1610aa 100644 --- a/src/Contracts/SmartCache.php +++ b/src/Contracts/SmartCache.php @@ -2,84 +2,38 @@ namespace SmartCache\Contracts; -interface SmartCache +use Illuminate\Contracts\Cache\Repository; + +/** + * SmartCache Contract + * + * This interface extends Laravel's Repository interface to ensure full compatibility + * with Laravel's cache system while adding SmartCache-specific optimization features. + */ +interface SmartCache extends Repository { /** - * Get an item from the cache. + * Get a SmartCache instance using a specific cache store. * - * @param string $key - * @param mixed $default - * @return mixed - */ - public function get(string $key, mixed $default = null): mixed; - - /** - * Store an item in the cache. - * - * @param string $key - * @param mixed $value - * @param \DateTimeInterface|\DateInterval|int|null $ttl - * @return bool - */ - public function put(string $key, mixed $value, $ttl = null): bool; - - /** - * Determine if an item exists in the cache. - * - * @param string $key - * @return bool - */ - public function has(string $key): bool; - - /** - * Remove an item from the cache. - * - * @param string $key - * @return bool - */ - public function forget(string $key): bool; - - /** - * Store an item in the cache indefinitely. - * - * @param string $key - * @param mixed $value - * @return bool - */ - public function forever(string $key, mixed $value): bool; - - /** - * Get an item from the cache, or execute the given Closure and store the result. - * - * @param string $key - * @param \DateTimeInterface|\DateInterval|int|null $ttl - * @param \Closure $callback - * @return mixed - */ - public function remember(string $key, $ttl, \Closure $callback): mixed; - - /** - * Get an item from the cache, or execute the given Closure and store the result forever. + * When called without arguments, returns the current instance. + * When called with a store name, returns a new SmartCache instance + * configured to use that store while maintaining all optimization strategies. * - * @param string $key - * @param \Closure $callback - * @return mixed + * @param string|null $name The cache store name (e.g., 'redis', 'file', 'memcached') + * @return static */ - public function rememberForever(string $key, \Closure $callback): mixed; + public function store(string|null $name = null): static; /** - * Get the underlying cache store. + * Get the underlying cache repository directly. * - * @return \Illuminate\Contracts\Cache\Repository - */ - public function store(string|null $name = null): \Illuminate\Contracts\Cache\Repository; - - /** - * Clear all cache keys managed by SmartCache. + * This provides raw access to Laravel's cache repository without SmartCache optimizations. + * Use this when you need direct access to the cache driver. * - * @return bool + * @param string|null $name The store name (null for current store) + * @return Repository */ - public function clear(): bool; + public function repository(string|null $name = null): Repository; /** * Get all keys managed by SmartCache. @@ -291,14 +245,6 @@ public function many(array $keys): array; */ public function putMany(array $values, $ttl = null): bool; - /** - * Remove multiple items from the cache. - * - * @param array $keys - * @return bool - */ - public function deleteMultiple(array $keys): bool; - /** * Get a memoized cache instance. * diff --git a/src/Facades/SmartCache.php b/src/Facades/SmartCache.php index 8a9821d..f6f961c 100644 --- a/src/Facades/SmartCache.php +++ b/src/Facades/SmartCache.php @@ -16,7 +16,8 @@ * @method static mixed swr(string $key, \Closure $callback, int $ttl = 3600, int $staleTtl = 7200) * @method static mixed stale(string $key, \Closure $callback, int $ttl = 1800, int $staleTtl = 86400) * @method static mixed refreshAhead(string $key, \Closure $callback, int $ttl = 3600, int $refreshWindow = 600) - * @method static \Illuminate\Contracts\Cache\Repository store(string|null $name = null) + * @method static \SmartCache\SmartCache store(string|null $name = null) + * @method static \Illuminate\Contracts\Cache\Repository repository(string|null $name = null) * @method static bool clear() * @method static array getManagedKeys() * @method static static tags(string|array $tags) @@ -35,7 +36,7 @@ * @method static array analyzePerformance() * @method static int cleanupExpiredManagedKeys() * @method static bool hasFeature(string $feature) - * + * * @see \SmartCache\SmartCache */ class SmartCache extends Facade diff --git a/src/SmartCache.php b/src/SmartCache.php index 5e3144a..920d909 100644 --- a/src/SmartCache.php +++ b/src/SmartCache.php @@ -3,6 +3,7 @@ namespace SmartCache; use Illuminate\Contracts\Cache\Repository; +use Illuminate\Contracts\Cache\Store; use Illuminate\Contracts\Cache\Factory as CacheManager; use Illuminate\Contracts\Config\Repository as ConfigRepository; use Illuminate\Support\Facades\Log; @@ -16,7 +17,7 @@ use SmartCache\Traits\HasLocks; use SmartCache\Traits\DispatchesCacheEvents; -class SmartCache implements SmartCacheContract +class SmartCache implements SmartCacheContract, Repository { use HasLocks, DispatchesCacheEvents; /** @@ -170,9 +171,9 @@ public function addStrategy(OptimizationStrategy $strategy): self /** * {@inheritdoc} */ - public function get(string $key, mixed $default = null): mixed + public function get($key, $default = null): mixed { - $key = $this->applyNamespace($key); + $key = $this->applyNamespace((string) $key); $startTime = $this->enablePerformanceMonitoring ? microtime(true) : null; $value = $this->cache->get($key, null); @@ -212,9 +213,9 @@ protected function trackAccessFrequency(string $key): void /** * {@inheritdoc} */ - public function put(string $key, mixed $value, $ttl = null): bool + public function put($key, $value, $ttl = null): bool { - $key = $this->applyNamespace($key); + $key = $this->applyNamespace((string) $key); $startTime = $this->enablePerformanceMonitoring ? microtime(true) : null; $optimizedValue = $this->maybeOptimizeValue($value, $key, $ttl); @@ -241,21 +242,45 @@ public function put(string $key, mixed $value, $ttl = null): bool return $result; } + /** + * PSR-16 alias for put(). + * + * @param string $key + * @param mixed $value + * @param \DateTimeInterface|\DateInterval|int|null $ttl + * @return bool + */ + public function set($key, $value, $ttl = null): bool + { + return $this->put($key, $value, $ttl); + } + /** * {@inheritdoc} */ - public function has(string $key): bool + public function has($key): bool { - $key = $this->applyNamespace($key); + $key = $this->applyNamespace((string) $key); return $this->cache->has($key); } + /** + * PSR-16 alias for forget(). + * + * @param string $key + * @return bool + */ + public function delete($key): bool + { + return $this->forget($key); + } + /** * {@inheritdoc} */ - public function forget(string $key): bool + public function forget($key): bool { - $key = $this->applyNamespace($key); + $key = $this->applyNamespace((string) $key); $value = $this->cache->get($key); // If value is chunked, clean up all chunk keys @@ -285,9 +310,9 @@ public function forget(string $key): bool /** * {@inheritdoc} */ - public function forever(string $key, mixed $value): bool + public function forever($key, $value): bool { - $key = $this->applyNamespace($key); + $key = $this->applyNamespace((string) $key); $optimizedValue = $this->maybeOptimizeValue($value, $key, null); // Track all keys for pattern matching and invalidation @@ -304,45 +329,182 @@ public function forever(string $key, mixed $value): bool /** * {@inheritdoc} */ - public function remember(string $key, $ttl, \Closure $callback): mixed + public function remember($key, $ttl, \Closure $callback): mixed { if ($this->has($key)) { return $this->get($key); } - + $value = $callback(); $this->put($key, $value, $ttl); - + return $value; } + /** + * Get an item from the cache, or execute the given Closure and store the result forever. + * This is an alias for rememberForever(). + * + * @param string $key + * @param \Closure $callback + * @return mixed + */ + public function sear($key, \Closure $callback): mixed + { + return $this->rememberForever($key, $callback); + } + /** * {@inheritdoc} */ - public function rememberForever(string $key, \Closure $callback): mixed + public function rememberForever($key, \Closure $callback): mixed { if ($this->has($key)) { return $this->get($key); } - + $value = $callback(); $this->forever($key, $value); - + return $value; } /** * {@inheritdoc} */ - public function store(string|null $name = null): Repository + public function store(string|null $name = null): static + { + if ($name === null) { + return $this; + } + + // Create a new SmartCache instance with the specified store + // This preserves all optimization strategies while using a different cache driver + return new static( + $this->cacheManager->store($name), + $this->cacheManager, + $this->config, + $this->strategies + ); + } + + /** + * Get the underlying cache repository. + * + * This provides direct access to the Laravel cache repository without SmartCache optimizations. + * Use this when you need raw access to the cache driver. + * + * @param string|null $name The store name (null for current store) + * @return Repository + */ + public function repository(string|null $name = null): Repository { if ($name === null) { return $this->cache; } - + return $this->cacheManager->store($name); } + /** + * {@inheritdoc} + */ + public function getStore(): Store + { + return $this->cache->getStore(); + } + + /** + * Retrieve an item from the cache and delete it. + * + * @param array|string $key + * @param mixed $default + * @return mixed + */ + public function pull($key, $default = null): mixed + { + $value = $this->get($key, $default); + $this->forget($key); + return $value; + } + + /** + * Store an item in the cache if the key does not exist. + * + * @param string $key + * @param mixed $value + * @param \DateTimeInterface|\DateInterval|int|null $ttl + * @return bool + */ + public function add($key, $value, $ttl = null): bool + { + if ($this->has($key)) { + return false; + } + + return $this->put($key, $value, $ttl); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function increment($key, $value = 1): int|bool + { + $key = $this->applyNamespace((string) $key); + return $this->cache->increment($key, $value); + } + + /** + * Decrement the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function decrement($key, $value = 1): int|bool + { + $key = $this->applyNamespace((string) $key); + return $this->cache->decrement($key, $value); + } + + /** + * PSR-16: Obtains multiple cache items identified by their unique keys. + * + * @param iterable $keys + * @param mixed $default + * @return iterable + */ + public function getMultiple($keys, $default = null): iterable + { + $results = []; + foreach ($keys as $key) { + $results[$key] = $this->get($key, $default); + } + return $results; + } + + /** + * PSR-16: Persists a set of key => value pairs in the cache. + * + * @param iterable $values + * @param \DateTimeInterface|\DateInterval|int|null $ttl + * @return bool + */ + public function setMultiple($values, $ttl = null): bool + { + $success = true; + foreach ($values as $key => $value) { + if (!$this->put($key, $value, $ttl)) { + $success = false; + } + } + return $success; + } + /** * {@inheritdoc} */ @@ -1785,10 +1947,10 @@ public function putMany(array $values, $ttl = null): bool /** * Remove multiple items from the cache. * - * @param array $keys + * @param iterable $keys * @return bool */ - public function deleteMultiple(array $keys): bool + public function deleteMultiple($keys): bool { $success = true; diff --git a/tests/Unit/Console/ClearCommandTest.php b/tests/Unit/Console/ClearCommandTest.php index e992580..7ba739f 100644 --- a/tests/Unit/Console/ClearCommandTest.php +++ b/tests/Unit/Console/ClearCommandTest.php @@ -63,17 +63,12 @@ public function test_clear_command_with_no_managed_keys_and_force() ->twice() // Called once for display, once in clearOrphanedKeys ->andReturn([]); - // Mock the store to return a cache repository that will simulate an unsupported driver - $mockRepository = Mockery::mock(\Illuminate\Contracts\Cache\Repository::class); + // Mock getStore to return a store that will simulate an unsupported driver $mockStore = Mockery::mock(\stdClass::class); // Use stdClass to simulate unsupported driver - - $mockRepository->shouldReceive('getStore') + + $this->mockSmartCache->shouldReceive('getStore') ->once() ->andReturn($mockStore); - - $this->mockSmartCache->shouldReceive('store') - ->once() - ->andReturn($mockRepository); // Inject the mock into the command $this->command->setLaravel($this->app); @@ -302,17 +297,17 @@ public function test_clear_specific_key_not_managed_but_exists_with_force() ->once() ->with($existingKey) ->andReturn(true); - - // Mock the store() method to return a cache repository - $mockStore = Mockery::mock(\Illuminate\Contracts\Cache\Repository::class); - $mockStore->shouldReceive('forget') + + // Mock the store() method to return a SmartCache instance for non-managed key clearing + $mockStoreInstance = Mockery::mock(SmartCache::class); + $mockStoreInstance->shouldReceive('forget') ->once() ->with($existingKey) ->andReturn(true); - + $this->mockSmartCache->shouldReceive('store') ->once() - ->andReturn($mockStore); + ->andReturn($mockStoreInstance); // Inject the mock into the command $this->command->setLaravel($this->app); diff --git a/tests/Unit/Console/StatusCommandTest.php b/tests/Unit/Console/StatusCommandTest.php index e511c48..1c3de63 100644 --- a/tests/Unit/Console/StatusCommandTest.php +++ b/tests/Unit/Console/StatusCommandTest.php @@ -300,19 +300,14 @@ public function test_status_command_with_force_option_no_orphaned_keys() ->with('smart-cache') ->once() ->andReturn($this->getDefaultSmartCacheConfig()); - - // Mock the store() method to return a cache repository - $mockRepository = Mockery::mock(\Illuminate\Contracts\Cache\Repository::class); + + // Mock getStore() to return a cache store $mockStore = Mockery::mock(\Illuminate\Cache\ArrayStore::class); - - $mockRepository->shouldReceive('getStore') + + $this->mockSmartCache->shouldReceive('getStore') ->once() ->andReturn($mockStore); - - $this->mockSmartCache->shouldReceive('store') - ->once() - ->andReturn($mockRepository); - + // Mock has() calls to check if managed keys exist $this->mockSmartCache->shouldReceive('has') ->with('key1') @@ -354,19 +349,14 @@ public function test_status_command_with_force_option_finds_missing_managed_keys ->with('smart-cache') ->once() ->andReturn($this->getDefaultSmartCacheConfig()); - - // Mock the store() method to return a cache repository - $mockRepository = Mockery::mock(\Illuminate\Contracts\Cache\Repository::class); + + // Mock getStore() to return a cache store $mockStore = Mockery::mock(\Illuminate\Cache\ArrayStore::class); - - $mockRepository->shouldReceive('getStore') + + $this->mockSmartCache->shouldReceive('getStore') ->once() ->andReturn($mockStore); - - $this->mockSmartCache->shouldReceive('store') - ->once() - ->andReturn($mockRepository); - + // Mock has() calls - one key exists, two are missing $this->mockSmartCache->shouldReceive('has') ->with('existing-key') diff --git a/tests/Unit/SmartCacheTest.php b/tests/Unit/SmartCacheTest.php index 8eb1990..4d81bb6 100644 --- a/tests/Unit/SmartCacheTest.php +++ b/tests/Unit/SmartCacheTest.php @@ -244,15 +244,104 @@ public function test_managed_keys_tracking() $this->assertContains($key2, $managedKeys); } - public function test_can_get_different_cache_stores() + public function test_store_returns_smart_cache_instance() { - // Test getting default store + // Test getting default store returns self $defaultStore = $this->smartCache->store(); - $this->assertEquals($this->getCacheStore(), $defaultStore); + $this->assertInstanceOf(\SmartCache\SmartCache::class, $defaultStore); + $this->assertSame($this->smartCache, $defaultStore); - // Test getting named store + // Test getting named store returns a new SmartCache instance $arrayStore = $this->smartCache->store('array'); - $this->assertNotNull($arrayStore); + $this->assertInstanceOf(\SmartCache\SmartCache::class, $arrayStore); + $this->assertNotSame($this->smartCache, $arrayStore); + } + + public function test_store_method_preserves_optimization_strategies() + { + // Create a SmartCache instance with compression enabled + $smartCache = new SmartCache( + $this->getCacheStore(), + $this->getCacheManager(), + $this->app['config'], + [new CompressionStrategy(1024, 6)] + ); + + // Get a store instance for array driver + $arraySmartCache = $smartCache->store('array'); + + // Store large data through the new store instance + $key = 'store-optimization-test'; + $value = $this->createCompressibleData(); + + $arraySmartCache->put($key, $value); + + // Verify the value is compressed in the raw cache + $rawCached = $this->getCacheStore('array')->get($key); + $this->assertValueIsCompressed($rawCached); + + // Verify we can retrieve the original value + $retrieved = $arraySmartCache->get($key); + $this->assertEquals($value, $retrieved); + } + + public function test_store_method_allows_chaining_operations() + { + // Test that store() allows chaining cache operations + $key = 'chained-store-key'; + $value = 'chained-value'; + + // Put using chained store + $this->smartCache->store('array')->put($key, $value, 3600); + + // Get using chained store + $retrieved = $this->smartCache->store('array')->get($key); + $this->assertEquals($value, $retrieved); + + // Remember using chained store + $rememberValue = $this->smartCache->store('array')->remember('remember-chain-key', 3600, fn() => 'remembered'); + $this->assertEquals('remembered', $rememberValue); + } + + public function test_store_method_uses_correct_driver() + { + $key = 'driver-test-key'; + $value = 'test-value'; + + // Store in array driver via store() method + $this->smartCache->store('array')->put($key, $value); + + // Value should be in array store + $this->assertTrue($this->smartCache->store('array')->has($key)); + + // Value should not be in file store (different driver) + $this->assertFalse($this->smartCache->store('file')->has($key)); + } + + public function test_repository_method_returns_raw_cache() + { + // Test getting default repository + $defaultRepo = $this->smartCache->repository(); + $this->assertInstanceOf(\Illuminate\Contracts\Cache\Repository::class, $defaultRepo); + $this->assertEquals($this->getCacheStore(), $defaultRepo); + + // Test getting named repository + $arrayRepo = $this->smartCache->repository('array'); + $this->assertInstanceOf(\Illuminate\Contracts\Cache\Repository::class, $arrayRepo); + } + + public function test_repository_bypasses_optimization() + { + $key = 'repository-bypass-test'; + $value = $this->createCompressibleData(); // Large, compressible data + + // Store via repository (should bypass SmartCache optimization) + $this->smartCache->repository()->put($key, $value); + + // Get raw value - should NOT be compressed since we used repository + $rawCached = $this->getCacheStore()->get($key); + $this->assertEquals($value, $rawCached); + $this->assertIsString($rawCached); // Not wrapped in optimization array } public function test_chunked_data_cleanup_on_forget() @@ -540,6 +629,190 @@ public function test_flexible_method_with_optimization_integration() $this->assertNull($this->smartCache->get($metaKey)); } + // ======================================== + // Repository Interface Compliance Tests + // ======================================== + + public function test_smart_cache_implements_repository_interface() + { + $this->assertInstanceOf(\Illuminate\Contracts\Cache\Repository::class, $this->smartCache); + } + + public function test_pull_retrieves_and_deletes_item() + { + $key = 'pull-test-key'; + $value = 'pull-test-value'; + + $this->smartCache->put($key, $value); + $this->assertTrue($this->smartCache->has($key)); + + $pulled = $this->smartCache->pull($key); + $this->assertEquals($value, $pulled); + $this->assertFalse($this->smartCache->has($key)); + } + + public function test_pull_returns_default_when_key_missing() + { + $default = 'default-value'; + $pulled = $this->smartCache->pull('non-existent-key', $default); + $this->assertEquals($default, $pulled); + } + + public function test_add_stores_item_only_if_not_exists() + { + $key = 'add-test-key'; + + // Add should succeed when key doesn't exist + $result = $this->smartCache->add($key, 'first-value'); + $this->assertTrue($result); + $this->assertEquals('first-value', $this->smartCache->get($key)); + + // Add should fail when key already exists + $result = $this->smartCache->add($key, 'second-value'); + $this->assertFalse($result); + $this->assertEquals('first-value', $this->smartCache->get($key)); // Still first value + } + + public function test_increment_increases_value() + { + $key = 'increment-test-key'; + $this->getCacheStore()->put($key, 5); + + $result = $this->smartCache->increment($key); + $this->assertEquals(6, $result); + + $result = $this->smartCache->increment($key, 3); + $this->assertEquals(9, $result); + } + + public function test_decrement_decreases_value() + { + $key = 'decrement-test-key'; + $this->getCacheStore()->put($key, 10); + + $result = $this->smartCache->decrement($key); + $this->assertEquals(9, $result); + + $result = $this->smartCache->decrement($key, 4); + $this->assertEquals(5, $result); + } + + public function test_sear_is_alias_for_remember_forever() + { + $key = 'sear-test-key'; + $callCount = 0; + + $callback = function () use (&$callCount) { + $callCount++; + return 'seared-value'; + }; + + // First call should execute callback + $value = $this->smartCache->sear($key, $callback); + $this->assertEquals('seared-value', $value); + $this->assertEquals(1, $callCount); + + // Second call should return cached value without executing callback + $value = $this->smartCache->sear($key, $callback); + $this->assertEquals('seared-value', $value); + $this->assertEquals(1, $callCount); // Callback should not be called again + } + + public function test_set_is_alias_for_put() + { + $key = 'set-test-key'; + $value = 'set-test-value'; + + $result = $this->smartCache->set($key, $value); + $this->assertTrue($result); + $this->assertEquals($value, $this->smartCache->get($key)); + } + + public function test_delete_is_alias_for_forget() + { + $key = 'delete-test-key'; + $value = 'delete-test-value'; + + $this->smartCache->put($key, $value); + $this->assertTrue($this->smartCache->has($key)); + + $result = $this->smartCache->delete($key); + $this->assertTrue($result); + $this->assertFalse($this->smartCache->has($key)); + } + + public function test_get_store_returns_underlying_store() + { + $store = $this->smartCache->getStore(); + $this->assertInstanceOf(\Illuminate\Contracts\Cache\Store::class, $store); + } + + public function test_get_multiple_retrieves_multiple_items() + { + $this->smartCache->put('multi1', 'value1'); + $this->smartCache->put('multi2', 'value2'); + $this->smartCache->put('multi3', 'value3'); + + $results = $this->smartCache->getMultiple(['multi1', 'multi2', 'multi3']); + + $this->assertIsIterable($results); + $this->assertEquals('value1', $results['multi1']); + $this->assertEquals('value2', $results['multi2']); + $this->assertEquals('value3', $results['multi3']); + } + + public function test_get_multiple_returns_default_for_missing_keys() + { + $this->smartCache->put('existing', 'exists'); + + $results = $this->smartCache->getMultiple(['existing', 'missing'], 'default'); + + $this->assertEquals('exists', $results['existing']); + $this->assertEquals('default', $results['missing']); + } + + public function test_set_multiple_stores_multiple_items() + { + $values = [ + 'setmulti1' => 'val1', + 'setmulti2' => 'val2', + 'setmulti3' => 'val3', + ]; + + $result = $this->smartCache->setMultiple($values); + $this->assertTrue($result); + + $this->assertEquals('val1', $this->smartCache->get('setmulti1')); + $this->assertEquals('val2', $this->smartCache->get('setmulti2')); + $this->assertEquals('val3', $this->smartCache->get('setmulti3')); + } + + public function test_delete_multiple_removes_multiple_items() + { + $this->smartCache->put('del1', 'value1'); + $this->smartCache->put('del2', 'value2'); + $this->smartCache->put('del3', 'value3'); + + $result = $this->smartCache->deleteMultiple(['del1', 'del2']); + $this->assertTrue($result); + + $this->assertFalse($this->smartCache->has('del1')); + $this->assertFalse($this->smartCache->has('del2')); + $this->assertTrue($this->smartCache->has('del3')); // This one was not deleted + } + + public function test_store_returns_repository_compatible_instance() + { + $storeInstance = $this->smartCache->store('array'); + + // Should be both SmartCache and Repository + $this->assertInstanceOf(\SmartCache\SmartCache::class, $storeInstance); + $this->assertInstanceOf(\Illuminate\Contracts\Cache\Repository::class, $storeInstance); + + // Should have getStore() method working + $this->assertInstanceOf(\Illuminate\Contracts\Cache\Store::class, $storeInstance->getStore()); + } + protected function tearDown(): void { Mockery::close();