initializeDrivers(); } /** * Singleton-Instanz abrufen */ public static function getInstance() { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } /** * Cache-Drivers initialisieren */ private function initializeDrivers() { // File-Cache Driver $this->drivers['file'] = new FileCacheDriver(); // Redis-Cache Driver (falls verfügbar) if (extension_loaded('redis')) { $this->drivers['redis'] = new RedisCacheDriver(); } // Memcached-Cache Driver (falls verfügbar) if (extension_loaded('memcached')) { $this->drivers['memcached'] = new MemcachedCacheDriver(); } // Database-Cache Driver $this->drivers['database'] = new DatabaseCacheDriver(); } /** * Cache-Wert abrufen */ public function get($key, $default = null, $driver = null) { if (!$this->enabled) { return $default; } $driver = $driver ?: $this->defaultDriver; if (!isset($this->drivers[$driver])) { return $default; } $startTime = microtime(true); try { $value = $this->drivers[$driver]->get($key); $endTime = microtime(true); $this->updateStatistics($driver, 'get', $endTime - $startTime, $value !== null); return $value !== null ? $value : $default; } catch (\Exception $e) { $this->logCacheError($driver, 'get', $key, $e); return $default; } } /** * Cache-Wert setzen */ public function set($key, $value, $ttl = 3600, $driver = null) { if (!$this->enabled) { return false; } $driver = $driver ?: $this->defaultDriver; if (!isset($this->drivers[$driver])) { return false; } $startTime = microtime(true); try { $result = $this->drivers[$driver]->set($key, $value, $ttl); $endTime = microtime(true); $this->updateStatistics($driver, 'set', $endTime - $startTime, $result); return $result; } catch (\Exception $e) { $this->logCacheError($driver, 'set', $key, $e); return false; } } /** * Cache-Wert löschen */ public function delete($key, $driver = null) { if (!$this->enabled) { return false; } $driver = $driver ?: $this->defaultDriver; if (!isset($this->drivers[$driver])) { return false; } $startTime = microtime(true); try { $result = $this->drivers[$driver]->delete($key); $endTime = microtime(true); $this->updateStatistics($driver, 'delete', $endTime - $startTime, $result); return $result; } catch (\Exception $e) { $this->logCacheError($driver, 'delete', $key, $e); return false; } } /** * Cache-Wert prüfen */ public function has($key, $driver = null) { if (!$this->enabled) { return false; } $driver = $driver ?: $this->defaultDriver; if (!isset($this->drivers[$driver])) { return false; } try { return $this->drivers[$driver]->has($key); } catch (\Exception $e) { $this->logCacheError($driver, 'has', $key, $e); return false; } } /** * Cache leeren */ public function clear($driver = null) { if (!$this->enabled) { return false; } $driver = $driver ?: $this->defaultDriver; if (!isset($this->drivers[$driver])) { return false; } try { return $this->drivers[$driver]->clear(); } catch (\Exception $e) { $this->logCacheError($driver, 'clear', '', $e); return false; } } /** * Cache-Tags verwenden */ public function getByTag($tag, $driver = null) { if (!$this->enabled) { return []; } $driver = $driver ?: $this->defaultDriver; if (!isset($this->drivers[$driver])) { return []; } try { return $this->drivers[$driver]->getByTag($tag); } catch (\Exception $e) { $this->logCacheError($driver, 'getByTag', $tag, $e); return []; } } /** * Cache-Tags löschen */ public function deleteByTag($tag, $driver = null) { if (!$this->enabled) { return false; } $driver = $driver ?: $this->defaultDriver; if (!isset($this->drivers[$driver])) { return false; } try { return $this->drivers[$driver]->deleteByTag($tag); } catch (\Exception $e) { $this->logCacheError($driver, 'deleteByTag', $tag, $e); return false; } } /** * Cache-Wert mit Tags setzen */ public function setWithTags($key, $value, $tags = [], $ttl = 3600, $driver = null) { if (!$this->enabled) { return false; } $driver = $driver ?: $this->defaultDriver; if (!isset($this->drivers[$driver])) { return false; } try { return $this->drivers[$driver]->setWithTags($key, $value, $tags, $ttl); } catch (\Exception $e) { $this->logCacheError($driver, 'setWithTags', $key, $e); return false; } } /** * Cache-Warmup */ public function warmup($keys = []) { if (!$this->enabled) { return false; } $results = []; foreach ($this->drivers as $driverName => $driver) { try { $results[$driverName] = $driver->warmup($keys); } catch (\Exception $e) { $this->logCacheError($driverName, 'warmup', '', $e); $results[$driverName] = false; } } return $results; } /** * Cache-Statistiken abrufen */ public function getStatistics($driver = null) { if ($driver) { return isset($this->statistics[$driver]) ? $this->statistics[$driver] : []; } return $this->statistics; } /** * Cache-Status abrufen */ public function getStatus($driver = null) { if ($driver) { return isset($this->drivers[$driver]) ? $this->drivers[$driver]->getStatus() : false; } $status = []; foreach ($this->drivers as $driverName => $driver) { $status[$driverName] = $driver->getStatus(); } return $status; } /** * Cache aktivieren/deaktivieren */ public function setEnabled($enabled) { $this->enabled = $enabled; return $this; } /** * Cache-Status prüfen */ public function isEnabled() { return $this->enabled; } /** * Standard-Driver setzen */ public function setDefaultDriver($driver) { if (isset($this->drivers[$driver])) { $this->defaultDriver = $driver; } return $this; } /** * Verfügbare Drivers abrufen */ public function getAvailableDrivers() { return array_keys($this->drivers); } /** * Cache zurücksetzen */ public function reset() { foreach ($this->drivers as $driver) { $driver->clear(); } $this->statistics = []; return $this; } /** * Statistiken aktualisieren */ private function updateStatistics($driver, $operation, $time, $success) { if (!isset($this->statistics[$driver])) { $this->statistics[$driver] = [ 'operations' => [], 'total_operations' => 0, 'total_time' => 0, 'avg_time' => 0, 'hits' => 0, 'misses' => 0 ]; } $stats = &$this->statistics[$driver]; if (!isset($stats['operations'][$operation])) { $stats['operations'][$operation] = [ 'count' => 0, 'total_time' => 0, 'avg_time' => 0, 'success_count' => 0 ]; } $stats['total_operations']++; $stats['total_time'] += $time; $stats['avg_time'] = $stats['total_time'] / $stats['total_operations']; $stats['operations'][$operation]['count']++; $stats['operations'][$operation]['total_time'] += $time; $stats['operations'][$operation]['avg_time'] = $stats['operations'][$operation]['total_time'] / $stats['operations'][$operation]['count']; if ($success) { $stats['operations'][$operation]['success_count']++; if ($operation === 'get') { $stats['hits']++; } } else { if ($operation === 'get') { $stats['misses']++; } } } /** * Cache-Fehler loggen */ private function logCacheError($driver, $operation, $key, $exception) { $errorMessage = sprintf( 'Cache-Fehler: Driver=%s, Operation=%s, Key=%s, Fehler=%s', $driver, $operation, $key, $exception->getMessage() ); error_log($errorMessage); // Fehler in Datenbank loggen $this->logCacheErrorToDatabase($driver, $operation, $key, $exception); } /** * Cache-Fehler in Datenbank loggen */ private function logCacheErrorToDatabase($driver, $operation, $key, $exception) { try { $conn = DriverManager::getConnection([ 'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop' ]); $stmt = $conn->prepare(' INSERT INTO ws_cache_errors ( driver_name, operation, cache_key, error_message, error_trace, created_at ) VALUES (?, ?, ?, ?, ?, NOW()) '); $stmt->execute([ $driver, $operation, $key, $exception->getMessage(), $exception->getTraceAsString() ]); } catch (Exception $e) { error_log('Cache-Error-Log Fehler: ' . $e->getMessage()); } } } /** * File-Cache Driver */ class FileCacheDriver { private $cacheDir; public function __construct() { $this->cacheDir = __DIR__ . '/../../../cache/file/'; if (!is_dir($this->cacheDir)) { mkdir($this->cacheDir, 0755, true); } } public function get($key) { $filename = $this->getFilename($key); if (!file_exists($filename)) { return null; } $data = unserialize(file_get_contents($filename)); if ($data['expires'] > 0 && time() > $data['expires']) { unlink($filename); return null; } return $data['value']; } public function set($key, $value, $ttl = 3600) { $filename = $this->getFilename($key); $data = [ 'value' => $value, 'expires' => $ttl > 0 ? time() + $ttl : 0, 'created' => time() ]; return file_put_contents($filename, serialize($data)) !== false; } public function delete($key) { $filename = $this->getFilename($key); if (file_exists($filename)) { return unlink($filename); } return true; } public function has($key) { $filename = $this->getFilename($key); if (!file_exists($filename)) { return false; } $data = unserialize(file_get_contents($filename)); if ($data['expires'] > 0 && time() > $data['expires']) { unlink($filename); return false; } return true; } public function clear() { $files = glob($this->cacheDir . '*'); foreach ($files as $file) { if (is_file($file)) { unlink($file); } } return true; } public function getByTag($tag) { $files = glob($this->cacheDir . '*'); $results = []; foreach ($files as $file) { if (is_file($file)) { $data = unserialize(file_get_contents($file)); if (isset($data['tags']) && in_array($tag, $data['tags'])) { $key = basename($file, '.cache'); $results[$key] = $data['value']; } } } return $results; } public function deleteByTag($tag) { $files = glob($this->cacheDir . '*'); $deleted = 0; foreach ($files as $file) { if (is_file($file)) { $data = unserialize(file_get_contents($file)); if (isset($data['tags']) && in_array($tag, $data['tags'])) { unlink($file); $deleted++; } } } return $deleted > 0; } public function setWithTags($key, $value, $tags = [], $ttl = 3600) { $filename = $this->getFilename($key); $data = [ 'value' => $value, 'tags' => $tags, 'expires' => $ttl > 0 ? time() + $ttl : 0, 'created' => time() ]; return file_put_contents($filename, serialize($data)) !== false; } public function warmup($keys = []) { // File-Cache benötigt kein Warmup return true; } public function getStatus() { return [ 'driver' => 'file', 'enabled' => is_dir($this->cacheDir), 'directory' => $this->cacheDir, 'files_count' => count(glob($this->cacheDir . '*')), 'size' => $this->getDirectorySize($this->cacheDir) ]; } private function getFilename($key) { return $this->cacheDir . md5($key) . '.cache'; } private function getDirectorySize($dir) { $size = 0; $files = glob($dir . '*'); foreach ($files as $file) { if (is_file($file)) { $size += filesize($file); } } return $size; } } /** * Redis-Cache Driver */ class RedisCacheDriver { private $redis; public function __construct() { $this->redis = new \Redis(); try { $this->redis->connect( getenv('REDIS_HOST') ?: 'localhost', getenv('REDIS_PORT') ?: 6379 ); if (getenv('REDIS_PASSWORD')) { $this->redis->auth(getenv('REDIS_PASSWORD')); } } catch (\Exception $e) { error_log('Redis-Verbindung fehlgeschlagen: ' . $e->getMessage()); } } public function get($key) { try { $value = $this->redis->get($key); return $value !== false ? unserialize($value) : null; } catch (\Exception $e) { return null; } } public function set($key, $value, $ttl = 3600) { try { $serialized = serialize($value); return $ttl > 0 ? $this->redis->setex($key, $ttl, $serialized) : $this->redis->set($key, $serialized); } catch (\Exception $e) { return false; } } public function delete($key) { try { return $this->redis->del($key) > 0; } catch (\Exception $e) { return false; } } public function has($key) { try { return $this->redis->exists($key); } catch (\Exception $e) { return false; } } public function clear() { try { return $this->redis->flushDB(); } catch (\Exception $e) { return false; } } public function getByTag($tag) { try { $keys = $this->redis->keys('tag:' . $tag . ':*'); $results = []; foreach ($keys as $key) { $cacheKey = str_replace('tag:' . $tag . ':', '', $key); $value = $this->get($cacheKey); if ($value !== null) { $results[$cacheKey] = $value; } } return $results; } catch (\Exception $e) { return []; } } public function deleteByTag($tag) { try { $keys = $this->redis->keys('tag:' . $tag . ':*'); foreach ($keys as $key) { $cacheKey = str_replace('tag:' . $tag . ':', '', $key); $this->delete($cacheKey); $this->redis->del($key); } return true; } catch (\Exception $e) { return false; } } public function setWithTags($key, $value, $tags = [], $ttl = 3600) { try { $result = $this->set($key, $value, $ttl); if ($result) { foreach ($tags as $tag) { $this->redis->set('tag:' . $tag . ':' . $key, 1, $ttl); } } return $result; } catch (\Exception $e) { return false; } } public function warmup($keys = []) { // Redis benötigt kein Warmup return true; } public function getStatus() { try { $info = $this->redis->info(); return [ 'driver' => 'redis', 'enabled' => $this->redis->ping() === '+PONG', 'version' => $info['redis_version'] ?? 'unknown', 'memory_used' => $info['used_memory_human'] ?? 'unknown', 'connected_clients' => $info['connected_clients'] ?? 0 ]; } catch (\Exception $e) { return [ 'driver' => 'redis', 'enabled' => false, 'error' => $e->getMessage() ]; } } } /** * Memcached-Cache Driver */ class MemcachedCacheDriver { private $memcached; public function __construct() { $this->memcached = new \Memcached(); try { $this->memcached->addServer( getenv('MEMCACHED_HOST') ?: 'localhost', getenv('MEMCACHED_PORT') ?: 11211 ); } catch (\Exception $e) { error_log('Memcached-Verbindung fehlgeschlagen: ' . $e->getMessage()); } } public function get($key) { try { $value = $this->memcached->get($key); return $value !== false ? $value : null; } catch (\Exception $e) { return null; } } public function set($key, $value, $ttl = 3600) { try { return $this->memcached->set($key, $value, $ttl); } catch (\Exception $e) { return false; } } public function delete($key) { try { return $this->memcached->delete($key); } catch (\Exception $e) { return false; } } public function has($key) { try { return $this->memcached->get($key) !== false; } catch (\Exception $e) { return false; } } public function clear() { try { return $this->memcached->flush(); } catch (\Exception $e) { return false; } } public function getByTag($tag) { // Memcached unterstützt keine Tags nativ return []; } public function deleteByTag($tag) { // Memcached unterstützt keine Tags nativ return false; } public function setWithTags($key, $value, $tags = [], $ttl = 3600) { // Memcached unterstützt keine Tags nativ return $this->set($key, $value, $ttl); } public function warmup($keys = []) { // Memcached benötigt kein Warmup return true; } public function getStatus() { try { $stats = $this->memcached->getStats(); return [ 'driver' => 'memcached', 'enabled' => !empty($stats), 'servers' => count($stats), 'memory_used' => $stats['memory'] ?? 'unknown' ]; } catch (\Exception $e) { return [ 'driver' => 'memcached', 'enabled' => false, 'error' => $e->getMessage() ]; } } } /** * Database-Cache Driver */ class DatabaseCacheDriver { private $conn; public function __construct() { try { $this->conn = DriverManager::getConnection([ 'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop' ]); } catch (Exception $e) { error_log('Database-Cache-Verbindung fehlgeschlagen: ' . $e->getMessage()); } } public function get($key) { try { $stmt = $this->conn->prepare(' SELECT cache_value, expires_at FROM ws_cache WHERE cache_key = ? AND (expires_at IS NULL OR expires_at > NOW()) '); $stmt->execute([$key]); $result = $stmt->fetchAssociative(); if ($result) { return unserialize($result['cache_value']); } return null; } catch (Exception $e) { return null; } } public function set($key, $value, $ttl = 3600) { try { $expiresAt = $ttl > 0 ? date('Y-m-d H:i:s', time() + $ttl) : null; $stmt = $this->conn->prepare(' INSERT INTO ws_cache (cache_key, cache_value, expires_at, created_at) VALUES (?, ?, ?, NOW()) ON DUPLICATE KEY UPDATE cache_value = ?, expires_at = ?, updated_at = NOW() '); $serialized = serialize($value); $stmt->execute([$key, $serialized, $expiresAt, $serialized, $expiresAt]); return true; } catch (Exception $e) { return false; } } public function delete($key) { try { $stmt = $this->conn->prepare('DELETE FROM ws_cache WHERE cache_key = ?'); $stmt->execute([$key]); return $stmt->rowCount() > 0; } catch (Exception $e) { return false; } } public function has($key) { try { $stmt = $this->conn->prepare(' SELECT 1 FROM ws_cache WHERE cache_key = ? AND (expires_at IS NULL OR expires_at > NOW()) '); $stmt->execute([$key]); return $stmt->rowCount() > 0; } catch (Exception $e) { return false; } } public function clear() { try { $stmt = $this->conn->prepare('DELETE FROM ws_cache'); $stmt->execute(); return true; } catch (Exception $e) { return false; } } public function getByTag($tag) { try { $stmt = $this->conn->prepare(' SELECT c.cache_key, c.cache_value FROM ws_cache c JOIN ws_cache_tags ct ON c.cache_key = ct.cache_key WHERE ct.tag_name = ? AND (c.expires_at IS NULL OR c.expires_at > NOW()) '); $stmt->execute([$tag]); $results = []; while ($row = $stmt->fetchAssociative()) { $results[$row['cache_key']] = unserialize($row['cache_value']); } return $results; } catch (Exception $e) { return []; } } public function deleteByTag($tag) { try { $stmt = $this->conn->prepare(' DELETE c FROM ws_cache c JOIN ws_cache_tags ct ON c.cache_key = ct.cache_key WHERE ct.tag_name = ? '); $stmt->execute([$tag]); return $stmt->rowCount() > 0; } catch (Exception $e) { return false; } } public function setWithTags($key, $value, $tags = [], $ttl = 3600) { try { $this->conn->beginTransaction(); // Cache-Wert setzen $result = $this->set($key, $value, $ttl); if ($result) { // Tags setzen foreach ($tags as $tag) { $stmt = $this->conn->prepare(' INSERT INTO ws_cache_tags (cache_key, tag_name, created_at) VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE updated_at = NOW() '); $stmt->execute([$key, $tag]); } } $this->conn->commit(); return $result; } catch (Exception $e) { $this->conn->rollBack(); return false; } } public function warmup($keys = []) { // Database-Cache benötigt kein Warmup return true; } public function getStatus() { try { $stmt = $this->conn->prepare(' SELECT COUNT(*) as total_entries, SUM(LENGTH(cache_value)) as total_size, COUNT(CASE WHEN expires_at IS NOT NULL AND expires_at <= NOW() THEN 1 END) as expired_entries FROM ws_cache '); $stmt->execute(); $stats = $stmt->fetchAssociative(); return [ 'driver' => 'database', 'enabled' => true, 'total_entries' => $stats['total_entries'] ?? 0, 'total_size' => $stats['total_size'] ?? 0, 'expired_entries' => $stats['expired_entries'] ?? 0 ]; } catch (Exception $e) { return [ 'driver' => 'database', 'enabled' => false, 'error' => $e->getMessage() ]; } } }