1124 lines
28 KiB
PHP
1124 lines
28 KiB
PHP
<?php
|
|
/**
|
|
* Copyright seit 2024 Webshop System
|
|
*
|
|
* Cache-System für PrestaShop-Modul-Kompatibilität
|
|
*
|
|
* @author Webshop System
|
|
* @license GPL v3
|
|
*/
|
|
|
|
namespace App\Core;
|
|
|
|
use Doctrine\DBAL\DriverManager;
|
|
use Doctrine\DBAL\Exception;
|
|
|
|
class Cache
|
|
{
|
|
private static $instance = null;
|
|
private $drivers = [];
|
|
private $defaultDriver = 'file';
|
|
private $enabled = true;
|
|
private $statistics = [];
|
|
|
|
private function __construct()
|
|
{
|
|
$this->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()
|
|
];
|
|
}
|
|
}
|
|
}
|