Files
michaelschiemer/docs/debugging/discovery-cache-refactoring.md
2025-11-24 21:28:25 +01:00

7.6 KiB

Discovery Cache Refactoring - Analyse und Vorschläge

Problem-Analyse

Identifiziertes Problem

Die Debug-Logs zeigen:

[DEBUG ComponentRegistry] LiveComponent: class=App\Application\LiveComponents\Popover\PopoverComponent, name=popover, arguments={"name":"popover"}

Befund:

  1. Die Datei enthält korrekt popover-live (bestätigt durch direkte Reflection)
  2. Der Discovery-Cache enthält noch {"name":"popover"}
  3. Die Discovery wird nicht neu durchgeführt - es erscheint "Cached registry loaded", aber kein "Performing fresh discovery"
  4. Der Cache wird sofort nach dem Löschen neu erstellt, bevor die Discovery-Registry die Attribute aus den Dateien liest

Root Cause

Der Discovery-Cache wird geladen, bevor die Discovery-Registry die Attribute aus den Dateien liest. Die Arguments werden aus dem Cache geladen, nicht direkt aus den Dateien.

Cache-Load-Reihenfolge:

  1. DiscoveryCacheManager::get() wird aufgerufen
  2. Cache wird geladen (enthält noch alte Arguments)
  3. ComponentRegistry::buildNameMap() verwendet die gecachten Arguments
  4. Kollision tritt auf, weil beide Komponenten als popover registriert werden

Refactoring-Vorschläge

Vorschlag 1: Staleness-Prüfung verbessern

Problem: Die Staleness-Prüfung prüft nicht auf die spezifischen Dateien, die geändert wurden.

Lösung: Erweitere StalenessChecker um eine Methode, die prüft, ob bestimmte Dateien geändert wurden:

// In StalenessChecker.php
public function areFilesModifiedSince(array $filePaths, Timestamp $since): bool
{
    foreach ($filePaths as $filePath) {
        if ($this->fileSystemService->exists($filePath)) {
            $modifiedTime = $this->fileSystemService->getModifiedTime($filePath);
            if ($modifiedTime->toInt() > $since->toInt()) {
                return true;
            }
        }
    }
    return false;
}

Vorteil: Präzise Prüfung auf geänderte Dateien statt allgemeine Staleness-Prüfung.

Vorschlag 2: Cache-Invalidierung bei Datei-Änderungen

Problem: Der Cache wird nicht automatisch invalidated, wenn Dateien geändert werden.

Lösung: Füge einen File-Watcher hinzu, der Datei-Änderungen erkennt und den Cache automatisch invalidated:

// In DiscoveryCacheManager.php
public function invalidateIfFilesChanged(array $filePaths): bool
{
    $key = $this->buildCacheKey($context);
    $entry = $this->getStandardCache($context);
    
    if (!$entry->found || !$entry->entry) {
        return false;
    }
    
    $cacheCreatedAt = $entry->entry->createdAt;
    
    foreach ($filePaths as $filePath) {
        if ($this->fileSystemService->exists($filePath)) {
            $modifiedTime = $this->fileSystemService->getModifiedTime($filePath);
            if ($modifiedTime->toInt() > $cacheCreatedAt->toInt()) {
                $this->invalidate($context);
                return true;
            }
        }
    }
    
    return false;
}

Vorteil: Automatische Cache-Invalidierung bei Datei-Änderungen.

Vorschlag 3: Arguments direkt aus Dateien lesen (Bypass Cache)

Problem: Die Arguments werden aus dem Cache geladen, nicht direkt aus den Dateien.

Lösung: Füge eine Option hinzu, die Arguments direkt aus den Dateien liest, wenn der Cache als stale erkannt wird:

