From 39558fc949da6635f300cd35339b79d08d3b1ea6 Mon Sep 17 00:00:00 2001 From: Armin Date: Fri, 5 Sep 2025 00:54:40 +0330 Subject: [PATCH] patch: cache issues and filament panel resolver --- config/modulite.php | 32 ++++---- .../Commands/ModuliteClearCacheCommand.php | 7 +- .../Commands/ModuliteOptimizeCommand.php | 80 +++++++++++++++++-- src/Providers/ModuliteServiceProvider.php | 56 +++++++------ src/Services/ComponentDiscoveryService.php | 4 +- src/Services/UnifiedCacheManager.php | 15 +++- 6 files changed, 133 insertions(+), 61 deletions(-) diff --git a/config/modulite.php b/config/modulite.php index 817694e..0520245 100644 --- a/config/modulite.php +++ b/config/modulite.php @@ -85,7 +85,7 @@ 'auto_register' => env('MODULITE_AUTO_REGISTER_PANELS', true), 'sort_by' => 'priority', // 'priority', 'name', 'none' 'respect_environment' => true, - 'validate_before_register' => app()->hasDebugModeEnabled(), + 'validate_before_register' => env('MODULITE_VALIDATE_BEFORE_REGISTER', env('APP_DEBUG', false)), ], /* @@ -189,7 +189,7 @@ 'registration' => [ 'auto_register' => env('MODULITE_AUTO_REGISTER_COMPONENTS', true), 'sort_by' => env('MODULITE_SORT_COMPONENTS', 'none'), // 'name', 'priority', 'none' - 'validate_before_register' => env('MODULITE_VALIDATE_COMPONENTS', app()->hasDebugModeEnabled()), + 'validate_before_register' => env('MODULITE_VALIDATE_COMPONENTS', env('APP_DEBUG', false)), 'group_by_module' => true, ], @@ -251,7 +251,7 @@ | request. Recommended: true for production, false for development. | */ - 'enabled' => env('MODULITE_CACHE_ENABLED', !app()->hasDebugModeEnabled()), + 'enabled' => env('MODULITE_CACHE_ENABLED', !env('APP_DEBUG', false)), /* |-------------------------------------------------------------------------- @@ -275,7 +275,7 @@ | Set to 0 in production for maximum performance (never expires). | */ - 'ttl' => env('MODULITE_CACHE_TTL', app()->hasDebugModeEnabled() ? 300 : 0), + 'ttl' => env('MODULITE_CACHE_TTL', env('APP_DEBUG', false) ? 300 : 0), /* |-------------------------------------------------------------------------- @@ -286,7 +286,7 @@ | Only works in development mode for performance reasons. | */ - 'auto_invalidate' => app()->hasDebugModeEnabled(), + 'auto_invalidate' => env('MODULITE_AUTO_INVALIDATE', env('APP_DEBUG', false)), /* |-------------------------------------------------------------------------- @@ -312,9 +312,9 @@ */ 'optimizations' => [ 'static_caching' => env('MODULITE_STATIC_CACHING', true), - 'defer_validation' => env('MODULITE_DEFER_VALIDATION', !app()->hasDebugModeEnabled()), + 'defer_validation' => env('MODULITE_DEFER_VALIDATION', !env('APP_DEBUG', false)), 'skip_duplicate_panels' => env('MODULITE_SKIP_DUPLICATE_PANELS', true), - 'disable_reflection' => env('MODULITE_DISABLE_REFLECTION', app()->isProduction()), + 'disable_reflection' => env('MODULITE_DISABLE_REFLECTION', 'production' === env('APP_ENV')), ], ], @@ -436,7 +436,7 @@ | Enable/disable logging and configure log channels. | */ - 'enabled' => env('MODULITE_LOGGING_ENABLED', app()->hasDebugModeEnabled()), + 'enabled' => env('MODULITE_LOGGING_ENABLED', env('APP_DEBUG', false)), 'channel' => env('MODULITE_LOG_CHANNEL', 'stack'), 'level' => env('MODULITE_LOG_LEVEL', 'info'), @@ -448,9 +448,9 @@ | Log performance metrics for optimization and monitoring. | */ - 'log_discovery_time' => app()->hasDebugModeEnabled(), - 'log_cache_hits' => app()->hasDebugModeEnabled(), - 'log_scan_stats' => app()->hasDebugModeEnabled(), + 'log_discovery_time' => env('MODULITE_LOG_DISCOVERY_TIME', env('APP_DEBUG', false)), + 'log_cache_hits' => env('MODULITE_LOG_CACHE_HITS', env('APP_DEBUG', false)), + 'log_scan_stats' => env('MODULITE_LOG_SCAN_STATS', env('APP_DEBUG', false)), ], /* @@ -470,9 +470,9 @@ | Control whether errors are thrown or handled silently. | */ - 'fail_silently' => !app()->hasDebugModeEnabled(), - 'log_errors' => true, - 'max_errors_per_scan' => 10, + 'fail_silently' => env('MODULITE_FAIL_SILENTLY', !env('APP_DEBUG', false)), + 'log_errors' => env('MODULITE_LOG_ERRORS', true), + 'max_errors_per_scan' => env('MODULITE_MAX_ERRORS_PER_SCAN', 10), /* |-------------------------------------------------------------------------- @@ -482,7 +482,7 @@ | Configure handling of validation errors during discovery. | */ - 'throw_on_invalid_class' => app()->hasDebugModeEnabled(), - 'throw_on_missing_requirements' => app()->hasDebugModeEnabled(), + 'throw_on_invalid_class' => env('MODULITE_THROW_ON_INVALID_CLASS', env('APP_DEBUG', false)), + 'throw_on_missing_requirements' => env('MODULITE_THROW_ON_MISSING_REQUIREMENTS', env('APP_DEBUG', false)), ], ]; diff --git a/src/Console/Commands/ModuliteClearCacheCommand.php b/src/Console/Commands/ModuliteClearCacheCommand.php index c6bacc7..bea9425 100644 --- a/src/Console/Commands/ModuliteClearCacheCommand.php +++ b/src/Console/Commands/ModuliteClearCacheCommand.php @@ -10,6 +10,7 @@ /** * Command to clear all Modulite caches. + * This command is automatically called by Laravel's `optimize:clear` command. */ class ModuliteClearCacheCommand extends Command { @@ -20,12 +21,6 @@ class ModuliteClearCacheCommand extends Command public function handle(CacheManagerInterface $cacheManager): int { - if (!$this->option('force') && !$this->confirm('Are you sure you want to clear all Modulite caches?')) - { - $this->info('Cache clear cancelled.'); - return self::SUCCESS; - } - $this->info('Clearing Modulite caches...'); try diff --git a/src/Console/Commands/ModuliteOptimizeCommand.php b/src/Console/Commands/ModuliteOptimizeCommand.php index e6e0331..32d2bc5 100644 --- a/src/Console/Commands/ModuliteOptimizeCommand.php +++ b/src/Console/Commands/ModuliteOptimizeCommand.php @@ -8,6 +8,7 @@ use PanicDevs\Modulite\Contracts\CacheManagerInterface; use PanicDevs\Modulite\Contracts\PanelScannerInterface; use PanicDevs\Modulite\Contracts\ComponentScannerInterface; +use PanicDevs\Modulite\Contracts\ModuleResolverInterface; use Throwable; /** @@ -28,7 +29,8 @@ class ModuliteOptimizeCommand extends Command * @var string */ protected $signature = 'modulite:cache - {--force : Force cache regeneration even if cache exists}'; + {--force : Force cache regeneration even if cache exists} + {--enable-cache : Temporarily enable caching even in debug mode}'; /** * The console command description. @@ -43,12 +45,21 @@ class ModuliteOptimizeCommand extends Command public function handle( CacheManagerInterface $cacheManager, PanelScannerInterface $panelScanner, - ComponentScannerInterface $componentScanner + ComponentScannerInterface $componentScanner, + ModuleResolverInterface $moduleResolver ): int { $this->info('Optimizing Modulite caches...'); try { + // Temporarily enable cache if requested + if ($this->option('enable-cache')) + { + $this->info('Temporarily enabling cache for this operation...'); + // Enable cache in the manager for this command only + $this->enableCacheForCommand($cacheManager); + } + // Clear existing caches if force flag is used if ($this->option('force')) { @@ -60,6 +71,11 @@ public function handle( $this->line('• Warming panel discovery cache...'); $panels = $panelScanner->discoverPanels(); $panelCount = count($panels); + + // Store panels in cache using the same key as the service provider + $panelCacheKey = $this->generatePanelCacheKey($moduleResolver); + $cacheManager->put($panelCacheKey, $panels); + $this->line(" ✓ Found {$panelCount} panel providers"); // Warm component discovery cache for all discovered panels @@ -69,9 +85,14 @@ public function handle( foreach ($panels as $panelClass) { // Extract panel ID from class name (e.g., UserPanelProvider -> user) - $panelId = $this->extractPanelIdFromClass($panelClass); - $components = $componentScanner->discoverComponents($panelId); - $totalComponents += array_sum(array_map('count', $components)); + $panelId = $this->extractPanelIdFromClass($panelClass); + + // Force cache warming by calling discoverComponents which stores in cache + $components = $componentScanner->discoverComponents($panelId); + $componentCount = array_sum(array_map('count', $components)); + $totalComponents += $componentCount; + + $this->line(" - {$panelId}: {$componentCount} components"); } $this->line(" ✓ Found {$totalComponents} components"); @@ -119,6 +140,55 @@ protected function extractPanelIdFromClass(string $className): string return mb_strtolower($panelId); } + /** + * Temporarily enable cache for this command operation. + */ + protected function enableCacheForCommand(CacheManagerInterface $cacheManager): void + { + // Check if cache manager supports runtime enable (UnifiedCacheManager does) + if (method_exists($cacheManager, 'enableTemporarily')) + { + $cacheManager->enableTemporarily(); + } else + { + $this->warn('Cache manager does not support temporary enabling. Use MODULITE_CACHE_ENABLED=true instead.'); + } + } + + /** + * Generate cache key for panels (same logic as ModuliteServiceProvider). + */ + protected function generatePanelCacheKey(ModuleResolverInterface $moduleResolver): string + { + // Use the module resolver to get enabled modules + $enabledModules = $moduleResolver->getEnabledModules(); + + // Include module names - use simple array for consistent hashing + $moduleData = $enabledModules->sort()->values()->toArray(); + + // Include the module resolver approach in cache key + $approach = config('modulite.modules.approach', 'nwidart'); + + // Include configuration in cache key + $configHash = md5(serialize([ + 'panels' => config('modulite.panels', []), + 'components' => config('modulite.components', []), + 'modules' => config('modulite.modules', []) + ])); + + // Include environment + $environment = config('app.env', 'production'); + + $keyData = [ + 'modules' => $moduleData, + 'approach' => $approach, + 'config' => $configHash, + 'environment' => $environment, + ]; + + return 'panels:'.md5(serialize($keyData)); + } + /** * Display cache file information. */ diff --git a/src/Providers/ModuliteServiceProvider.php b/src/Providers/ModuliteServiceProvider.php index 7616826..dc17118 100644 --- a/src/Providers/ModuliteServiceProvider.php +++ b/src/Providers/ModuliteServiceProvider.php @@ -79,7 +79,13 @@ public function register(): void { $this->registerConfiguration(); $this->registerCoreServices(); - $this->app->beforeResolving(self::FILAMENT_NAMESPACE, fn() => $this->registerPanelDiscovery()); + if ('nwidart' === config('modulite.modules.approach')) + { + $this->app->beforeResolving('filament', fn() => $this->registerPanelDiscovery()); + } else + { + $this->registerPanelDiscovery(); + } $this->registerCacheInvalidationListeners(); } @@ -96,6 +102,7 @@ public function boot(): void $this->publishConfiguration(); $this->validateConfiguration(); $this->setupDevelopmentHelpers(); + $this->registerCommands(); } /** @@ -119,7 +126,7 @@ protected function registerCoreServices(): void }); // Register ModuleResolver based on configuration - $this->app->singleton(ModuleResolverInterface::class, fn (Application $app) => $this->createModuleResolver($app)); + $this->app->singleton(ModuleResolverInterface::class, fn(Application $app) => $this->createModuleResolver($app)); // Register PanelScannerService with dependencies $this->app->singleton(PanelScannerInterface::class, function (Application $app) @@ -356,7 +363,7 @@ protected function publishConfiguration(): void */ protected function validateConfiguration(): void { - if (!$this->app->hasDebugModeEnabled()) + if (!config('app.debug', false)) { return; } @@ -393,16 +400,7 @@ protected function validateConfiguration(): void } } - // Validate cache configuration - if ($config['cache']['enabled'] ?? false) - { - $driver = $config['cache']['driver'] ?? 'file'; - if (!in_array($driver, ['file', 'redis', 'memcached', 'array', 'database'], true)) - { - Log::channel(config('modulite.logging.channel', 'default')) - ->warning("Modulite cache driver '{$driver}' may not be supported"); - } - } + // Cache configuration validation removed as we use file-based cache only } /** @@ -410,26 +408,26 @@ protected function validateConfiguration(): void */ protected function setupDevelopmentHelpers(): void { - // Register artisan commands if available - if ($this->app->runningInConsole()) - { - $this->registerConsoleCommands(); - } + // Development helpers are now registered in registerCommands() } /** - * Register console commands for development. + * Register console commands. */ - protected function registerConsoleCommands(): void + protected function registerCommands(): void { - $this->commands([ - ModuliteClearCacheCommand::class, - ModuliteStatusCommand::class, - ModuliteOptimizeCommand::class, - ]); - - // Note: Laravel optimize integration removed to prevent circular dependencies - // Users should manually run: php artisan modulite:cache + // Only register commands in console environment + if ($this->app->runningInConsole()) + { + $this->commands([ + ModuliteClearCacheCommand::class, + ModuliteStatusCommand::class, + ModuliteOptimizeCommand::class, + ]); + + // Register optimization commands with Laravel's optimize system + $this->optimizes('modulite:cache', clear: 'modulite:clear'); + } } /** @@ -444,7 +442,7 @@ protected function shouldPerformDiscovery(): bool } // In production, always perform discovery to ensure routes are cached properly - if ($this->app->isProduction()) + if ('production' === config('app.env')) { return true; } diff --git a/src/Services/ComponentDiscoveryService.php b/src/Services/ComponentDiscoveryService.php index 2617ce1..297ebd5 100644 --- a/src/Services/ComponentDiscoveryService.php +++ b/src/Services/ComponentDiscoveryService.php @@ -71,13 +71,13 @@ public function discoverComponents(string $panelName): array } // Only measure time in development - $startTime = app()->hasDebugModeEnabled() ? microtime(true) : 0; + $startTime = config('app.debug', false) ? microtime(true) : 0; // Discover components efficiently $components = $this->performOptimizedDiscovery($panelName); // Update stats only in development - if (app()->hasDebugModeEnabled()) + if (config('app.debug', false)) { $this->stats['scan_time'] = microtime(true) - $startTime; } diff --git a/src/Services/UnifiedCacheManager.php b/src/Services/UnifiedCacheManager.php index a1313c4..e55a7f0 100644 --- a/src/Services/UnifiedCacheManager.php +++ b/src/Services/UnifiedCacheManager.php @@ -73,7 +73,7 @@ public function get(string $key, mixed $default = null): mixed { unset($this->cache['data'][$key]); // Defer cache save to avoid I/O during request - if (!app()->isProduction()) + if ('production' !== env('APP_ENV')) { $this->scheduleDelayedSave(); } @@ -220,7 +220,7 @@ protected function loadCache(): void // Production optimization: use static cache to avoid repeated file includes $cacheKey = $this->cacheFile; - if (app()->isProduction() && isset(static::$staticCache[$cacheKey])) + if ('production' === env('APP_ENV') && isset(static::$staticCache[$cacheKey])) { $this->cache = static::$staticCache[$cacheKey]; $this->loaded = true; @@ -261,7 +261,7 @@ protected function loadCache(): void } // Store in static cache for production - if (app()->isProduction()) + if ('production' === env('APP_ENV')) { static::$staticCache[$cacheKey] = $this->cache; } @@ -326,6 +326,15 @@ public function isCacheEnabled(): bool return $this->enabled; } + /** + * Temporarily enable caching for command operations. + * Useful for development when cache is normally disabled. + */ + public function enableTemporarily(): void + { + $this->enabled = true; + } + /** * Schedule a delayed save to avoid I/O during critical paths. */