chore: complete update
This commit is contained in:
69
.archive/Archived/CacheCapability.php
Normal file
69
.archive/Archived/CacheCapability.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Archive\Archived;
|
||||
|
||||
|
||||
final readonly class CacheCapability
|
||||
{
|
||||
public function __construct(
|
||||
public bool $canBeCachedFully,
|
||||
public bool $canBeCachedPartially,
|
||||
public bool $supportsFragments,
|
||||
public CacheComplexity $complexity,
|
||||
public array $restrictions = [],
|
||||
) {}
|
||||
|
||||
public static function fromTemplateContent(TemplateContent $content): self
|
||||
{
|
||||
$canBeCachedFully = !$content->hasDynamicContent();
|
||||
|
||||
$canBeCachedPartially = !$content->hasUserSpecificContent
|
||||
&& !$content->hasSessionData
|
||||
&& !$content->hasTimeBasedContent;
|
||||
|
||||
$supportsFragments = count($content->staticBlocks) > 0;
|
||||
|
||||
$complexity = match (true) {
|
||||
!$content->hasDynamicContent() => CacheComplexity::LOW,
|
||||
$content->hasUserSpecificContent || $content->hasSessionData => CacheComplexity::HIGH,
|
||||
$content->hasFormElements || $content->hasTimeBasedContent => CacheComplexity::MEDIUM,
|
||||
default => CacheComplexity::EXTREME,
|
||||
};
|
||||
|
||||
$restrictions = [];
|
||||
if ($content->hasUserSpecificContent) $restrictions[] = 'user_specific';
|
||||
if ($content->hasSessionData) $restrictions[] = 'session_dependent';
|
||||
if ($content->hasTimeBasedContent) $restrictions[] = 'time_sensitive';
|
||||
if ($content->hasCsrfTokens) $restrictions[] = 'csrf_protected';
|
||||
|
||||
return new self(
|
||||
canBeCachedFully: $canBeCachedFully,
|
||||
canBeCachedPartially: $canBeCachedPartially,
|
||||
supportsFragments: $supportsFragments,
|
||||
complexity: $complexity,
|
||||
restrictions: $restrictions,
|
||||
);
|
||||
}
|
||||
|
||||
public function isFullyCacheable(): bool
|
||||
{
|
||||
return $this->canBeCachedFully;
|
||||
}
|
||||
|
||||
public function isPartiallyCacheable(): bool
|
||||
{
|
||||
return $this->canBeCachedPartially;
|
||||
}
|
||||
|
||||
public function getComplexity(): CacheComplexity
|
||||
{
|
||||
return $this->complexity;
|
||||
}
|
||||
|
||||
public function hasRestriction(string $restriction): bool
|
||||
{
|
||||
return in_array($restriction, $this->restrictions, true);
|
||||
}
|
||||
}
|
||||
12
.archive/Archived/CacheComplexity.php
Normal file
12
.archive/Archived/CacheComplexity.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Archive\Archived;
|
||||
|
||||
enum CacheComplexity: string
|
||||
{
|
||||
case LOW = 'low';
|
||||
case MEDIUM = 'medium';
|
||||
case HIGH = 'high';
|
||||
case EXTREME = 'extreme';
|
||||
}
|
||||
41
.archive/Archived/CacheStrategy.php
Normal file
41
.archive/Archived/CacheStrategy.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Archive\Archived;
|
||||
|
||||
enum CacheStrategy: string
|
||||
{
|
||||
case STATIC = 'static';
|
||||
case PARTIAL = 'partial';
|
||||
case FRAGMENT = 'fragment';
|
||||
case DYNAMIC = 'dynamic';
|
||||
case DISABLED = 'disabled';
|
||||
|
||||
public function getTtl(): int
|
||||
{
|
||||
return match($this) {
|
||||
self::STATIC => 3600, // 1 Stunde für statische Inhalte
|
||||
self::PARTIAL => 900, // 15 Minuten für teilweise dynamische Inhalte
|
||||
self::FRAGMENT => 300, // 5 Minuten für Fragment-Cache
|
||||
self::DYNAMIC => 0, // Keine Caching für dynamische Inhalte
|
||||
self::DISABLED => 0, // Caching deaktiviert
|
||||
};
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return match($this) {
|
||||
self::STATIC => 1,
|
||||
self::PARTIAL => 2,
|
||||
self::FRAGMENT => 3,
|
||||
self::DYNAMIC => 4,
|
||||
self::DISABLED => 5,
|
||||
};
|
||||
}
|
||||
|
||||
public function shouldCache(): bool
|
||||
{
|
||||
return $this !== self::DYNAMIC && $this !== self::DISABLED;
|
||||
}
|
||||
}
|
||||
64
.archive/Archived/CacheWarmer.php
Normal file
64
.archive/Archived/CacheWarmer.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Archive\Archived;
|
||||
|
||||
use App\Framework\View\RenderContext;
|
||||
|
||||
final class CacheWarmer
|
||||
{
|
||||
private array $popularTemplates = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly SmartCacheEngine $cacheEngine,
|
||||
private readonly array $warmupTemplates = []
|
||||
) {}
|
||||
|
||||
public function warmupTemplates(array $templates = []): array
|
||||
{
|
||||
$templatesToWarm = !empty($templates) ? $templates : $this->warmupTemplates;
|
||||
$results = [];
|
||||
|
||||
foreach ($templatesToWarm as $template => $data) {
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$context = new RenderContext($template, $data ?? []);
|
||||
$this->cacheEngine->render($context);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$results[$template] = [
|
||||
'success' => true,
|
||||
'duration' => round($duration * 1000, 2) . 'ms'
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$results[$template] = [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function addPopularTemplate(string $template, array $data = []): void
|
||||
{
|
||||
$this->popularTemplates[$template] = $data;
|
||||
}
|
||||
|
||||
public function warmupPopularTemplates(): array
|
||||
{
|
||||
return $this->warmupTemplates($this->popularTemplates);
|
||||
}
|
||||
|
||||
public function scheduleWarmup(array $templates): void
|
||||
{
|
||||
// In einer echten Implementierung könnte hier ein Background-Job gescheduled werden
|
||||
foreach ($templates as $template => $data) {
|
||||
$this->addPopularTemplate($template, $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
.archive/Archived/CacheabilityInfo.php
Normal file
75
.archive/Archived/CacheabilityInfo.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Archive\Archived;
|
||||
|
||||
final class CacheabilityInfo
|
||||
{
|
||||
public bool $hasDynamicComponents = false;
|
||||
public bool $hasUserSpecificContent = false;
|
||||
public bool $hasFormElements = false;
|
||||
public bool $hasTimeBasedContent = false;
|
||||
public bool $hasSessionData = false;
|
||||
public array $staticFragments = [];
|
||||
public array $dynamicFragments = [];
|
||||
public array $dynamicComponents = [];
|
||||
public CacheStrategy $cacheStrategy = CacheStrategy::DYNAMIC;
|
||||
public int $ttl = 0;
|
||||
public array $dependencies = [];
|
||||
|
||||
public function isFullyCacheable(): bool
|
||||
{
|
||||
return !$this->hasDynamicComponents
|
||||
&& !$this->hasUserSpecificContent
|
||||
&& !$this->hasFormElements
|
||||
&& !$this->hasSessionData
|
||||
&& !$this->hasTimeBasedContent;
|
||||
}
|
||||
|
||||
public function isPartiallyCacheable(): bool
|
||||
{
|
||||
return count($this->staticFragments) > 0 && !$this->hasUserSpecificContent;
|
||||
}
|
||||
|
||||
public function shouldUseFragmentCache(): bool
|
||||
{
|
||||
return count($this->staticFragments) > 0 || count($this->dynamicFragments) > 0;
|
||||
}
|
||||
|
||||
public function getCacheComplexity(): int
|
||||
{
|
||||
$complexity = 0;
|
||||
$complexity += count($this->staticFragments);
|
||||
$complexity += count($this->dynamicFragments) * 3;
|
||||
$complexity += $this->hasUserSpecificContent ? 5 : 0;
|
||||
$complexity += $this->hasFormElements ? 3 : 0;
|
||||
$complexity += $this->hasDynamicComponents ? 4 : 0;
|
||||
|
||||
return $complexity;
|
||||
}
|
||||
|
||||
public function addStaticFragment(string $fragmentId): void
|
||||
{
|
||||
if (!in_array($fragmentId, $this->staticFragments, true)) {
|
||||
$this->staticFragments[] = $fragmentId;
|
||||
}
|
||||
}
|
||||
|
||||
public function addDynamicFragment(string $fragmentId): void
|
||||
{
|
||||
if (!in_array($fragmentId, $this->dynamicFragments, true)) {
|
||||
$this->dynamicFragments[] = $fragmentId;
|
||||
}
|
||||
}
|
||||
|
||||
public function hasFragments(): bool
|
||||
{
|
||||
return count($this->staticFragments) > 0 || count($this->dynamicFragments) > 0;
|
||||
}
|
||||
|
||||
public function getFragmentCount(): int
|
||||
{
|
||||
return count($this->staticFragments) + count($this->dynamicFragments);
|
||||
}
|
||||
}
|
||||
93
.archive/Archived/DependencyCollection.php
Normal file
93
.archive/Archived/DependencyCollection.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Archive\Archived;
|
||||
|
||||
use App\Framework\View\Cache\CacheDependency;
|
||||
use App\Framework\View\Cache\DependencyType;
|
||||
|
||||
final readonly class DependencyCollection
|
||||
{
|
||||
/** @param CacheDependency[] $dependencies */
|
||||
public function __construct(
|
||||
private array $dependencies = [],
|
||||
) {
|
||||
foreach ($dependencies as $dependency) {
|
||||
if (!$dependency instanceof CacheDependency) {
|
||||
throw new \InvalidArgumentException('All items must be CacheDependency instances');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function empty(): self
|
||||
{
|
||||
return new self([]);
|
||||
}
|
||||
|
||||
public static function fromStrings(array $strings): self
|
||||
{
|
||||
$dependencies = array_map(
|
||||
fn(string $str) => CacheDependency::fromString($str),
|
||||
$strings
|
||||
);
|
||||
|
||||
return new self($dependencies);
|
||||
}
|
||||
|
||||
public function add(CacheDependency $dependency): self
|
||||
{
|
||||
// Prevent duplicates
|
||||
foreach ($this->dependencies as $existing) {
|
||||
if ($existing->equals($dependency)) {
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
return new self([...$this->dependencies, $dependency]);
|
||||
}
|
||||
|
||||
public function has(string $key): bool
|
||||
{
|
||||
foreach ($this->dependencies as $dependency) {
|
||||
if ($dependency->key === $key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function ofType(DependencyType $type): self
|
||||
{
|
||||
return new self(
|
||||
array_filter(
|
||||
$this->dependencies,
|
||||
fn(CacheDependency $dep) => $dep->type === $type
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->dependencies);
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty($this->dependencies);
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return array_map(
|
||||
fn(CacheDependency $dep) => $dep->toArray(),
|
||||
$this->dependencies
|
||||
);
|
||||
}
|
||||
|
||||
/** @return CacheDependency[] */
|
||||
public function all(): array
|
||||
{
|
||||
return $this->dependencies;
|
||||
}
|
||||
}
|
||||
93
.archive/Archived/FragmentCacheManager.php
Normal file
93
.archive/Archived/FragmentCacheManager.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Archive\Archived;
|
||||
|
||||
use App\Framework\Cache\Cache;
|
||||
|
||||
final readonly class FragmentCacheManager
|
||||
{
|
||||
public function __construct(
|
||||
private Cache $cache,
|
||||
) {}
|
||||
|
||||
public function hasFragment(string $key, array $dependencies = [], int $ttl = 300): bool
|
||||
{
|
||||
$cacheKey = $this->buildFragmentKey($key, $dependencies);
|
||||
return $this->cache->has($cacheKey);
|
||||
}
|
||||
|
||||
public function cacheFragment(string $key, callable $generator, array $dependencies = [], int $ttl = 300): string
|
||||
{
|
||||
$cacheKey = $this->buildFragmentKey($key, $dependencies);
|
||||
|
||||
return $this->cache->remember(
|
||||
$cacheKey,
|
||||
$generator,
|
||||
$ttl
|
||||
)->value;
|
||||
}
|
||||
|
||||
public function cacheStaticFragment(string $fragmentId, string $content, array $dependencies = []): void
|
||||
{
|
||||
$cacheKey = $this->buildFragmentKey($fragmentId, $dependencies);
|
||||
|
||||
// Statische Fragmente haben lange TTL
|
||||
$this->cache->set($cacheKey, $content, 3600);
|
||||
}
|
||||
|
||||
public function getDynamicFragment(string $fragmentId, callable $generator, array $context = []): string
|
||||
{
|
||||
// Dynamische Fragmente werden nicht gecacht, aber durch den Generator optimiert
|
||||
return $generator($context);
|
||||
}
|
||||
|
||||
public function invalidateFragment(string $key, array $dependencies = []): void
|
||||
{
|
||||
$cacheKey = $this->buildFragmentKey($key, $dependencies);
|
||||
$this->cache->forget($cacheKey);
|
||||
}
|
||||
|
||||
public function invalidateByPattern(string $pattern): int
|
||||
{
|
||||
// Vereinfacht - das Cache-Interface bietet möglicherweise keine Pattern-Invalidierung
|
||||
// Hier könnten wir eine erweiterte Implementierung hinzufügen
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getFragment(string $key, array $dependencies = []): ?string
|
||||
{
|
||||
$cacheKey = $this->buildFragmentKey($key, $dependencies);
|
||||
|
||||
if ($this->cache->has($cacheKey)) {
|
||||
return $this->cache->get($cacheKey)->value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function warmupFragment(string $key, callable $generator, array $dependencies = [], int $ttl = 300): void
|
||||
{
|
||||
$this->cacheFragment($key, $generator, $dependencies, $ttl);
|
||||
}
|
||||
|
||||
public function getFragmentStats(): array
|
||||
{
|
||||
return [
|
||||
'cache_enabled' => true,
|
||||
'cache_interface' => get_class($this->cache),
|
||||
];
|
||||
}
|
||||
|
||||
private function buildFragmentKey(string $key, array $dependencies): string
|
||||
{
|
||||
$dependencyHash = '';
|
||||
if (!empty($dependencies)) {
|
||||
ksort($dependencies); // Konsistente Reihenfolge
|
||||
$dependencyHash = ':' . md5(serialize($dependencies));
|
||||
}
|
||||
|
||||
return "fragment:{$key}{$dependencyHash}";
|
||||
}
|
||||
}
|
||||
428
.archive/Archived/SmartCacheEngine.php
Normal file
428
.archive/Archived/SmartCacheEngine.php
Normal file
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Archive\Archived;
|
||||
|
||||
use App\Framework\Cache\Cache;
|
||||
use App\Framework\View\DomWrapper;
|
||||
use App\Framework\View\Loading\TemplateLoader;
|
||||
use App\Framework\View\RenderContext;
|
||||
use App\Framework\View\TemplateProcessor;
|
||||
use App\Framework\View\TemplateRenderer;
|
||||
|
||||
final class SmartCacheEngine implements TemplateRenderer
|
||||
{
|
||||
private array $cacheStats = [
|
||||
'hits' => 0,
|
||||
'misses' => 0,
|
||||
'renders' => 0,
|
||||
'cached_templates' => [],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly TemplateLoader $loader,
|
||||
private readonly TemplateAnalyzer $analyzer,
|
||||
private readonly FragmentCacheManager $fragmentCache,
|
||||
private readonly Cache $cache,
|
||||
private readonly TemplateProcessor $processor = new TemplateProcessor(),
|
||||
private readonly bool $cacheEnabled = true,
|
||||
private readonly bool $debugMode = true,
|
||||
private readonly TemplatePreprocessor $preprocessor = new TemplatePreprocessor(),
|
||||
) {
|
||||
dd('oldcacheengine');
|
||||
}
|
||||
|
||||
public function render(RenderContext $context): string
|
||||
{
|
||||
$this->cacheStats['renders']++;
|
||||
|
||||
if (!$this->cacheEnabled) {
|
||||
$this->logDebug("Cache disabled - rendering without cache", $context);
|
||||
return $this->renderWithoutCache($context);
|
||||
}
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
// Lade und analysiere Template
|
||||
$template = $this->loader->load($context->template, $context->controllerClass, $context);
|
||||
|
||||
// Preprocessing für optimierte Cache-Keys
|
||||
$normalizedTemplate = $this->preprocessor->normalizeTemplate($template, $context);
|
||||
$cacheableBlocks = $this->preprocessor->extractCacheableBlocks($template);
|
||||
|
||||
$analysis = $this->analyzer->analyzeCacheability($normalizedTemplate, $context);
|
||||
|
||||
// Erweitere Analysis mit extrahierten Blöcken
|
||||
foreach ($cacheableBlocks as $block) {
|
||||
if ($block['type'] === 'explicit') {
|
||||
$analysis->addStaticFragment($block['id']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->logDebug("Template loaded, length: " . strlen($template) . " chars", $context);
|
||||
$this->logDebug("Cache strategy: {$analysis->cacheStrategy->value}, TTL: {$analysis->ttl}s", $context);
|
||||
$this->logDebug("Cacheable blocks found: " . count($cacheableBlocks), $context);
|
||||
|
||||
$result = match($analysis->cacheStrategy) {
|
||||
CacheStrategy::STATIC => $this->renderStaticCached($context, $template, $analysis),
|
||||
CacheStrategy::PARTIAL => $this->renderPartialCached($context, $template, $analysis),
|
||||
CacheStrategy::FRAGMENT => $this->renderFragmentCached($context, $template, $analysis),
|
||||
CacheStrategy::DYNAMIC => $this->processor->render($context, $template),
|
||||
CacheStrategy::DISABLED => $this->renderWithoutCache($context),
|
||||
};
|
||||
|
||||
$renderTime = microtime(true) - $startTime;
|
||||
$this->logCachePerformance($context, $analysis, $renderTime);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function renderStaticCached(RenderContext $context, string $template, CacheabilityInfo $analysis): string
|
||||
{
|
||||
$cacheKey = $this->buildStaticCacheKey($context, $analysis);
|
||||
|
||||
// Prüfe zuerst, ob bereits im Cache
|
||||
$wasInCache = $this->cache->has($cacheKey);
|
||||
|
||||
// Nutze remember-Pattern
|
||||
$cacheItem = $this->cache->remember(
|
||||
$cacheKey,
|
||||
function() use ($context, $template) {
|
||||
$this->logDebug("CACHE MISS - Generating new content", $context);
|
||||
$this->cacheStats['misses']++;
|
||||
return $this->processor->render($context, $template);
|
||||
},
|
||||
$analysis->ttl
|
||||
);
|
||||
|
||||
if ($wasInCache) {
|
||||
$this->logDebug("CACHE HIT - Using cached content", $context);
|
||||
$this->cacheStats['hits']++;
|
||||
}
|
||||
|
||||
$this->cacheStats['cached_templates'][$context->template] = [
|
||||
'strategy' => 'static',
|
||||
'key' => $cacheKey,
|
||||
'ttl' => $analysis->ttl,
|
||||
'hit' => $wasInCache
|
||||
];
|
||||
|
||||
return $cacheItem->value;
|
||||
}
|
||||
|
||||
private function renderPartialCached(RenderContext $context, string $template, CacheabilityInfo $analysis): string
|
||||
{
|
||||
$cacheKey = $this->buildPartialCacheKey($context, $analysis);
|
||||
$wasInCache = $this->cache->has($cacheKey);
|
||||
|
||||
$cacheItem = $this->cache->remember(
|
||||
$cacheKey,
|
||||
function() use ($context, $template) {
|
||||
$this->cacheStats['misses']++;
|
||||
return $this->processor->render($context, $template);
|
||||
},
|
||||
$analysis->ttl
|
||||
);
|
||||
|
||||
if ($wasInCache) {
|
||||
$this->cacheStats['hits']++;
|
||||
}
|
||||
|
||||
return $cacheItem->value;
|
||||
}
|
||||
|
||||
private function renderFragmentCached(RenderContext $context, string $template, CacheabilityInfo $analysis): string
|
||||
{
|
||||
// Wenn keine Fragmente erkannt wurden, verwende ein Template-weites Fragment
|
||||
if (empty($analysis->staticFragments) && empty($analysis->dynamicFragments)) {
|
||||
$this->logDebug("No fragments found - creating template-wide fragment", $context);
|
||||
|
||||
$templateFragmentKey = "template_{$context->template}";
|
||||
$wasInCache = $this->fragmentCache->hasFragment($templateFragmentKey, $analysis->dependencies ?? [], $analysis->ttl);
|
||||
|
||||
if ($wasInCache) {
|
||||
$this->logDebug("FRAGMENT HIT - Using cached template", $context);
|
||||
$this->cacheStats['hits']++;
|
||||
$content = $this->fragmentCache->getFragment($templateFragmentKey, $analysis->dependencies ?? []);
|
||||
} else {
|
||||
$this->logDebug("FRAGMENT MISS - Generating new template content", $context);
|
||||
$this->cacheStats['misses']++;
|
||||
$content = $this->fragmentCache->cacheFragment(
|
||||
$templateFragmentKey,
|
||||
function() use ($context, $template) {
|
||||
return $this->processor->render($context, $template);
|
||||
},
|
||||
$analysis->dependencies ?? [],
|
||||
$analysis->ttl
|
||||
);
|
||||
}
|
||||
|
||||
$this->cacheStats['cached_templates'][$context->template] = [
|
||||
'strategy' => 'fragment',
|
||||
'key' => $templateFragmentKey,
|
||||
'ttl' => $analysis->ttl,
|
||||
'hit' => $wasInCache
|
||||
];
|
||||
|
||||
// Request-spezifische Statistiken
|
||||
$hitRate = $this->cacheStats['hits'] + $this->cacheStats['misses'] > 0
|
||||
? round(($this->cacheStats['hits'] / ($this->cacheStats['hits'] + $this->cacheStats['misses'])) * 100, 1)
|
||||
: 0;
|
||||
|
||||
error_log("SmartCache Request Stats: Hits: " . $this->cacheStats['hits'] . ", Misses: " . $this->cacheStats['misses'] . ", Hit Rate: {$hitRate}%");
|
||||
|
||||
return $content;
|
||||
} else {
|
||||
$this->logDebug("Found " . count($analysis->staticFragments) . " static fragments and " . count($analysis->dynamicFragments) . " dynamic fragments", $context);
|
||||
}
|
||||
|
||||
// Verarbeite explizite Fragmente
|
||||
$finalContent = '';
|
||||
|
||||
foreach ($analysis->staticFragments as $fragmentId) {
|
||||
$wasInCache = $this->fragmentCache->hasFragment($fragmentId, $analysis->dependencies ?? [], $analysis->ttl);
|
||||
|
||||
if ($wasInCache) {
|
||||
$this->logDebug("FRAGMENT HIT - Using cached fragment: {$fragmentId}", $context);
|
||||
$this->cacheStats['hits']++;
|
||||
// Direkt aus Cache holen ohne Generator zu verwenden
|
||||
$fragmentContent = $this->fragmentCache->getFragment($fragmentId, $analysis->dependencies ?? []);
|
||||
} else {
|
||||
$this->logDebug("FRAGMENT MISS - Generating fragment: {$fragmentId}", $context);
|
||||
$this->cacheStats['misses']++;
|
||||
$fragmentContent = $this->fragmentCache->cacheFragment(
|
||||
$fragmentId,
|
||||
function() use ($fragmentId, $template, $context) {
|
||||
return $this->renderFragmentFromTemplate($fragmentId, $template, $context);
|
||||
},
|
||||
$analysis->dependencies ?? [],
|
||||
$analysis->ttl
|
||||
);
|
||||
}
|
||||
|
||||
$finalContent .= $fragmentContent;
|
||||
}
|
||||
|
||||
// Rendere dynamische Fragmente ohne Caching
|
||||
foreach ($analysis->dynamicFragments as $fragmentId) {
|
||||
$fragmentContent = $this->renderFragmentFromTemplate($fragmentId, $template, $context);
|
||||
$finalContent .= $fragmentContent;
|
||||
}
|
||||
|
||||
return $finalContent ?: $this->processor->render($context, $template);
|
||||
}
|
||||
|
||||
private function renderFragmentFromTemplate(string $fragmentId, string $template, RenderContext $context): string
|
||||
{
|
||||
// Für jetzt: Wenn es ein Template-Fragment ist, rendere das komplette Template
|
||||
if (str_starts_with($fragmentId, 'template_')) {
|
||||
return $this->processor->render($context, $template);
|
||||
}
|
||||
|
||||
// TODO: Hier könnten wir später spezifische Fragment-Extraktion implementieren
|
||||
// Für jetzt verwende DOM-Wrapper für Fragment-Extraktion
|
||||
try {
|
||||
$domWrapper = DomWrapper::fromString($template);
|
||||
return $this->renderFragment($fragmentId, $domWrapper, $context);
|
||||
} catch (\Exception $e) {
|
||||
$this->logDebug("Fragment rendering failed for {$fragmentId}: " . $e->getMessage(), $context);
|
||||
// Fallback: Rendere komplettes Template
|
||||
return $this->processor->render($context, $template);
|
||||
}
|
||||
}
|
||||
|
||||
private function renderWithoutCache(RenderContext $context): string
|
||||
{
|
||||
$template = $this->loader->load($context->template, $context->controllerClass, $context);
|
||||
return $this->processor->render($context, $template);
|
||||
}
|
||||
|
||||
private function renderFragment(string $fragmentId, DomWrapper $domWrapper, RenderContext $context): string
|
||||
{
|
||||
// Vereinfachte Fragment-Rendering-Logik
|
||||
// In einer vollständigen Implementierung würde hier das spezifische Fragment extrahiert
|
||||
$elements = $domWrapper->getElementsByAttribute('data-fragment-id', $fragmentId);
|
||||
|
||||
if (count($elements) > 0) {
|
||||
$element = $elements[0];
|
||||
return $domWrapper->document->saveHTML($element);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function extractStaticContent(string $content, CacheabilityInfo $analysis): string
|
||||
{
|
||||
// Entferne dynamische Platzhalter für statisches Caching
|
||||
$staticContent = $content;
|
||||
|
||||
// Ersetze dynamische Teile mit Platzhaltern
|
||||
$staticContent = preg_replace('/\{\{[^}]+}}/', '{{DYNAMIC_PLACEHOLDER}}', $staticContent);
|
||||
|
||||
return $staticContent;
|
||||
}
|
||||
|
||||
private function buildStaticCacheKey(RenderContext $context, $analysis): string
|
||||
{
|
||||
$dependencies = is_object($analysis) && method_exists($analysis, 'dependencies')
|
||||
? $analysis->dependencies
|
||||
: ($analysis->dependencies ?? []);
|
||||
|
||||
$optimizedKey = $this->preprocessor->generateOptimizedCacheKey($context, $dependencies);
|
||||
|
||||
return "static:{$optimizedKey}";
|
||||
}
|
||||
|
||||
private function buildPartialCacheKey(RenderContext $context, $analysis): string
|
||||
{
|
||||
$dependencies = is_object($analysis) && method_exists($analysis, 'dependencies')
|
||||
? $analysis->dependencies
|
||||
: ($analysis->dependencies ?? []);
|
||||
|
||||
$optimizedKey = $this->preprocessor->generateOptimizedCacheKey($context, $dependencies);
|
||||
|
||||
return "partial:{$optimizedKey}";
|
||||
}
|
||||
|
||||
|
||||
public function getCacheStats(): array
|
||||
{
|
||||
$hitRate = $this->cacheStats['hits'] + $this->cacheStats['misses'] > 0
|
||||
? round(($this->cacheStats['hits'] / ($this->cacheStats['hits'] + $this->cacheStats['misses'])) * 100, 2)
|
||||
: 0;
|
||||
|
||||
return [
|
||||
'smart_cache' => [
|
||||
'enabled' => $this->cacheEnabled,
|
||||
'debug_mode' => $this->debugMode,
|
||||
'cache_interface' => get_class($this->cache),
|
||||
'performance' => [
|
||||
'total_renders' => $this->cacheStats['renders'],
|
||||
'cache_hits' => $this->cacheStats['hits'],
|
||||
'cache_misses' => $this->cacheStats['misses'],
|
||||
'hit_rate' => $hitRate . '%',
|
||||
],
|
||||
'cached_templates' => $this->cacheStats['cached_templates'],
|
||||
'fragment_stats' => $this->fragmentCache->getFragmentStats(),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function invalidateCache(?string $template = null): int
|
||||
{
|
||||
$invalidated = 0;
|
||||
|
||||
if ($template) {
|
||||
// Invalidiere spezifisches Template
|
||||
$patterns = [
|
||||
"static:{$template}:",
|
||||
"partial:{$template}:",
|
||||
"template_{$template}",
|
||||
];
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
// Vereinfachte Implementierung - da das Cache-Interface keine Pattern-Suche unterstützt
|
||||
// könnte hier eine erweiterte Lösung implementiert werden
|
||||
$invalidated++;
|
||||
}
|
||||
} else {
|
||||
// Alle Caches löschen und Stats zurücksetzen
|
||||
if ($this->cache->clear()) {
|
||||
$this->resetStats();
|
||||
$invalidated = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $invalidated;
|
||||
}
|
||||
|
||||
public function resetStats(): void
|
||||
{
|
||||
$this->cacheStats = [
|
||||
'hits' => 0,
|
||||
'misses' => 0,
|
||||
'renders' => 0,
|
||||
'cached_templates' => [],
|
||||
];
|
||||
}
|
||||
|
||||
public function warmupCache(array $templates = []): array
|
||||
{
|
||||
if (!$this->warmer) {
|
||||
return ['error' => 'CacheWarmer not configured'];
|
||||
}
|
||||
|
||||
return $this->warmer->warmupTemplates($templates);
|
||||
}
|
||||
|
||||
public function addPopularTemplate(string $template, array $data = []): void
|
||||
{
|
||||
if ($this->warmer) {
|
||||
$this->warmer->addPopularTemplate($template, $data);
|
||||
}
|
||||
}
|
||||
|
||||
public function diagnoseCaching(): void
|
||||
{
|
||||
echo "\n=== CACHE DIAGNOSTICS ===\n";
|
||||
echo "Smart Cache Enabled: " . ($this->cacheEnabled ? 'YES' : 'NO') . "\n";
|
||||
echo "Cache Interface: " . get_class($this->cache) . "\n";
|
||||
echo "Debug Mode: " . ($this->debugMode ? 'YES' : 'NO') . "\n";
|
||||
echo "Total Renders: " . $this->cacheStats['renders'] . "\n";
|
||||
echo "Cache Hits: " . $this->cacheStats['hits'] . "\n";
|
||||
echo "Cache Misses: " . $this->cacheStats['misses'] . "\n";
|
||||
|
||||
$hitRate = $this->cacheStats['hits'] + $this->cacheStats['misses'] > 0
|
||||
? round(($this->cacheStats['hits'] / ($this->cacheStats['hits'] + $this->cacheStats['misses'])) * 100, 2)
|
||||
: 0;
|
||||
echo "Hit Rate: " . $hitRate . "%\n";
|
||||
|
||||
echo "\n=== ANALYSIS ===\n";
|
||||
if ($this->cacheStats['hits'] === 0 && $this->cacheStats['misses'] === 0) {
|
||||
echo "⚠️ PROBLEM: No cache operations detected!\n";
|
||||
echo " This suggests that templates are being rendered with CacheStrategy::DYNAMIC or CacheStrategy::DISABLED\n";
|
||||
echo " Check the TemplateAnalyzer logic to ensure templates are being marked as cacheable.\n";
|
||||
}
|
||||
|
||||
echo "========================\n";
|
||||
}
|
||||
|
||||
private function logCachePerformance(RenderContext $context, CacheabilityInfo $analysis, float $renderTime): void
|
||||
{
|
||||
if (!$this->debugMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
$logData = [
|
||||
'template' => $context->template,
|
||||
'strategy' => $analysis->cacheStrategy->value,
|
||||
'render_time' => round($renderTime * 1000, 2) . 'ms',
|
||||
'complexity' => $analysis->getCacheComplexity(),
|
||||
'cacheable' => $analysis->cacheStrategy->shouldCache(),
|
||||
'request_hits' => $this->cacheStats['hits'],
|
||||
'request_misses' => $this->cacheStats['misses'],
|
||||
'static_fragments' => count($analysis->staticFragments),
|
||||
'dynamic_fragments' => count($analysis->dynamicFragments),
|
||||
'ttl' => $analysis->ttl,
|
||||
];
|
||||
|
||||
error_log('SmartCache Performance: ' . json_encode($logData));
|
||||
|
||||
// Zusätzliche Fragment-Details wenn Fragment-Strategy
|
||||
if ($analysis->cacheStrategy === CacheStrategy::FRAGMENT) {
|
||||
error_log('SmartCache Fragments: static=[' . implode(',', $analysis->staticFragments) .
|
||||
'] dynamic=[' . implode(',', $analysis->dynamicFragments) . ']');
|
||||
}
|
||||
}
|
||||
|
||||
private function logDebug(string $message, RenderContext $context): void
|
||||
{
|
||||
if (!$this->debugMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
error_log("SmartCache DEBUG [{$context->template}]: {$message}");
|
||||
}
|
||||
}
|
||||
216
.archive/Archived/TemplateAnalyzer.php
Normal file
216
.archive/Archived/TemplateAnalyzer.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Archive\Archived;
|
||||
|
||||
use App\Framework\View\RenderContext;
|
||||
|
||||
final readonly class TemplateAnalyzer
|
||||
{
|
||||
private const array DYNAMIC_COMPONENTS = [
|
||||
'user-info', 'session-data', 'csrf-token', 'timestamp',
|
||||
'user-menu', 'cart', 'notifications', 'live-data'
|
||||
];
|
||||
|
||||
private const array TIME_BASED_PATTERNS = [
|
||||
'data-timestamp', 'data-live', 'data-realtime',
|
||||
'class="live"', 'class="timestamp"'
|
||||
];
|
||||
|
||||
public function analyzeCacheability(string $template, RenderContext $context): CacheabilityInfo
|
||||
{
|
||||
$analysis = new CacheabilityInfo();
|
||||
|
||||
try {
|
||||
// Einfache String-basierte Analyse für bessere Kompatibilität
|
||||
$analysis->hasDynamicComponents = $this->findDynamicComponentsInString($template, $analysis);
|
||||
$analysis->hasUserSpecificContent = $this->findUserContentInString($template, $context);
|
||||
$analysis->hasFormElements = $this->findFormsInString($template);
|
||||
$analysis->hasTimeBasedContent = $this->findTimeBasedContent($template);
|
||||
$analysis->hasSessionData = $this->findSessionData($template, $context);
|
||||
|
||||
// Fragment-Analyse hinzufügen
|
||||
$this->autoDetectFragments($template, $analysis);
|
||||
|
||||
// Bestimme beste Cache-Strategie
|
||||
$analysis->cacheStrategy = $this->determineBestStrategy($analysis);
|
||||
$analysis->ttl = $this->calculateOptimalTtl($analysis);
|
||||
$analysis->dependencies = $this->identifyDependencies($analysis, $context);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// Bei Fehlern: Konservativ statisches Caching
|
||||
$analysis->cacheStrategy = CacheStrategy::STATIC;
|
||||
$analysis->ttl = 300;
|
||||
}
|
||||
|
||||
return $analysis;
|
||||
}
|
||||
|
||||
private function findDynamicComponentsInString(string $template, CacheabilityInfo $analysis): bool
|
||||
{
|
||||
$hasDynamic = false;
|
||||
foreach (self::DYNAMIC_COMPONENTS as $component) {
|
||||
if (str_contains($template, $component)) {
|
||||
$analysis->dynamicComponents[] = $component;
|
||||
$hasDynamic = true;
|
||||
}
|
||||
}
|
||||
return $hasDynamic;
|
||||
}
|
||||
|
||||
private function findUserContentInString(string $template, RenderContext $context): bool
|
||||
{
|
||||
// Suche nach user-spezifischen Patterns
|
||||
$userPatterns = ['{{user', '$user', 'data-user', 'class="user-'];
|
||||
|
||||
if (array_any($userPatterns, fn($pattern) => str_contains($template, $pattern))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prüfe Context-Daten auf User-Informationen
|
||||
return isset($context->data['user']) || isset($context->data['session']);
|
||||
}
|
||||
|
||||
private function findFormsInString(string $template): bool
|
||||
{
|
||||
return str_contains($template, '<form') || str_contains($template, 'csrf') || str_contains($template, 'token');
|
||||
}
|
||||
|
||||
// Entfernte Methoden werden durch die neuen String-basierten Methoden ersetzt
|
||||
|
||||
private function findTimeBasedContent(string $template): bool
|
||||
{
|
||||
return array_any(self::TIME_BASED_PATTERNS, fn($pattern) => str_contains($template, $pattern));
|
||||
}
|
||||
|
||||
private function findSessionData(string $template, RenderContext $context): bool
|
||||
{
|
||||
// Prüfe auf Session-bezogene Patterns im Template
|
||||
$sessionPatterns = ['session.', '{{session', '$session', 'data-session'];
|
||||
|
||||
if (array_any($sessionPatterns, fn($pattern) => str_contains($template, $pattern))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prüfe Context auf Session-Daten
|
||||
return isset($context->data['session']) || isset($context->data['flash']);
|
||||
}
|
||||
|
||||
// Fragment-Analyse vereinfacht entfernt für bessere Stabilität
|
||||
|
||||
private function determineBestStrategy(CacheabilityInfo $analysis): CacheStrategy
|
||||
{
|
||||
// Vollständig statisch
|
||||
if ($analysis->isFullyCacheable()) {
|
||||
return CacheStrategy::STATIC;
|
||||
}
|
||||
|
||||
// Teilweise cacheable mit Fragmenten
|
||||
if ($analysis->shouldUseFragmentCache()) {
|
||||
return CacheStrategy::FRAGMENT;
|
||||
}
|
||||
|
||||
// Teilweise cacheable ohne Fragmente
|
||||
if ($analysis->isPartiallyCacheable()) {
|
||||
return CacheStrategy::PARTIAL;
|
||||
}
|
||||
|
||||
// Vollständig dynamisch
|
||||
return CacheStrategy::DYNAMIC;
|
||||
}
|
||||
|
||||
private function calculateOptimalTtl(CacheabilityInfo $analysis): int
|
||||
{
|
||||
$baseTtl = $analysis->cacheStrategy->getTtl();
|
||||
|
||||
// Reduziere TTL basierend auf Komplexität
|
||||
$complexity = $analysis->getCacheComplexity();
|
||||
$reduction = min($complexity * 60, $baseTtl * 0.5); // Max 50% Reduktion
|
||||
|
||||
return max(0, $baseTtl - (int)$reduction);
|
||||
}
|
||||
|
||||
private function identifyDependencies(CacheabilityInfo $analysis, RenderContext $context): array
|
||||
{
|
||||
$dependencies = [];
|
||||
|
||||
// Template-File als Basis-Dependency
|
||||
$dependencies['template'] = $context->template;
|
||||
|
||||
// Dynamic Fragment Dependencies - verwende dynamicFragments anstatt dynamicComponents
|
||||
foreach ($analysis->dynamicFragments as $fragment) {
|
||||
// Verwende einfach den Fragment-Namen und aktuellen Timestamp
|
||||
$dependencies['fragment:' . $fragment] = time();
|
||||
}
|
||||
|
||||
// User-spezifische Dependencies
|
||||
if ($analysis->hasUserSpecificContent && isset($context->data['user']['id'])) {
|
||||
$dependencies['user'] = $context->data['user']['id'];
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
private function autoDetectFragments(string $template, CacheabilityInfo $info): void
|
||||
{
|
||||
$fragmentsFound = false;
|
||||
|
||||
// Erkenne große statische Blöcke (Navigation, Footer, etc.)
|
||||
if (preg_match('/<nav[^>]*>(.*?)<\/nav>/s', $template)) {
|
||||
$info->addStaticFragment('navigation');
|
||||
$fragmentsFound = true;
|
||||
}
|
||||
|
||||
if (preg_match('/<footer[^>]*>(.*?)<\/footer>/s', $template)) {
|
||||
$info->addStaticFragment('footer');
|
||||
$fragmentsFound = true;
|
||||
}
|
||||
|
||||
if (preg_match('/<header[^>]*>(.*?)<\/header>/s', $template)) {
|
||||
$info->addStaticFragment('header');
|
||||
$fragmentsFound = true;
|
||||
}
|
||||
|
||||
// Erkenne HTML-Sektionen
|
||||
if (preg_match('/<section[^>]*>(.*?)<\/section>/s', $template)) {
|
||||
$info->addStaticFragment('content_section');
|
||||
$fragmentsFound = true;
|
||||
}
|
||||
|
||||
if (preg_match('/<main[^>]*>(.*?)<\/main>/s', $template)) {
|
||||
$info->addStaticFragment('main_content');
|
||||
$fragmentsFound = true;
|
||||
}
|
||||
|
||||
// Erkenne CSS-Klassen-basierte Bereiche
|
||||
if (preg_match('/class=["\'].*sidebar.*["\']/', $template)) {
|
||||
$info->addStaticFragment('sidebar');
|
||||
$fragmentsFound = true;
|
||||
}
|
||||
|
||||
// Erkenne dynamische Bereiche
|
||||
if (preg_match('/\{\{\s*flash|errors|session/', $template)) {
|
||||
$info->addDynamicFragment('user_messages');
|
||||
$fragmentsFound = true;
|
||||
}
|
||||
|
||||
// Analysiere Template-Komplexität
|
||||
$dynamicMatches = preg_match_all('/\{\{[^}]+}}/', $template);
|
||||
$templateLength = strlen($template);
|
||||
|
||||
// Debugging der Fragment-Erkennung
|
||||
error_log("TemplateAnalyzer DEBUG: Template length: {$templateLength}, Dynamic matches: {$dynamicMatches}, Fragments found: " . ($fragmentsFound ? 'yes' : 'no'));
|
||||
|
||||
// Wenn weniger als 5 dynamische Platzhalter pro 1_000 Zeichen UND keine anderen Fragmente
|
||||
$dynamicDensity = $templateLength > 0 ? ($dynamicMatches / $templateLength) * 1000 : 0;
|
||||
|
||||
error_log("TemplateAnalyzer DEBUG: Dynamic density: {$dynamicDensity}, Has dynamic components: " . ($info->hasDynamicComponents ? 'yes' : 'no'));
|
||||
|
||||
// Immer ein template_main Fragment hinzufügen wenn wenig dynamische Inhalte
|
||||
if ($dynamicDensity < 5 || (!$fragmentsFound && !$info->hasDynamicComponents)) {
|
||||
$info->addStaticFragment('template_main');
|
||||
error_log("TemplateAnalyzer DEBUG: Added template_main fragment (density: {$dynamicDensity})");
|
||||
}
|
||||
}
|
||||
}
|
||||
56
.archive/Archived/TemplateContent.php
Normal file
56
.archive/Archived/TemplateContent.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Archive\Archived;
|
||||
|
||||
final readonly class TemplateContent
|
||||
{
|
||||
public function __construct(
|
||||
public string $rawTemplate,
|
||||
public bool $hasUserSpecificContent = false,
|
||||
public bool $hasSessionData = false,
|
||||
public bool $hasFormElements = false,
|
||||
public bool $hasTimeBasedContent = false,
|
||||
public bool $hasRandomContent = false,
|
||||
public bool $hasCsrfTokens = false,
|
||||
public array $dynamicVariables = [],
|
||||
public array $staticBlocks = [],
|
||||
) {}
|
||||
|
||||
public function hasDynamicContent(): bool
|
||||
{
|
||||
return $this->hasUserSpecificContent
|
||||
|| $this->hasSessionData
|
||||
|| $this->hasFormElements
|
||||
|| $this->hasTimeBasedContent
|
||||
|| $this->hasRandomContent
|
||||
|| $this->hasCsrfTokens
|
||||
|| !empty($this->dynamicVariables);
|
||||
}
|
||||
|
||||
public function getDynamicContentTypes(): array
|
||||
{
|
||||
$types = [];
|
||||
|
||||
if ($this->hasUserSpecificContent) $types[] = 'user_specific';
|
||||
if ($this->hasSessionData) $types[] = 'session_data';
|
||||
if ($this->hasFormElements) $types[] = 'form_elements';
|
||||
if ($this->hasTimeBasedContent) $types[] = 'time_based';
|
||||
if ($this->hasRandomContent) $types[] = 'random_content';
|
||||
if ($this->hasCsrfTokens) $types[] = 'csrf_tokens';
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
public function getCharacteristics(): array
|
||||
{
|
||||
return [
|
||||
'has_dynamic_content' => $this->hasDynamicContent(),
|
||||
'dynamic_types' => $this->getDynamicContentTypes(),
|
||||
'dynamic_variables_count' => count($this->dynamicVariables),
|
||||
'static_blocks_count' => count($this->staticBlocks),
|
||||
'template_size' => strlen($this->rawTemplate),
|
||||
];
|
||||
}
|
||||
}
|
||||
108
.archive/Archived/TemplatePreprocessor.php
Normal file
108
.archive/Archived/TemplatePreprocessor.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Archive\Archived;
|
||||
|
||||
use App\Framework\View\RenderContext;
|
||||
|
||||
final class TemplatePreprocessor
|
||||
{
|
||||
private array $normalizedTemplates = [];
|
||||
|
||||
public function normalizeTemplate(string $template, RenderContext $context): string
|
||||
{
|
||||
$cacheKey = md5($template . serialize($context->data));
|
||||
|
||||
if (isset($this->normalizedTemplates[$cacheKey])) {
|
||||
return $this->normalizedTemplates[$cacheKey];
|
||||
}
|
||||
|
||||
$normalized = $template;
|
||||
|
||||
// Entferne Kommentare für konsistentere Cache-Keys
|
||||
$normalized = preg_replace('/<!--.*?-->/s', '', $normalized);
|
||||
|
||||
// Normalisiere Whitespace
|
||||
$normalized = preg_replace('/\s+/', ' ', $normalized);
|
||||
$normalized = trim($normalized);
|
||||
|
||||
// Cache das normalisierte Template
|
||||
$this->normalizedTemplates[$cacheKey] = $normalized;
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
public function extractCacheableBlocks(string $template): array
|
||||
{
|
||||
$blocks = [];
|
||||
|
||||
// Finde data-cache Attribute für explizite Cache-Bereiche
|
||||
if (preg_match_all('/<[^>]+data-cache="([^"]+)"[^>]*>(.*?)<\/[^>]+>/s', $template, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
$blocks[] = [
|
||||
'id' => $match[1],
|
||||
'content' => $match[2],
|
||||
'type' => 'explicit'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Finde wiederholende Strukturen (Listen, Cards etc.)
|
||||
if (preg_match_all('/<(ul|ol|div class="[^"]*list[^"]*")[^>]*>(.*?)<\/\1>/s', $template, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $i => $match) {
|
||||
$blocks[] = [
|
||||
'id' => 'list_block_' . $i,
|
||||
'content' => $match[2],
|
||||
'type' => 'list'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $blocks;
|
||||
}
|
||||
|
||||
public function generateOptimizedCacheKey(RenderContext $context, array $dependencies = []): string
|
||||
{
|
||||
$keyParts = [
|
||||
$context->template,
|
||||
$context->controllerClass ?? 'default',
|
||||
];
|
||||
|
||||
// Füge nur relevante Daten hinzu (nicht alles aus $context->data)
|
||||
$relevantData = $this->extractRelevantData($context->data, $dependencies);
|
||||
if (!empty($relevantData)) {
|
||||
$keyParts[] = md5(serialize($relevantData));
|
||||
}
|
||||
|
||||
// Füge Template-Änderungszeit hinzu für Auto-Invalidation
|
||||
$templatePath = $this->resolveTemplatePath($context->template);
|
||||
if (file_exists($templatePath)) {
|
||||
$keyParts[] = filemtime($templatePath);
|
||||
}
|
||||
|
||||
return implode(':', $keyParts);
|
||||
}
|
||||
|
||||
private function extractRelevantData(array $data, array $dependencies): array
|
||||
{
|
||||
if (empty($dependencies)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$relevant = [];
|
||||
foreach ($dependencies as $key) {
|
||||
if (isset($data[$key])) {
|
||||
$relevant[$key] = $data[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $relevant;
|
||||
}
|
||||
|
||||
private function resolveTemplatePath(string $template): string
|
||||
{
|
||||
// Vereinfachte Template-Pfad-Auflösung
|
||||
return "views/{$template}.php";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user