// In ComponentRegistry.php, buildNameMap()
private function buildNameMap(): ComponentNameMap
{
    $mappings = [];
    $liveComponentClassNames = [];

    // Get all LiveComponent attributes from DiscoveryRegistry
    $liveComponents = $this->discoveryRegistry->attributes()->get(LiveComponent::class);

    foreach ($liveComponents as $discoveredAttribute) {
        /** @var LiveComponent|null $attribute */
        $attribute = $discoveredAttribute->createAttributeInstance();

        if ($attribute && ! empty($attribute->name)) {
            $className = $discoveredAttribute->className->toString();
            
            // DEBUG: Log für Popover-Komponenten
            if (str_contains($className, 'Popover')) {
                // Prüfe ob Datei geändert wurde seit Cache-Erstellung
                $filePath = $discoveredAttribute->filePath?->toString();
                if ($filePath && $this->isFileModifiedSinceCache($filePath)) {
                    // Lese Arguments direkt aus Datei
                    $attribute = $this->readAttributeFromFile($className, LiveComponent::class);
                }
                
                error_log(sprintf(
                    "[DEBUG ComponentRegistry] LiveComponent: class=%s, name=%s, arguments=%s, source=%s",
                    $className,
                    $attribute->name,
                    json_encode($discoveredAttribute->arguments),
                    $filePath ?? 'unknown'
                ));
            }
            
            $mappings[] = new ComponentMapping($attribute->name, $className);
            $liveComponentClassNames[] = $className;
        }
    }
    
    // ... rest of method
}

Vorteil: Umgeht den Cache, wenn Dateien geändert wurden.

Vorschlag 4: Cache-Versionierung

Problem: Der Cache hat keine Versionierung, die bei Änderungen der Cache-Struktur hilft.

Lösung: Füge eine Cache-Version hinzu, die bei Änderungen der Cache-Struktur erhöht wird:

// In CacheEntry.php
private const int CACHE_VERSION = 2; // Erhöhe bei Cache-Struktur-Änderungen

public function __construct(
    public readonly DiscoveryRegistry|array $registry,
    public readonly Timestamp $createdAt,
    public readonly string $version = '',
    public readonly CacheLevel $cacheLevel = CacheLevel::NORMAL,
    public readonly CacheTier $cacheTier = CacheTier::HOT,
    public readonly int $cacheVersion = self::CACHE_VERSION
) {
}

// In DiscoveryCacheManager.php
private function isCacheVersionValid(CacheEntry $entry): bool
{
    return $entry->cacheVersion === CacheEntry::CACHE_VERSION;
}

Vorteil: Automatische Cache-Invalidierung bei Cache-Struktur-Änderungen.

Vorschlag 5: Discovery-Registry Refresh-Mechanismus

Problem: Die Discovery-Registry wird nicht automatisch aktualisiert, wenn Dateien geändert werden.

Lösung: Füge einen Refresh-Mechanismus hinzu, der die Discovery-Registry automatisch aktualisiert:

// In DiscoveryServiceBootstrapper.php
public function refreshIfNeeded(): void
{
    $discoveryCacheManager = $this->container->get(DiscoveryCacheManager::class);
    $context = $this->createDiscoveryContext();
    
    $cachedRegistry = $discoveryCacheManager->get($context);
    
    if ($cachedRegistry === null) {
        // Cache ist leer, führe Discovery durch
        $this->bootstrap();
        return;
    }
    
    // Prüfe ob Registry stale ist
    if ($this->isRegistryStale($cachedRegistry, $context)) {
        // Registry ist stale, führe Discovery durch
        $this->bootstrap();
    }
}

Vorteil: Automatische Aktualisierung der Discovery-Registry bei Änderungen.

Empfohlene Implementierungsreihenfolge

  1. Vorschlag 1 (Staleness-Prüfung verbessern) - Sofortige Verbesserung
  2. Vorschlag 2 (Cache-Invalidierung bei Datei-Änderungen) - Langfristige Lösung
  3. Vorschlag 3 (Arguments direkt aus Dateien lesen) - Workaround für aktuelles Problem
  4. Vorschlag 4 (Cache-Versionierung) - Präventive Maßnahme
  5. Vorschlag 5 (Discovery-Registry Refresh-Mechanismus) - Automatisierung

Sofortige Lösung (Workaround)

Für das aktuelle Problem sollte Vorschlag 3 implementiert werden, da er das Problem direkt behebt, ohne die gesamte Cache-Architektur zu ändern.

Langfristige Lösung

Vorschlag 2 sollte langfristig implementiert werden, da er das Problem an der Wurzel löst und verhindert, dass ähnliche Probleme in Zukunft auftreten.