diff --git a/config/modularize.php b/config/modularize.php index fe397e6..eda8c68 100644 --- a/config/modularize.php +++ b/config/modularize.php @@ -22,4 +22,17 @@ * Setting value to false will prevent autloading of module service provider. Eg. ModuleNameServiceProvider */ 'autoload_service_provider' => true, + + /** + * Enable caching of module discovery results to improve boot performance. + * When enabled, module discovery results are cached to avoid repeated filesystem scans on every request. + * Use 'php artisan modularize:cache' to generate cache and 'php artisan modularize:clear' to clear it. + */ + 'cache_enabled' => true, + + /** + * Define the path where the module discovery cache file will be stored. + * Relative to application base path. Defaults to Laravel's bootstrap/cache directory. + */ + 'cache_path' => 'bootstrap/cache/modularize.php', ]; diff --git a/src/Console/Commands/ModuleCacheCommand.php b/src/Console/Commands/ModuleCacheCommand.php new file mode 100644 index 0000000..392431f --- /dev/null +++ b/src/Console/Commands/ModuleCacheCommand.php @@ -0,0 +1,239 @@ +files = $files; + } + + /** + * Execute the console command. + */ + public function handle(): int + { + $this->moduleRootPath = base_path(config('modularize.root_path')); + + if (! is_dir($this->moduleRootPath)) { + $this->components->error('Modules directory does not exist: '.config('modularize.root_path')); + + return self::FAILURE; + } + + $this->components->info('Building module cache...'); + + $manifest = $this->buildModuleManifest(); + + $cachePath = $this->getCachedModulesPath(); + $cacheDirectory = dirname($cachePath); + + // Ensure cache directory exists + if (! is_dir($cacheDirectory)) { + $this->files->makeDirectory($cacheDirectory, 0755, true); + } + + // Write cache file + $content = 'files->put($cachePath, $content); + + $this->components->info('Module cache created successfully.'); + $this->components->twoColumnDetail('Cache file', $cachePath); + $this->components->twoColumnDetail('Modules cached', (string) count($manifest['modules'])); + + return self::SUCCESS; + } + + /** + * Get the path to the cached modules file. + */ + protected function getCachedModulesPath(): string + { + return base_path(config('modularize.cache_path', 'bootstrap/cache/modularize.php')); + } + + /** + * Build the module discovery manifest array. + * + * This method scans the filesystem to discover all modules and their associated + * resources (service providers, configs, routes, etc.). The result will be cached + * to avoid repeated filesystem scans on every request. + */ + protected function buildModuleManifest(): array + { + $manifest = [ + 'modules' => [], + 'service_providers' => [], + 'configs' => [], + 'console_commands' => [], + 'routes' => [], + 'helpers' => [], + 'views' => [], + 'translations' => [], + 'view_components' => [], + ]; + + if (! is_dir($this->moduleRootPath)) { + return $manifest; + } + + $modules = array_map( + 'class_basename', + $this->files->directories($this->moduleRootPath) + ); + + $manifest['modules'] = $modules; + + foreach ($modules as $module) { + // Check for service provider + $provider = "{$module}/Providers/{$module}ServiceProvider.php"; + $providerFile = "{$this->moduleRootPath}/$provider"; + if ($this->files->exists($providerFile)) { + $providerNamespace = $this->rootNamespace.str_replace( + ['/', '.php'], + ['\\', ''], + $provider + ); + + if ( + is_subclass_of($providerNamespace, ServiceProvider::class) + && ! (new ReflectionClass($providerNamespace))->isAbstract() + ) { + $manifest['service_providers'][$module] = $providerNamespace; + } + } + + // Check for config file + $configFile = "{$this->moduleRootPath}/{$module}/config.php"; + if ($this->files->exists($configFile)) { + $manifest['configs'][$module] = Str::slug($module); + } + + // Check for console commands + $consolePath = "{$this->moduleRootPath}/{$module}/Console"; + if (is_dir($consolePath)) { + $commands = []; + foreach ((new Finder)->in($consolePath)->files() as $command) { + $commandClass = $this->rootNamespace.str_replace( + ['/', '.php'], + ['\\', ''], + Str::after($command->getRealPath(), realpath($this->moduleRootPath).DIRECTORY_SEPARATOR) + ); + + if ( + is_subclass_of($commandClass, Command::class) + && ! (new ReflectionClass($commandClass))->isAbstract() + ) { + $commands[] = $commandClass; + } + } + + if (! empty($commands)) { + $manifest['console_commands'][$module] = $commands; + } + } + + // Check for routes + if (config('modularize.autoload_routes')) { + $routeFiles = [ + "{$this->moduleRootPath}/{$module}/routes.php", + "{$this->moduleRootPath}/{$module}/Routes/web.php", + "{$this->moduleRootPath}/{$module}/Routes/api.php", + ]; + + $existingRoutes = []; + foreach ($routeFiles as $routeFile) { + if ($this->files->isDirectory($routeFile)) { + foreach ($this->files->allFiles($routeFile) as $file) { + $existingRoutes[] = $file->getPathname(); + } + } elseif ($this->files->exists($routeFile)) { + $existingRoutes[] = $routeFile; + } + } + + if (! empty($existingRoutes)) { + $manifest['routes'][$module] = $existingRoutes; + } + } + + // Check for helper file + $helperFile = "{$this->moduleRootPath}/{$module}/helper.php"; + if ($this->files->exists($helperFile)) { + $manifest['helpers'][$module] = $helperFile; + } + + // Check for views directory + $viewsPath = "{$this->moduleRootPath}/{$module}/Views"; + if ($this->files->isDirectory($viewsPath)) { + $manifest['views'][$module] = [ + 'path' => $viewsPath, + 'namespace' => $this->getModuleNamespace($module), + ]; + } + + // Check for translations directory + $translationsPath = "{$this->moduleRootPath}/{$module}/Lang"; + if ($this->files->isDirectory($translationsPath)) { + $manifest['translations'][$module] = [ + 'path' => $translationsPath, + 'namespace' => $this->getModuleNamespace($module), + ]; + } + + // Store view component namespace + $manifest['view_components'][$module] = [ + 'namespace' => "Modules\\{$module}\\Components", + 'alias' => $this->getModuleNamespace($module), + ]; + } + + return $manifest; + } + + /** + * Get the module namespace for views and translations. + */ + private function getModuleNamespace(string $name): string + { + return Str::of($name) + ->replace(search: '/', replace: '.') + ->snake('-') + ->replace(search: '.-', replace: '.') + ->lower(); + } +} diff --git a/src/Console/Commands/ModuleClearCacheCommand.php b/src/Console/Commands/ModuleClearCacheCommand.php new file mode 100644 index 0000000..bd4981e --- /dev/null +++ b/src/Console/Commands/ModuleClearCacheCommand.php @@ -0,0 +1,65 @@ +files = $files; + } + + /** + * Execute the console command. + */ + public function handle(): int + { + $cachePath = $this->getCachedModulesPath(); + + if (! $this->files->exists($cachePath)) { + $this->components->info('Module cache does not exist.'); + + return self::SUCCESS; + } + + $this->files->delete($cachePath); + + $this->components->info('Module cache cleared successfully.'); + $this->components->twoColumnDetail('Removed file', $cachePath); + + return self::SUCCESS; + } + + /** + * Get the path to the cached modules file. + */ + protected function getCachedModulesPath(): string + { + return base_path(config('modularize.cache_path', 'bootstrap/cache/modularize.php')); + } +} diff --git a/src/ModularizeServiceProvider.php b/src/ModularizeServiceProvider.php index 50a1fe4..48a1d33 100644 --- a/src/ModularizeServiceProvider.php +++ b/src/ModularizeServiceProvider.php @@ -9,6 +9,8 @@ use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; +use NorbyBaru\Modularize\Console\Commands\ModuleCacheCommand; +use NorbyBaru\Modularize\Console\Commands\ModuleClearCacheCommand; use NorbyBaru\Modularize\Console\Commands\ModuleListCommand; use NorbyBaru\Modularize\Console\Commands\ModuleMakeComponentCommand; use NorbyBaru\Modularize\Console\Commands\ModuleMakeConsoleCommand; @@ -50,11 +52,22 @@ public function boot() { $this->publishConfig(); - if (is_dir($this->moduleRootPath = base_path(config('modularize.root_path')))) { - if (! config('modularize.enable')) { - return; - } + if (! config('modularize.enable')) { + return; + } + + $this->moduleRootPath = base_path(config('modularize.root_path')); + + if (! is_dir($this->moduleRootPath)) { + return; + } + // Check if modules are cached and load from cache if available + if ($this->modulesAreCached()) { + $manifest = $this->loadCachedModuleManifest(); + $this->loadModulesFromManifest($manifest); + } else { + // Fall back to filesystem scanning $modules = array_map( 'class_basename', $this->files->directories($this->moduleRootPath) @@ -72,7 +85,6 @@ public function boot() $this->autoloadViewComponents($module); } } - } /** @@ -113,6 +125,265 @@ protected function publishConfig() } } + /** + * Get the path to the cached modules file. + * + * @return string + */ + protected function getCachedModulesPath() + { + return base_path(config('modularize.cache_path', 'bootstrap/cache/modularize.php')); + } + + /** + * Determine if the module discovery results are cached. + * + * @return bool + */ + protected function modulesAreCached() + { + return config('modularize.cache_enabled', false) && $this->files->exists($this->getCachedModulesPath()); + } + + /** + * Build the module discovery manifest array. + * + * This method scans the filesystem to discover all modules and their associated + * resources (service providers, configs, routes, etc.). The result can be cached + * to avoid repeated filesystem scans on every request. + * + * @return array + */ + protected function buildModuleManifest() + { + $manifest = [ + 'modules' => [], + 'service_providers' => [], + 'configs' => [], + 'console_commands' => [], + 'routes' => [], + 'helpers' => [], + 'views' => [], + 'translations' => [], + 'view_components' => [], + ]; + + if (! is_dir($moduleRootPath = base_path(config('modularize.root_path')))) { + return $manifest; + } + + $modules = array_map( + 'class_basename', + $this->files->directories($moduleRootPath) + ); + + $manifest['modules'] = $modules; + + foreach ($modules as $module) { + // Check for service provider + $provider = "{$module}/Providers/{$module}ServiceProvider.php"; + $providerFile = "{$moduleRootPath}/$provider"; + if ($this->files->exists($providerFile)) { + $providerNamespace = $this->rootNamespace.str_replace( + ['/', '.php'], + ['\\', ''], + $provider + ); + + if ( + is_subclass_of($providerNamespace, ServiceProvider::class) + && ! (new ReflectionClass($providerNamespace))->isAbstract() + ) { + $manifest['service_providers'][$module] = $providerNamespace; + } + } + + // Check for config file + $configFile = "{$moduleRootPath}/{$module}/config.php"; + if ($this->files->exists($configFile)) { + $manifest['configs'][$module] = Str::slug($module); + } + + // Check for console commands + $consolePath = "{$moduleRootPath}/{$module}/Console"; + if (is_dir($consolePath)) { + $commands = []; + foreach ((new Finder)->in($consolePath)->files() as $command) { + $commandClass = $this->rootNamespace.str_replace( + ['/', '.php'], + ['\\', ''], + Str::after($command->getRealPath(), realpath($moduleRootPath).DIRECTORY_SEPARATOR) + ); + + if ( + is_subclass_of($commandClass, Command::class) + && ! (new ReflectionClass($commandClass))->isAbstract() + ) { + $commands[] = $commandClass; + } + } + + if (! empty($commands)) { + $manifest['console_commands'][$module] = $commands; + } + } + + // Check for routes + if (config('modularize.autoload_routes')) { + $routeFiles = [ + "{$moduleRootPath}/{$module}/routes.php", + "{$moduleRootPath}/{$module}/Routes/web.php", + "{$moduleRootPath}/{$module}/Routes/api.php", + ]; + + $existingRoutes = []; + foreach ($routeFiles as $routeFile) { + if ($this->files->isDirectory($routeFile)) { + foreach ($this->files->allFiles($routeFile) as $file) { + $existingRoutes[] = $file->getPathname(); + } + } elseif ($this->files->exists($routeFile)) { + $existingRoutes[] = $routeFile; + } + } + + if (! empty($existingRoutes)) { + $manifest['routes'][$module] = $existingRoutes; + } + } + + // Check for helper file + $helperFile = "{$moduleRootPath}/{$module}/helper.php"; + if ($this->files->exists($helperFile)) { + $manifest['helpers'][$module] = $helperFile; + } + + // Check for views directory + $viewsPath = "{$moduleRootPath}/{$module}/Views"; + if ($this->files->isDirectory($viewsPath)) { + $manifest['views'][$module] = [ + 'path' => $viewsPath, + 'namespace' => $this->getModuleNamespace($module), + ]; + } + + // Check for translations directory + $translationsPath = "{$moduleRootPath}/{$module}/Lang"; + if ($this->files->isDirectory($translationsPath)) { + $manifest['translations'][$module] = [ + 'path' => $translationsPath, + 'namespace' => $this->getModuleNamespace($module), + ]; + } + + // Store view component namespace + $manifest['view_components'][$module] = [ + 'namespace' => "Modules\\{$module}\\Components", + 'alias' => $this->getModuleNamespace($module), + ]; + } + + return $manifest; + } + + /** + * Load the cached module manifest. + * + * @return array + */ + protected function loadCachedModuleManifest() + { + $cachePath = $this->getCachedModulesPath(); + + if (! $this->files->exists($cachePath)) { + return [ + 'modules' => [], + 'service_providers' => [], + 'configs' => [], + 'console_commands' => [], + 'routes' => [], + 'helpers' => [], + 'views' => [], + 'translations' => [], + 'view_components' => [], + ]; + } + + return require $cachePath; + } + + /** + * Load all modules from the cached manifest. + * + * @return void + */ + protected function loadModulesFromManifest(array $manifest) + { + // Load service providers + foreach ($manifest['service_providers'] ?? [] as $providerClass) { + $this->app->register($providerClass); + } + + // Load configs + foreach ($manifest['configs'] ?? [] as $module => $configKey) { + $configFile = "{$this->moduleRootPath}/{$module}/config.php"; + if ($this->files->exists($configFile)) { + $this->mergeConfigFrom($configFile, $configKey); + } + } + + // Load console commands + if ($this->app->runningInConsole()) { + foreach ($manifest['console_commands'] ?? [] as $commands) { + foreach ($commands as $commandClass) { + $this->commands($commandClass); + } + } + } + + // Load migrations + foreach ($manifest['modules'] ?? [] as $module) { + $this->loadMigrationsFrom("{$this->moduleRootPath}/{$module}/Database/migrations"); + } + + // Load routes + if (config('modularize.autoload_routes') && ! ($this->app instanceof CachesRoutes && $this->app->routesAreCached())) { + foreach ($manifest['routes'] ?? [] as $routeFiles) { + foreach ($routeFiles as $routeFile) { + if ($this->files->exists($routeFile)) { + include $routeFile; + } + } + } + } + + // Load helpers + foreach ($manifest['helpers'] ?? [] as $helperFile) { + if ($this->files->exists($helperFile)) { + include_once $helperFile; + } + } + + // Load views + foreach ($manifest['views'] ?? [] as $viewConfig) { + if ($this->files->isDirectory($viewConfig['path'])) { + $this->loadViewsFrom($viewConfig['path'], $viewConfig['namespace']); + } + } + + // Load translations + foreach ($manifest['translations'] ?? [] as $translationConfig) { + if ($this->files->isDirectory($translationConfig['path'])) { + $this->loadTranslationsFrom($translationConfig['path'], $translationConfig['namespace']); + } + } + + // Register view components + foreach ($manifest['view_components'] ?? [] as $componentConfig) { + Blade::componentNamespace($componentConfig['namespace'], $componentConfig['alias']); + } + } + private function getModuleNamespace(string $name): string { return Str::of($name) @@ -289,6 +560,8 @@ private function autoloadTranslations(string $moduleRootPath, string $module): v protected function registerMakeCommand() { $this->commands([ + ModuleCacheCommand::class, + ModuleClearCacheCommand::class, ModuleMakeComponentCommand::class, ModuleMakeConsoleCommand::class, ModuleMakeControllerCommand::class, diff --git a/tests/CacheCommandTest.php b/tests/CacheCommandTest.php new file mode 100644 index 0000000..a050527 --- /dev/null +++ b/tests/CacheCommandTest.php @@ -0,0 +1,374 @@ +getCachePath(); + if (File::exists($cachePath)) { + File::delete($cachePath); + } + + parent::tearDown(); + } + + public function test_it_creates_cache_file_successfully() + { + // Create a basic module structure + $this->files->ensureDirectoryExists($this->getModulePath($this->moduleName)); + + $this->artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $this->assertFileExists($cachePath); + + // Verify cache content is valid PHP + $cachedData = require $cachePath; + $this->assertIsArray($cachedData); + $this->assertArrayHasKey('modules', $cachedData); + $this->assertArrayHasKey('service_providers', $cachedData); + $this->assertArrayHasKey('configs', $cachedData); + $this->assertArrayHasKey('console_commands', $cachedData); + $this->assertArrayHasKey('routes', $cachedData); + $this->assertArrayHasKey('helpers', $cachedData); + $this->assertArrayHasKey('views', $cachedData); + $this->assertArrayHasKey('translations', $cachedData); + $this->assertArrayHasKey('view_components', $cachedData); + } + + public function test_it_fails_when_modules_directory_does_not_exist() + { + // Ensure the modules directory does not exist + $this->cleanUp(); + + $this->artisan('modularize:cache') + ->assertFailed(); + + // Cache file should not be created + $cachePath = $this->getCachePath(); + $this->assertFileDoesNotExist($cachePath); + } + + public function test_it_creates_cache_directory_if_not_exists() + { + // Create module structure + $this->files->ensureDirectoryExists($this->getModulePath($this->moduleName)); + + // Ensure cache directory doesn't exist + $cacheDirectory = dirname($this->getCachePath()); + if ($this->files->exists($cacheDirectory)) { + $this->files->deleteDirectory($cacheDirectory); + } + + $this->artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $this->assertFileExists($cachePath); + $this->assertDirectoryExists($cacheDirectory); + } + + public function test_it_caches_single_module() + { + // Create a basic module structure + $this->files->ensureDirectoryExists($this->getModulePath($this->moduleName)); + + $this->artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $cachedData = require $cachePath; + + $this->assertCount(1, $cachedData['modules']); + $this->assertContains($this->moduleName, $cachedData['modules']); + } + + public function test_it_caches_multiple_modules() + { + // Create multiple modules + $modules = ['Blog', 'Shop', 'Forum']; + + foreach ($modules as $module) { + $this->files->ensureDirectoryExists($this->getModulePath($module)); + } + + $this->artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $cachedData = require $cachePath; + + $this->assertCount(3, $cachedData['modules']); + foreach ($modules as $module) { + $this->assertContains($module, $cachedData['modules']); + } + } + + public function test_it_caches_service_provider() + { + $module = 'Blog'; + $providerPath = $this->getModulePath($module).'/Providers'; + $this->files->ensureDirectoryExists($providerPath); + + // Create a valid service provider + $providerContent = <<files->put("{$providerPath}/{$module}ServiceProvider.php", $providerContent); + + // Require the file so the class is available for reflection + require_once "{$providerPath}/{$module}ServiceProvider.php"; + + $this->artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $cachedData = require $cachePath; + + $this->assertArrayHasKey($module, $cachedData['service_providers']); + $this->assertEquals("Modules\\{$module}\\Providers\\{$module}ServiceProvider", $cachedData['service_providers'][$module]); + } + + public function test_it_caches_config_file() + { + $module = 'Blog'; + $this->files->ensureDirectoryExists($this->getModulePath($module)); + + // Create config file + $this->files->put( + $this->getModulePath($module).'/config.php', + "artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $cachedData = require $cachePath; + + $this->assertArrayHasKey($module, $cachedData['configs']); + } + + public function test_it_caches_routes() + { + $module = 'Blog'; + $this->files->ensureDirectoryExists($this->getModulePath($module)); + + // Create routes.php + $this->files->put( + $this->getModulePath($module).'/routes.php', + "artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $cachedData = require $cachePath; + + $this->assertArrayHasKey($module, $cachedData['routes']); + $this->assertNotEmpty($cachedData['routes'][$module]); + } + + public function test_it_caches_helper_file() + { + $module = 'Blog'; + $this->files->ensureDirectoryExists($this->getModulePath($module)); + + // Create helper file + $this->files->put( + $this->getModulePath($module).'/helper.php', + "artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $cachedData = require $cachePath; + + $this->assertArrayHasKey($module, $cachedData['helpers']); + } + + public function test_it_caches_views_directory() + { + $module = 'Blog'; + $viewsPath = $this->getModulePath($module).'/Views'; + $this->files->ensureDirectoryExists($viewsPath); + + $this->artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $cachedData = require $cachePath; + + $this->assertArrayHasKey($module, $cachedData['views']); + $this->assertArrayHasKey('path', $cachedData['views'][$module]); + $this->assertArrayHasKey('namespace', $cachedData['views'][$module]); + } + + public function test_it_caches_translations_directory() + { + $module = 'Blog'; + $translationsPath = $this->getModulePath($module).'/Lang'; + $this->files->ensureDirectoryExists($translationsPath); + + $this->artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $cachedData = require $cachePath; + + $this->assertArrayHasKey($module, $cachedData['translations']); + $this->assertArrayHasKey('path', $cachedData['translations'][$module]); + $this->assertArrayHasKey('namespace', $cachedData['translations'][$module]); + } + + public function test_it_overwrites_existing_cache() + { + // Create initial module + $this->files->ensureDirectoryExists($this->getModulePath('Blog')); + + $this->artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $firstCachedData = require $cachePath; + $this->assertCount(1, $firstCachedData['modules']); + + // Add another module and re-cache + $this->files->ensureDirectoryExists($this->getModulePath('Shop')); + + $this->artisan('modularize:cache') + ->assertSuccessful(); + + $secondCachedData = require $cachePath; + $this->assertCount(2, $secondCachedData['modules']); + } + + public function test_clear_command_removes_cache_file() + { + // Create cache first + $this->files->ensureDirectoryExists($this->getModulePath($this->moduleName)); + + $this->artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $this->assertFileExists($cachePath); + + // Clear cache + $this->artisan('modularize:clear') + ->assertSuccessful(); + + $this->assertFileDoesNotExist($cachePath); + } + + public function test_clear_command_succeeds_when_cache_does_not_exist() + { + $cachePath = $this->getCachePath(); + + // Ensure cache doesn't exist + if (File::exists($cachePath)) { + File::delete($cachePath); + } + + $this->artisan('modularize:clear') + ->assertSuccessful(); + } + + public function test_it_caches_module_with_all_features() + { + // Create a complete module with all features + $module = 'Blog'; + $modulePath = $this->getModulePath($module); + + // Create service provider + $providerPath = "{$modulePath}/Providers"; + $this->files->ensureDirectoryExists($providerPath); + $providerContent = <<files->put("{$providerPath}/{$module}ServiceProvider.php", $providerContent); + + // Require the file so the class is available for reflection + require_once "{$providerPath}/{$module}ServiceProvider.php"; + + // Create config + $this->files->put("{$modulePath}/config.php", "files->put("{$modulePath}/routes.php", "files->put("{$modulePath}/helper.php", "files->ensureDirectoryExists("{$modulePath}/Views"); + + // Create translations directory + $this->files->ensureDirectoryExists("{$modulePath}/Lang"); + + $this->artisan('modularize:cache') + ->assertSuccessful(); + + $cachePath = $this->getCachePath(); + $cachedData = require $cachePath; + + // Verify all features are cached + $this->assertContains($module, $cachedData['modules']); + $this->assertArrayHasKey($module, $cachedData['service_providers']); + $this->assertArrayHasKey($module, $cachedData['configs']); + $this->assertArrayHasKey($module, $cachedData['routes']); + $this->assertArrayHasKey($module, $cachedData['helpers']); + $this->assertArrayHasKey($module, $cachedData['views']); + $this->assertArrayHasKey($module, $cachedData['translations']); + $this->assertArrayHasKey($module, $cachedData['view_components']); + } +}