diff --git a/library/config.php b/library/config.php index 93d05d074..2258220db 100644 --- a/library/config.php +++ b/library/config.php @@ -53,11 +53,14 @@ $bb_cfg['db_alias'] = [ ]; // Cache -// Future enhancement: implement Redis/Memcached storage for Nette $bb_cfg['cache'] = [ 'db_dir' => realpath(BB_ROOT) . '/internal_data/cache/filecache/', 'prefix' => 'tp_', - // Available cache types: file, sqlite, memory (file by default) + 'memcached' => [ + 'host' => '127.0.0.1', + 'port' => 11211, + ], + // Available cache types: file, sqlite, memory, memcached (file by default) 'engines' => [ 'bb_cache' => ['file'], 'bb_config' => ['file'], @@ -71,7 +74,7 @@ $bb_cfg['cache'] = [ ]; // Datastore -// Available datastore types: file, sqlite, memory (file by default) +// Available datastore types: file, sqlite, memory, memcache (file by default) $bb_cfg['datastore_type'] = 'file'; // Server diff --git a/src/Cache/CacheManager.php b/src/Cache/CacheManager.php index b7babb255..32f3cbe40 100644 --- a/src/Cache/CacheManager.php +++ b/src/Cache/CacheManager.php @@ -12,6 +12,7 @@ namespace TorrentPier\Cache; use Nette\Caching\Cache; use Nette\Caching\Storage; use Nette\Caching\Storages\FileStorage; +use Nette\Caching\Storages\MemcachedStorage; use Nette\Caching\Storages\MemoryStorage; use Nette\Caching\Storages\SQLiteStorage; use TorrentPier\Dev; @@ -42,18 +43,6 @@ class CacheManager */ private Storage $storage; - /** - * Cache configuration - * @var array - */ - private array $config; - - /** - * Cache namespace/name - * @var string - */ - private string $namespace; - /** * Cache prefix * @var string @@ -89,16 +78,14 @@ class CacheManager * Constructor * * @param string $namespace + * @param Storage $storage Pre-built storage instance from UnifiedCacheSystem * @param array $config */ - private function __construct(string $namespace, array $config) + private function __construct(string $namespace, Storage $storage, array $config) { - $this->namespace = $namespace; - $this->config = $config; + $this->storage = $storage; $this->prefix = $config['prefix'] ?? 'tp_'; - - // Initialize storage based on configuration - $this->initializeStorage(); + $this->engine = $config['engine'] ?? 'Unknown'; // Create Nette Cache instance with namespace $this->cache = new Cache($this->storage, $namespace); @@ -108,56 +95,24 @@ class CacheManager } /** - * Get singleton instance + * Get singleton instance (called by UnifiedCacheSystem) * * @param string $namespace + * @param Storage $storage Pre-built storage instance * @param array $config * @return self */ - public static function getInstance(string $namespace, array $config): self + public static function getInstance(string $namespace, Storage $storage, array $config): self { $key = $namespace . '_' . md5(serialize($config)); if (!isset(self::$instances[$key])) { - self::$instances[$key] = new self($namespace, $config); + self::$instances[$key] = new self($namespace, $storage, $config); } return self::$instances[$key]; } - /** - * Initialize storage based on configuration - */ - private function initializeStorage(): void - { - $storageType = $this->config['storage_type'] ?? 'file'; - - switch ($storageType) { - case 'file': - $this->engine = 'File'; - $dir = $this->config['db_dir'] ?? sys_get_temp_dir() . '/cache/'; - $this->storage = new FileStorage($dir); - break; - - case 'sqlite': - $this->engine = 'SQLite'; - $dbFile = $this->config['sqlite_path'] ?? sys_get_temp_dir() . '/cache.db'; - $this->storage = new SQLiteStorage($dbFile); - break; - - case 'memory': - $this->engine = 'Memory'; - $this->storage = new MemoryStorage(); - break; - - default: - // Fallback to file storage - $this->engine = 'File'; - $dir = $this->config['db_dir'] ?? sys_get_temp_dir() . '/cache/'; - $this->storage = new FileStorage($dir); - break; - } - } /** * Cache get method (Legacy Cache API) diff --git a/src/Cache/DatastoreManager.php b/src/Cache/DatastoreManager.php index 91efae97e..7e9db853e 100644 --- a/src/Cache/DatastoreManager.php +++ b/src/Cache/DatastoreManager.php @@ -10,6 +10,7 @@ namespace TorrentPier\Cache; use Nette\Caching\Cache; +use Nette\Caching\Storage; use TorrentPier\Dev; /** @@ -93,12 +94,13 @@ class DatastoreManager /** * Constructor * + * @param Storage $storage Pre-built storage instance from UnifiedCacheSystem * @param array $config */ - private function __construct(array $config) + private function __construct(Storage $storage, array $config) { - // Create unified cache manager for datastore - $this->cacheManager = CacheManager::getInstance('datastore', $config); + // Create unified cache manager for datastore with pre-built storage + $this->cacheManager = CacheManager::getInstance('datastore', $storage, $config); $this->engine = $this->cacheManager->engine; $this->dbg_enabled = dev()->checkSqlDebugAllowed(); } @@ -106,13 +108,14 @@ class DatastoreManager /** * Get singleton instance * + * @param Storage $storage Pre-built storage instance * @param array $config * @return self */ - public static function getInstance(array $config): self + public static function getInstance(Storage $storage, array $config): self { if (self::$instance === null) { - self::$instance = new self($config); + self::$instance = new self($storage, $config); } return self::$instance; diff --git a/src/Cache/README.md b/src/Cache/README.md index 5362bac5b..17bdc5aee 100644 --- a/src/Cache/README.md +++ b/src/Cache/README.md @@ -46,6 +46,7 @@ $datastore = datastore(); - ✅ **Advanced Features**: Dependencies, tags, bulk operations, memoization, output buffering - ✅ **Better Debugging**: Unified debug interface with compatibility for Dev.php - ✅ **Performance**: 456,647+ operations per second with efficient memory usage +- ✅ **Clean Architecture**: No redundant configuration logic, single storage creation path ## Usage @@ -190,17 +191,19 @@ $bb_cfg['datastore_type'] = 'file'; // Uses Nette FileStorage | `file` | `FileStorage` | File-based, persistent, dependencies | | `sqlite` | `SQLiteStorage` | Database, supports tags and complex dependencies | | `memory` | `MemoryStorage` | In-memory, fastest, non-persistent | +| `memcached` | `MemcachedStorage` | Distributed memory, high-performance | ### Storage Features Comparison -| Feature | FileStorage | SQLiteStorage | MemoryStorage | -|---------|-------------|---------------|---------------| -| Persistence | ✅ | ✅ | ❌ | -| File Dependencies | ✅ | ✅ | ✅ | -| Tags | ❌ | ✅ | ✅ | -| Callbacks | ✅ | ✅ | ✅ | -| Bulk Operations | ✅ | ✅ | ✅ | -| Performance | High | Medium | Highest | +| Feature | FileStorage | SQLiteStorage | MemoryStorage | MemcachedStorage | +|---------|-------------|---------------|---------------|------------------| +| Persistence | ✅ | ✅ | ❌ | ✅ | +| File Dependencies | ✅ | ✅ | ✅ | ✅ | +| Tags | ❌ | ✅ | ✅ | ❌ | +| Callbacks | ✅ | ✅ | ✅ | ✅ | +| Bulk Operations | ✅ | ✅ | ✅ | ✅ | +| Performance | High | Medium | Highest | Very High | +| Distributed | ❌ | ❌ | ❌ | ✅ | ## Migration Guide @@ -279,13 +282,40 @@ $rendered_page = $cache->capture("page_$page_id", function() { ## Implementation Details +### Architecture Flow (Refactored) + +**Clean, Non-Redundant Architecture:** +``` +UnifiedCacheSystem (singleton) +├── _buildStorage() → Creates Nette Storage instances directly +├── get_cache_obj() → Returns CacheManager with pre-built storage +└── getDatastore() → Returns DatastoreManager with pre-built storage + +CacheManager (receives pre-built Storage) +├── Constructor receives: Storage instance + minimal config +├── No redundant initializeStorage() switch statement +└── Focuses purely on cache operations + +DatastoreManager (uses CacheManager internally) +├── Constructor receives: Storage instance + minimal config +├── Uses CacheManager internally for unified functionality +└── Maintains datastore-specific methods and compatibility +``` + +**Benefits of Refactored Architecture:** +- **Single Source of Truth**: Only UnifiedCacheSystem creates storage instances +- **No Redundancy**: Eliminated duplicate switch statements and configuration parsing +- **Cleaner Separation**: CacheManager focuses on caching, not storage creation +- **Impossible Path Bugs**: Storage is pre-built, no configuration mismatches possible +- **Better Maintainability**: One place to modify storage creation logic + ### Directory Structure ``` src/Cache/ ├── CacheManager.php # Cache interface with Nette Caching + singleton pattern ├── DatastoreManager.php # Datastore interface using CacheManager internally -├── UnifiedCacheSystem.php # Main singleton orchestrator +├── UnifiedCacheSystem.php # Main singleton orchestrator + storage factory └── README.md # This documentation ``` @@ -304,6 +334,8 @@ The following development and testing files were removed after successful integr 4. **Efficient Singletons**: Memory-efficient instance management following TorrentPier patterns 5. **Unified Debugging**: Consistent debug interface compatible with Dev.php 6. **Production Ready**: Comprehensive error handling, validation, and performance optimization +7. **Clean Architecture**: Eliminated redundant configuration logic and switch statements +8. **Single Storage Source**: All storage creation centralized in UnifiedCacheSystem ### Architectural Consistency diff --git a/src/Cache/UnifiedCacheSystem.php b/src/Cache/UnifiedCacheSystem.php index 237dc1474..594910afc 100644 --- a/src/Cache/UnifiedCacheSystem.php +++ b/src/Cache/UnifiedCacheSystem.php @@ -9,6 +9,12 @@ namespace TorrentPier\Cache; +use Nette\Caching\Storage; +use Nette\Caching\Storages\FileStorage; +use Nette\Caching\Storages\MemcachedStorage; +use Nette\Caching\Storages\MemoryStorage; +use Nette\Caching\Storages\SQLiteStorage; + /** * Unified Cache System using Nette Caching * Replaces Legacy Caches class and provides both cache and datastore functionality @@ -81,10 +87,12 @@ class UnifiedCacheSystem $this->cfg = $cfg['cache'] ?? []; // Create stub cache manager - $this->stub = CacheManager::getInstance('__stub', [ - 'storage_type' => 'memory', + $stubStorage = new MemoryStorage(); + $stubConfig = [ + 'engine' => 'Memory', 'prefix' => $this->cfg['prefix'] ?? 'tp_' - ]); + ]; + $this->stub = CacheManager::getInstance('__stub', $stubStorage, $stubConfig); } /** @@ -101,10 +109,16 @@ class UnifiedCacheSystem $this->ref[$cache_name] = $this->stub; } else { $cache_type = $engine_cfg[0] ?? 'file'; - $config = $this->_buildCacheConfig($cache_type, $cache_name); if (!isset($this->managers[$cache_name])) { - $this->managers[$cache_name] = CacheManager::getInstance($cache_name, $config); + // Build storage and config directly + $storage = $this->_buildStorage($cache_type, $cache_name); + $config = [ + 'engine' => $this->_getEngineType($cache_type), + 'prefix' => $this->cfg['prefix'] ?? 'tp_' + ]; + + $this->managers[$cache_name] = CacheManager::getInstance($cache_name, $storage, $config); } $this->ref[$cache_name] = $this->managers[$cache_name]; } @@ -122,94 +136,108 @@ class UnifiedCacheSystem public function getDatastore(string $datastore_type = 'file'): DatastoreManager { if ($this->datastore === null) { - $config = $this->_buildDatastoreConfig($datastore_type); - $this->datastore = DatastoreManager::getInstance($config); + // Build storage and config for datastore + $storage = $this->_buildDatastoreStorage($datastore_type); + $config = [ + 'engine' => $this->_getEngineType($datastore_type), + 'prefix' => $this->cfg['prefix'] ?? 'tp_' + ]; + + $this->datastore = DatastoreManager::getInstance($storage, $config); } return $this->datastore; } /** - * Build cache configuration + * Build storage instance directly (eliminates redundancy with CacheManager) * * @param string $cache_type * @param string $cache_name - * @return array + * @return Storage */ - private function _buildCacheConfig(string $cache_type, string $cache_name): array + private function _buildStorage(string $cache_type, string $cache_name): Storage { - $config = [ - 'prefix' => $this->cfg['prefix'] ?? 'tp_', - ]; - switch ($cache_type) { case 'file': case 'filecache': case 'apcu': - case 'memcached': case 'redis': // Some deprecated cache types will fall back to file storage - $config['storage_type'] = 'file'; - $config['db_dir'] = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/' . $cache_name . '/'; - break; + $dir = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/' . $cache_name . '/'; + return new FileStorage($dir); case 'sqlite': - $config['storage_type'] = 'sqlite'; - $config['sqlite_path'] = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/' . $cache_name . '.db'; - break; + $dbFile = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/' . $cache_name . '.db'; + return new SQLiteStorage($dbFile); case 'memory': - $config['storage_type'] = 'memory'; - break; + return new MemoryStorage(); + + case 'memcached': + $memcachedConfig = $this->cfg['memcached'] ?? ['host' => '127.0.0.1', 'port' => 11211]; + $host = $memcachedConfig['host'] ?? '127.0.0.1'; + $port = $memcachedConfig['port'] ?? 11211; + return new MemcachedStorage("{$host}:{$port}"); default: - $config['storage_type'] = 'file'; - $config['db_dir'] = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/' . $cache_name . '/'; - break; + // Fallback to file storage + $dir = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/' . $cache_name . '/'; + return new FileStorage($dir); } - - return $config; } /** - * Build datastore configuration + * Get engine type name for debugging + * + * @param string $cache_type + * @return string + */ + private function _getEngineType(string $cache_type): string + { + return match ($cache_type) { + 'sqlite' => 'SQLite', + 'memory' => 'Memory', + 'memcached' => 'Memcached', + default => 'File', + }; + } + + /** + * Build datastore storage instance * * @param string $datastore_type - * @return array + * @return Storage */ - private function _buildDatastoreConfig(string $datastore_type): array + private function _buildDatastoreStorage(string $datastore_type): Storage { - $config = [ - 'prefix' => $this->cfg['prefix'] ?? 'tp_', - ]; - switch ($datastore_type) { case 'file': case 'filecache': case 'apcu': - case 'memcached': case 'redis': // Some deprecated cache types will fall back to file storage - $config['storage_type'] = 'file'; - $config['db_dir'] = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/datastore/'; - break; + $dir = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/datastore/'; + return new FileStorage($dir); case 'sqlite': - $config['storage_type'] = 'sqlite'; - $config['sqlite_path'] = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/datastore.db'; - break; + $dbFile = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/datastore.db'; + return new SQLiteStorage($dbFile); case 'memory': - $config['storage_type'] = 'memory'; - break; + return new MemoryStorage(); + + case 'memcached': + $memcachedConfig = $this->cfg['memcached'] ?? ['host' => '127.0.0.1', 'port' => 11211]; + $host = $memcachedConfig['host'] ?? '127.0.0.1'; + $port = $memcachedConfig['port'] ?? 11211; + return new MemcachedStorage("{$host}:{$port}"); default: - $config['storage_type'] = 'file'; - $config['db_dir'] = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/datastore/'; - break; + // Fallback to file storage + $dir = rtrim($this->cfg['db_dir'] ?? sys_get_temp_dir() . '/cache/', '/') . '/datastore/'; + return new FileStorage($dir); } - - return $config; } /** @@ -323,7 +351,15 @@ class UnifiedCacheSystem $fullConfig = array_merge($this->cfg, $config); $fullConfig['prefix'] = $fullConfig['prefix'] ?? 'tp_'; - return CacheManager::getInstance($namespace, $fullConfig); + // Build storage for the advanced cache + $storageType = $config['storage_type'] ?? 'file'; + $storage = $this->_buildStorage($storageType, $namespace); + $managerConfig = [ + 'engine' => $this->_getEngineType($storageType), + 'prefix' => $fullConfig['prefix'] + ]; + + return CacheManager::getInstance($namespace, $storage, $managerConfig); } /** @@ -353,10 +389,14 @@ class UnifiedCacheSystem */ public function createTaggedCache(string $namespace): CacheManager { - $config = $this->cfg; - $config['storage_type'] = 'sqlite'; // SQLite supports tags via journal + // Use SQLite storage which supports tags via journal + $storage = $this->_buildStorage('sqlite', $namespace); + $config = [ + 'engine' => 'SQLite', + 'prefix' => $this->cfg['prefix'] ?? 'tp_' + ]; - return CacheManager::getInstance($namespace, $config); + return CacheManager::getInstance($namespace, $storage, $config); } /**