471 lines
12 KiB
PHP
471 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* Copyright seit 2024 Webshop System
|
|
*
|
|
* Cache-Core-Klasse für das Webshop-System
|
|
*
|
|
* @author Webshop System
|
|
* @license GPL v3
|
|
*/
|
|
|
|
namespace App\Core;
|
|
|
|
use Redis;
|
|
use Exception;
|
|
|
|
class Cache
|
|
{
|
|
private $redis;
|
|
private $fileCachePath;
|
|
private $defaultTtl = 3600; // 1 Stunde
|
|
private $cacheEnabled = true;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->fileCachePath = __DIR__ . '/../../cache/';
|
|
$this->initRedis();
|
|
}
|
|
|
|
/**
|
|
* Redis-Verbindung initialisieren
|
|
*/
|
|
private function initRedis()
|
|
{
|
|
try {
|
|
$this->redis = new Redis();
|
|
$this->redis->connect(
|
|
getenv('REDIS_HOST') ?: 'redis',
|
|
getenv('REDIS_PORT') ?: 6379
|
|
);
|
|
|
|
if (getenv('REDIS_PASSWORD')) {
|
|
$this->redis->auth(getenv('REDIS_PASSWORD'));
|
|
}
|
|
|
|
$this->redis->select(getenv('REDIS_DB') ?: 0);
|
|
} catch (Exception $e) {
|
|
// Redis nicht verfügbar, verwende File-Cache
|
|
$this->redis = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cache-Schlüssel generieren
|
|
*/
|
|
private function generateKey($key, $prefix = 'ws')
|
|
{
|
|
return $prefix . ':' . md5($key);
|
|
}
|
|
|
|
/**
|
|
* Wert im Cache speichern
|
|
*/
|
|
public function set($key, $value, $ttl = null)
|
|
{
|
|
if (!$this->cacheEnabled) {
|
|
return false;
|
|
}
|
|
|
|
$ttl = $ttl ?: $this->defaultTtl;
|
|
$cacheKey = $this->generateKey($key);
|
|
|
|
try {
|
|
if ($this->redis) {
|
|
// Redis-Cache
|
|
$serializedValue = serialize($value);
|
|
return $this->redis->setex($cacheKey, $ttl, $serializedValue);
|
|
} else {
|
|
// File-Cache
|
|
return $this->setFileCache($cacheKey, $value, $ttl);
|
|
}
|
|
} catch (Exception $e) {
|
|
// Fallback zu File-Cache
|
|
return $this->setFileCache($cacheKey, $value, $ttl);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wert aus dem Cache abrufen
|
|
*/
|
|
public function get($key)
|
|
{
|
|
if (!$this->cacheEnabled) {
|
|
return false;
|
|
}
|
|
|
|
$cacheKey = $this->generateKey($key);
|
|
|
|
try {
|
|
if ($this->redis) {
|
|
// Redis-Cache
|
|
$value = $this->redis->get($cacheKey);
|
|
return $value ? unserialize($value) : false;
|
|
} else {
|
|
// File-Cache
|
|
return $this->getFileCache($cacheKey);
|
|
}
|
|
} catch (Exception $e) {
|
|
// Fallback zu File-Cache
|
|
return $this->getFileCache($cacheKey);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wert aus dem Cache löschen
|
|
*/
|
|
public function delete($key)
|
|
{
|
|
$cacheKey = $this->generateKey($key);
|
|
|
|
try {
|
|
if ($this->redis) {
|
|
// Redis-Cache
|
|
return $this->redis->del($cacheKey);
|
|
} else {
|
|
// File-Cache
|
|
return $this->deleteFileCache($cacheKey);
|
|
}
|
|
} catch (Exception $e) {
|
|
// Fallback zu File-Cache
|
|
return $this->deleteFileCache($cacheKey);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cache komplett leeren
|
|
*/
|
|
public function clear($pattern = null)
|
|
{
|
|
try {
|
|
if ($this->redis) {
|
|
// Redis-Cache
|
|
if ($pattern) {
|
|
$keys = $this->redis->keys($this->generateKey($pattern, 'ws*'));
|
|
if (!empty($keys)) {
|
|
return $this->redis->del($keys);
|
|
}
|
|
} else {
|
|
return $this->redis->flushDB();
|
|
}
|
|
} else {
|
|
// File-Cache
|
|
return $this->clearFileCache($pattern);
|
|
}
|
|
} catch (Exception $e) {
|
|
return $this->clearFileCache($pattern);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cache-Status prüfen
|
|
*/
|
|
public function has($key)
|
|
{
|
|
$cacheKey = $this->generateKey($key);
|
|
|
|
try {
|
|
if ($this->redis) {
|
|
// Redis-Cache
|
|
return $this->redis->exists($cacheKey);
|
|
} else {
|
|
// File-Cache
|
|
return $this->hasFileCache($cacheKey);
|
|
}
|
|
} catch (Exception $e) {
|
|
return $this->hasFileCache($cacheKey);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* File-Cache: Wert speichern
|
|
*/
|
|
private function setFileCache($key, $value, $ttl)
|
|
{
|
|
$filename = $this->fileCachePath . $key . '.cache';
|
|
$data = [
|
|
'value' => $value,
|
|
'expires' => time() + $ttl
|
|
];
|
|
|
|
if (!is_dir($this->fileCachePath)) {
|
|
mkdir($this->fileCachePath, 0755, true);
|
|
}
|
|
|
|
return file_put_contents($filename, serialize($data)) !== false;
|
|
}
|
|
|
|
/**
|
|
* File-Cache: Wert abrufen
|
|
*/
|
|
private function getFileCache($key)
|
|
{
|
|
$filename = $this->fileCachePath . $key . '.cache';
|
|
|
|
if (!file_exists($filename)) {
|
|
return false;
|
|
}
|
|
|
|
$data = unserialize(file_get_contents($filename));
|
|
|
|
if (!$data || !isset($data['expires']) || $data['expires'] < time()) {
|
|
// Cache abgelaufen
|
|
unlink($filename);
|
|
return false;
|
|
}
|
|
|
|
return $data['value'];
|
|
}
|
|
|
|
/**
|
|
* File-Cache: Wert löschen
|
|
*/
|
|
private function deleteFileCache($key)
|
|
{
|
|
$filename = $this->fileCachePath . $key . '.cache';
|
|
|
|
if (file_exists($filename)) {
|
|
return unlink($filename);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* File-Cache: Status prüfen
|
|
*/
|
|
private function hasFileCache($key)
|
|
{
|
|
$filename = $this->fileCachePath . $key . '.cache';
|
|
|
|
if (!file_exists($filename)) {
|
|
return false;
|
|
}
|
|
|
|
$data = unserialize(file_get_contents($filename));
|
|
|
|
if (!$data || !isset($data['expires']) || $data['expires'] < time()) {
|
|
// Cache abgelaufen
|
|
unlink($filename);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* File-Cache: Komplett leeren
|
|
*/
|
|
private function clearFileCache($pattern = null)
|
|
{
|
|
if (!is_dir($this->fileCachePath)) {
|
|
return true;
|
|
}
|
|
|
|
$files = glob($this->fileCachePath . '*.cache');
|
|
$deleted = 0;
|
|
|
|
foreach ($files as $file) {
|
|
if ($pattern) {
|
|
// Nur Dateien löschen, die dem Pattern entsprechen
|
|
if (strpos(basename($file), md5($pattern)) !== false) {
|
|
unlink($file);
|
|
$deleted++;
|
|
}
|
|
} else {
|
|
// Alle Cache-Dateien löschen
|
|
unlink($file);
|
|
$deleted++;
|
|
}
|
|
}
|
|
|
|
return $deleted;
|
|
}
|
|
|
|
/**
|
|
* Cache-Statistiken abrufen
|
|
*/
|
|
public function getStats()
|
|
{
|
|
$stats = [
|
|
'enabled' => $this->cacheEnabled,
|
|
'driver' => $this->redis ? 'redis' : 'file',
|
|
'file_cache_path' => $this->fileCachePath,
|
|
'default_ttl' => $this->defaultTtl
|
|
];
|
|
|
|
if ($this->redis) {
|
|
try {
|
|
$stats['redis_info'] = $this->redis->info();
|
|
$stats['redis_keys'] = $this->redis->dbSize();
|
|
} catch (Exception $e) {
|
|
$stats['redis_error'] = $e->getMessage();
|
|
}
|
|
} else {
|
|
$stats['file_cache_files'] = count(glob($this->fileCachePath . '*.cache'));
|
|
}
|
|
|
|
return $stats;
|
|
}
|
|
|
|
/**
|
|
* Cache aktivieren/deaktivieren
|
|
*/
|
|
public function setEnabled($enabled)
|
|
{
|
|
$this->cacheEnabled = $enabled;
|
|
}
|
|
|
|
/**
|
|
* Cache-Schlüssel mit Tags
|
|
*/
|
|
public function setWithTags($key, $value, $tags = [], $ttl = null)
|
|
{
|
|
$result = $this->set($key, $value, $ttl);
|
|
|
|
if ($result && !empty($tags)) {
|
|
foreach ($tags as $tag) {
|
|
$tagKey = 'tag:' . $tag;
|
|
$taggedKeys = $this->get($tagKey) ?: [];
|
|
$taggedKeys[] = $key;
|
|
$this->set($tagKey, $taggedKeys, $ttl);
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Cache mit Tags löschen
|
|
*/
|
|
public function deleteByTags($tags)
|
|
{
|
|
$deleted = 0;
|
|
|
|
foreach ($tags as $tag) {
|
|
$tagKey = 'tag:' . $tag;
|
|
$taggedKeys = $this->get($tagKey) ?: [];
|
|
|
|
foreach ($taggedKeys as $key) {
|
|
if ($this->delete($key)) {
|
|
$deleted++;
|
|
}
|
|
}
|
|
|
|
$this->delete($tagKey);
|
|
}
|
|
|
|
return $deleted;
|
|
}
|
|
|
|
/**
|
|
* Cache-Warmup für häufig verwendete Daten
|
|
*/
|
|
public function warmup()
|
|
{
|
|
// Kategorien cachen
|
|
$this->cacheCategories();
|
|
|
|
// Konfiguration cachen
|
|
$this->cacheConfiguration();
|
|
|
|
// Sprachen cachen
|
|
$this->cacheLanguages();
|
|
}
|
|
|
|
/**
|
|
* Kategorien cachen
|
|
*/
|
|
private function cacheCategories()
|
|
{
|
|
try {
|
|
$connectionParams = [
|
|
'dbname' => getenv('DB_DATABASE') ?: 'freeshop',
|
|
'user' => getenv('DB_USERNAME') ?: 'freeshop_user',
|
|
'password' => getenv('DB_PASSWORD') ?: 'freeshop_password',
|
|
'host' => getenv('DB_HOST') ?: 'db',
|
|
'driver' => 'pdo_mysql',
|
|
'port' => getenv('DB_PORT') ?: 3306,
|
|
'charset' => 'utf8mb4',
|
|
];
|
|
|
|
$conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams);
|
|
|
|
$stmt = $conn->prepare('SELECT * FROM ws_category WHERE active = 1 ORDER BY sort_order ASC');
|
|
$stmt->execute();
|
|
$categories = $stmt->fetchAllAssociative();
|
|
|
|
$this->set('categories:all', $categories, 3600);
|
|
|
|
// Einzelne Kategorien cachen
|
|
foreach ($categories as $category) {
|
|
$this->set('category:' . $category['id'], $category, 3600);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
// Ignore cache warmup errors
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Konfiguration cachen
|
|
*/
|
|
private function cacheConfiguration()
|
|
{
|
|
try {
|
|
$connectionParams = [
|
|
'dbname' => getenv('DB_DATABASE') ?: 'freeshop',
|
|
'user' => getenv('DB_USERNAME') ?: 'freeshop_user',
|
|
'password' => getenv('DB_PASSWORD') ?: 'freeshop_password',
|
|
'host' => getenv('DB_HOST') ?: 'db',
|
|
'driver' => 'pdo_mysql',
|
|
'port' => getenv('DB_PORT') ?: 3306,
|
|
'charset' => 'utf8mb4',
|
|
];
|
|
|
|
$conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams);
|
|
|
|
$stmt = $conn->prepare('SELECT name, value FROM ws_configuration');
|
|
$stmt->execute();
|
|
$config = $stmt->fetchAllAssociative();
|
|
|
|
$configArray = [];
|
|
foreach ($config as $item) {
|
|
$configArray[$item['name']] = $item['value'];
|
|
}
|
|
|
|
$this->set('config:all', $configArray, 7200);
|
|
|
|
} catch (Exception $e) {
|
|
// Ignore cache warmup errors
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sprachen cachen
|
|
*/
|
|
private function cacheLanguages()
|
|
{
|
|
try {
|
|
$connectionParams = [
|
|
'dbname' => getenv('DB_DATABASE') ?: 'freeshop',
|
|
'user' => getenv('DB_USERNAME') ?: 'freeshop_user',
|
|
'password' => getenv('DB_PASSWORD') ?: 'freeshop_password',
|
|
'host' => getenv('DB_HOST') ?: 'db',
|
|
'driver' => 'pdo_mysql',
|
|
'port' => getenv('DB_PORT') ?: 3306,
|
|
'charset' => 'utf8mb4',
|
|
];
|
|
|
|
$conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams);
|
|
|
|
$stmt = $conn->prepare('SELECT * FROM ws_language WHERE active = 1');
|
|
$stmt->execute();
|
|
$languages = $stmt->fetchAllAssociative();
|
|
|
|
$this->set('languages:all', $languages, 7200);
|
|
|
|
} catch (Exception $e) {
|
|
// Ignore cache warmup errors
|
|
}
|
|
}
|
|
}
|