diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f880bb2..b44e5d5 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -22,13 +22,16 @@ jobs: extensions: dom, curl, libxml, mbstring, zip, pcntl, bcmath, intl, iconv coverage: pcov + - name: Get composer cache directory + id: composer-cache + run: | + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: - path: | - vendor - ${{ steps.composer-cache-files-dir.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('composer.json') }} + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: | ${{ runner.os }}-composer- @@ -38,11 +41,10 @@ jobs: run: composer install --no-progress --no-interaction --prefer-dist - name: Run and publish code coverage - uses: paambaati/codeclimate-action@v5.0.0 + uses: paambaati/codeclimate-action@v9.0 env: CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} with: coverageCommand: vendor/bin/phpunit --coverage-clover ${{ github.workspace }}/clover.xml - debug: true coverageLocations: "${{github.workspace}}/clover.xml:clover" diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml index d5424d4..4efb99f 100644 --- a/.github/workflows/php-cs-fixer.yml +++ b/.github/workflows/php-cs-fixer.yml @@ -1,6 +1,11 @@ name: Code Style -on: [ pull_request, push ] +on: + push: + branches: + - main + - 3.x + pull_request: jobs: coverage: @@ -10,21 +15,25 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 8.3 extensions: dom, curl, libxml, mbstring, zip, pcntl, bcmath, intl, iconv + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: | + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: - path: | - vendor - ${{ steps.composer-cache-files-dir.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('composer.json') }} + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: | ${{ runner.os }}-composer- diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index de5c9d2..b821934 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -2,6 +2,9 @@ name: PHPUnit on: push: + branches: + - main + - 3.x pull_request: schedule: - cron: '0 14 * * 3' # Run Wednesdays at 2pm EST @@ -12,13 +15,8 @@ jobs: matrix: dependency-version: [ stable, lowest ] os: [ ubuntu-latest, windows-latest ] - laravel: [ 10.*, 11.*, 12.* ] - php: [ 8.1, 8.2, 8.3, 8.4 ] - exclude: - - php: 8.1 - laravel: 11.* - - php: 8.1 - laravel: 12.* + laravel: [ 11.*, 12.* ] + php: [ 8.3, 8.4 ] runs-on: ${{ matrix.os }} timeout-minutes: 10 @@ -37,23 +35,22 @@ jobs: tools: composer:v2 - name: Register composer cache directory - id: composer-cache-files-dir + id: composer-cache run: | - echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 + if: ${{ steps.composer-cache.outputs.dir != '' }} with: - path: | - vendor - ${{ steps.composer-cache-files-dir.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('composer.json') }} + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: | ${{ runner.os }}-composer- - name: Install dependencies run: | - composer require --no-progress --no-interaction --prefer-dist --update-with-all-dependencies --prefer-${{ matrix.dependency-version }} "illuminate/support:${{ matrix.laravel }}" + composer require --no-interaction --prefer-dist --update-with-all-dependencies --prefer-${{ matrix.dependency-version }} "illuminate/support:${{ matrix.laravel }}" - name: Execute tests run: vendor/bin/phpunit diff --git a/composer.json b/composer.json index 3776ca0..f5789b3 100644 --- a/composer.json +++ b/composer.json @@ -16,17 +16,17 @@ "type": "library", "license": "MIT", "require": { - "php": ">=8.0", + "php": ">=8.3", "ext-simplexml": "*", "ext-dom": "*", "composer/composer": "^2.1", - "illuminate/support": "^9|^10|^11|^12|13.x-dev|dev-master|dev-main" + "illuminate/support": "^11|^12|13.x-dev|dev-master|dev-main" }, "require-dev": { - "orchestra/testbench": "^7.52|^8.33|^9.11|^10.0|dev-master|dev-main", + "orchestra/testbench": "^9.11|^10.0|dev-master|dev-main", "friendsofphp/php-cs-fixer": "^3.14", "mockery/mockery": "^1.5", - "phpunit/phpunit": "^9.5|^10.5|^11.5", + "phpunit/phpunit": "^10.5|^11.5", "ext-json": "*", "livewire/livewire": "^2.5|^3.0" }, @@ -48,8 +48,7 @@ "laravel": { "providers": [ "InterNACHI\\Modular\\Support\\ModularServiceProvider", - "InterNACHI\\Modular\\Support\\ModularizedCommandsServiceProvider", - "InterNACHI\\Modular\\Support\\ModularEventServiceProvider" + "InterNACHI\\Modular\\Support\\ModularizedCommandsServiceProvider" ], "aliases": { "Modules": "InterNACHI\\Modular\\Support\\Facades\\Modules" diff --git a/config.php b/config/app-modules.php similarity index 100% rename from config.php rename to config/app-modules.php diff --git a/src/Console/Commands/Make/MakeModule.php b/src/Console/Commands/Make/MakeModule.php index 0dcdd61..997f4d6 100644 --- a/src/Console/Commands/Make/MakeModule.php +++ b/src/Console/Commands/Make/MakeModule.php @@ -94,13 +94,13 @@ public function handle() $this->setUpStyles(); $this->newLine(); - + if ($this->shouldAbortToPublishConfig()) { return 0; } - + $this->ensureModulesDirectoryExists(); - + $this->writeStubs(); $this->updateCoreComposerConfig(); @@ -115,6 +115,11 @@ public function handle() return 0; } + public function newLine($count = 1) + { + $this->getOutput()->newLine($count); + } + protected function shouldAbortToPublishConfig(): bool { if ( @@ -315,11 +320,6 @@ protected function title($title) $this->getOutput()->title($title); } - public function newLine($count = 1) - { - $this->getOutput()->newLine($count); - } - protected function getStubs(): array { if (is_array($custom_stubs = config('app-modules.stubs'))) { diff --git a/src/Console/Commands/Make/Modularize.php b/src/Console/Commands/Make/Modularize.php index a4aad98..91866a3 100644 --- a/src/Console/Commands/Make/Modularize.php +++ b/src/Console/Commands/Make/Modularize.php @@ -8,6 +8,16 @@ trait Modularize { use \InterNACHI\Modular\Console\Commands\Modularize; + public function call($command, array $arguments = []) + { + // Pass the --module flag on to subsequent commands + if ($module = $this->option('module')) { + $arguments['--module'] = $module; + } + + return $this->runCommand($command, $arguments, $this->output); + } + protected function getDefaultNamespace($rootNamespace) { $namespace = parent::getDefaultNamespace($rootNamespace); @@ -80,14 +90,4 @@ protected function getPath($name) return $path; } - - public function call($command, array $arguments = []) - { - // Pass the --module flag on to subsequent commands - if ($module = $this->option('module')) { - $arguments['--module'] = $module; - } - - return $this->runCommand($command, $arguments, $this->output); - } } diff --git a/src/Console/Commands/ModulesCache.php b/src/Console/Commands/ModulesCache.php index 1efe3bc..47a93ab 100644 --- a/src/Console/Commands/ModulesCache.php +++ b/src/Console/Commands/ModulesCache.php @@ -3,11 +3,7 @@ namespace InterNACHI\Modular\Console\Commands; use Illuminate\Console\Command; -use Illuminate\Filesystem\Filesystem; -use InterNACHI\Modular\Support\ModuleConfig; -use InterNACHI\Modular\Support\ModuleRegistry; -use LogicException; -use Throwable; +use InterNACHI\Modular\Support\AutodiscoveryHelper; class ModulesCache extends Command { @@ -15,27 +11,11 @@ class ModulesCache extends Command protected $description = 'Create a cache file for faster module loading'; - public function handle(ModuleRegistry $registry, Filesystem $filesystem) + public function handle(AutodiscoveryHelper $helper) { $this->call(ModulesClear::class); - $export = $registry->modules() - ->map(function(ModuleConfig $module_config) { - return $module_config->toArray(); - }) - ->toArray(); - - $cache_path = $registry->getCachePath(); - $cache_contents = 'put($cache_path, $cache_contents); - - try { - require $cache_path; - } catch (Throwable $e) { - $filesystem->delete($cache_path); - throw new LogicException('Unable to cache module configuration.', 0, $e); - } + $helper->writeCache($this->getLaravel()); $this->info('Modules cached successfully!'); } diff --git a/src/Console/Commands/ModulesClear.php b/src/Console/Commands/ModulesClear.php index 08b2976..fcad824 100644 --- a/src/Console/Commands/ModulesClear.php +++ b/src/Console/Commands/ModulesClear.php @@ -3,8 +3,7 @@ namespace InterNACHI\Modular\Console\Commands; use Illuminate\Console\Command; -use Illuminate\Filesystem\Filesystem; -use InterNACHI\Modular\Support\ModuleRegistry; +use InterNACHI\Modular\Support\AutodiscoveryHelper; class ModulesClear extends Command { @@ -12,9 +11,10 @@ class ModulesClear extends Command protected $description = 'Remove the module cache file'; - public function handle(Filesystem $filesystem, ModuleRegistry $registry) + public function handle(AutodiscoveryHelper $helper) { - $filesystem->delete($registry->getCachePath()); + $helper->clearCache(); + $this->info('Module cache cleared!'); } } diff --git a/src/Support/AutodiscoveryHelper.php b/src/Support/AutodiscoveryHelper.php new file mode 100644 index 0000000..ccc9884 --- /dev/null +++ b/src/Support/AutodiscoveryHelper.php @@ -0,0 +1,328 @@ +modules(...), + $this->routes(...), + $this->views(...), + $this->blade(...), + $this->translations(...), + $this->migrations(...), + $this->commands(...), + $this->policies(...), + $this->livewire(...), + ]; + + foreach ($helpers as $helper) { + try { + $app->call($helper); + } catch (BindingResolutionException) { + } + } + + $cache = Collection::make($this->data)->toArray(); + $php = 'fs->ensureDirectoryExists($this->fs->dirname($this->cache_path)); + + if (! $this->fs->put($this->cache_path, $php)) { + throw new RuntimeException('Unable to write cache file.'); + } + + try { + require $this->cache_path; + } catch (Throwable $e) { + $this->fs->delete($this->cache_path); + throw new RuntimeException('Attempted to write invalid cache file.', $e->getCode(), $e); + } + } + + public function clearCache(): void + { + if ($this->fs->exists($this->cache_path)) { + $this->fs->delete($this->cache_path); + } + } + + /** @return Collection */ + public function modules(bool $reload = false): Collection + { + if ($reload) { + unset($this->data['modules']); + } + + $data = $this->withCache( + key: 'modules', + default: fn() => $this->finders + ->moduleComposerFileFinder() + ->values() + ->mapWithKeys(function(SplFileInfo $file) { + $composer_config = json_decode($file->getContents(), true, 16, JSON_THROW_ON_ERROR); + $base_path = rtrim(str_replace('\\', '/', $file->getPath()), '/'); + $name = basename($base_path); + + return [ + $name => [ + 'name' => $name, + 'base_path' => $base_path, + 'namespaces' => Collection::make($composer_config['autoload']['psr-4'] ?? []) + ->mapWithKeys(fn($src, $namespace) => ["{$base_path}/{$src}" => $namespace]) + ->all(), + ], + ]; + }), + ); + + return Collection::make($data) + ->map(fn(array $d) => new ModuleConfig($d['name'], $d['base_path'], new Collection($d['namespaces']))); + } + + public function routes(): void + { + $this->withCache( + key: 'route_files', + default: fn() => $this->finders + ->routeFileFinder() + ->values() + ->map(fn(SplFileInfo $file) => $file->getRealPath()), + each: fn(string $filename) => require $filename + ); + } + + public function views(ViewFactory $factory): void + { + $this->withCache( + key: 'view_namespaces', + default: fn() => $this->finders + ->viewDirectoryFinder() + ->withModuleInfo() + ->values() + ->map(fn(ModuleFileInfo $dir) => [ + 'namespace' => $dir->module()->name, + 'path' => $dir->getRealPath(), + ]), + each: fn(array $row) => $factory->addNamespace($row['namespace'], $row['path']), + ); + } + + public function blade(BladeCompiler $blade): void + { + // Handle individual Blade components (old syntax: ``) + $this->withCache( + key: 'blade_component_files', + default: fn() => $this->finders + ->bladeComponentFileFinder() + ->withModuleInfo() + ->values() + ->map(fn(ModuleFileInfo $component) => [ + 'prefix' => $component->module()->name, + 'fqcn' => $component->fullyQualifiedClassName(), + ]), + each: fn(array $row) => $blade->component($row['fqcn'], null, $row['prefix']), + ); + + // Handle Blade component namespaces (new syntax: ``) + $this->withCache( + key: 'blade_component_dirs', + default: fn() => $this->finders + ->bladeComponentDirectoryFinder() + ->withModuleInfo() + ->values() + ->map(fn(ModuleFileInfo $component) => [ + 'prefix' => $component->module()->name, + 'namespace' => $component->module()->qualify('View\\Components'), + ]), + each: fn(array $row) => $blade->componentNamespace($row['namespace'], $row['prefix']), + ); + } + + public function translations(Translator $translator): void + { + $this->withCache( + key: 'translation_files', + default: fn() => $this->finders + ->langDirectoryFinder() + ->withModuleInfo() + ->values() + ->map(fn(ModuleFileInfo $dir) => [ + 'namespace' => $dir->module()->name, + 'path' => $dir->getRealPath(), + ]), + each: function(array $row) use ($translator) { + $translator->addNamespace($row['namespace'], $row['path']); + $translator->addJsonPath($row['path']); + }, + ); + } + + public function migrations(Migrator $migrator): void + { + $this->withCache( + key: 'migration_files', + default: fn() => $this->finders + ->migrationDirectoryFinder() + ->values() + ->map(fn(SplFileInfo $file) => $file->getRealPath()), + each: fn(string $path) => $migrator->path($path), + ); + } + + public function commands(Artisan $artisan): void + { + $this->withCache( + key: 'command_files', + default: fn() => $this->finders + ->commandFileFinder() + ->withModuleInfo() + ->values() + ->map(fn(ModuleFileInfo $file) => $file->fullyQualifiedClassName()) + ->filter($this->isInstantiableCommand(...)), + each: fn(string $fqcn) => $artisan->resolve($fqcn), + ); + } + + public function policies(Gate $gate): void + { + $this->withCache( + key: 'model_policy_files', + default: fn() => $this->finders + ->modelFileFinder() + ->withModuleInfo() + ->values() + ->map(function(ModuleFileInfo $file) use ($gate) { + $fqcn = $file->fullyQualifiedClassName(); + $namespace = rtrim($file->module()->namespaces->first(), '\\'); + + $candidates = [ + $namespace.'\\Policies\\'.Str::after($fqcn, 'Models\\').'Policy', // Policies/Foo/BarPolicy + $namespace.'\\Policies\\'.Str::afterLast($fqcn, '\\').'Policy', // Policies/BarPolicy + ]; + + foreach ($candidates as $candidate) { + if (class_exists($candidate)) { + return [ + 'fqcn' => $fqcn, + 'policy' => $candidate, + ]; + } + } + + return null; + }) + ->filter(), + each: fn(array $row) => $gate->policy($row['fqcn'], $row['policy']), + ); + } + + public function events(Dispatcher $events, bool $autodiscover = true): void + { + $this->withCache( + key: 'events', + default: fn() => $autodiscover + ? $this->finders + ->listenerDirectoryFinder() + ->withModuleInfo() + ->reduce(function(array $discovered, ModuleFileInfo $file) { + return array_merge_recursive( + $discovered, + DiscoverEvents::within($file->getPathname(), $file->module()->path('src')) + ); + }, []) + : [], + each: function(array $listeners, string $event) use ($events) { + foreach (array_unique($listeners, SORT_REGULAR) as $listener) { + $events->listen($event, $listener); + } + }, + ); + } + + public function livewire(LivewireManager $livewire): void + { + $this->withCache( + key: 'livewire_component_files', + default: fn() => $this->finders + ->livewireComponentFileFinder() + ->withModuleInfo() + ->values() + ->map(fn(ModuleFileInfo $file) => [ + 'name' => sprintf( + '%s::%s', + $file->module()->name, + Str::of($file->getRelativePath()) + ->explode('/') + ->filter() + ->push($file->getBasename('.php')) + ->map([Str::class, 'kebab']) + ->implode('.') + ), + 'fqcn' => $file->fullyQualifiedClassName(), + ]), + each: fn(array $row) => $livewire->component($row['name'], $row['fqcn']), + ); + } + + protected function withCache( + string $key, + Closure $default, + ?Closure $each = null, + ): iterable { + $this->data ??= $this->readData(); + $this->data[$key] ??= value($default); + + return $each + ? Collection::make($this->data[$key])->each($each) + : $this->data[$key]; + } + + protected function readData(): array + { + try { + return $this->fs->exists($this->cache_path) + ? require $this->cache_path + : []; + } catch (Throwable) { + return []; + } + } + + protected function isInstantiableCommand($command): bool + { + return is_subclass_of($command, Command::class) + && ! (new ReflectionClass($command))->isAbstract(); + } +} diff --git a/src/Support/FinderCollection.php b/src/Support/FinderCollection.php index a1db3c9..6e9a711 100644 --- a/src/Support/FinderCollection.php +++ b/src/Support/FinderCollection.php @@ -2,20 +2,24 @@ namespace InterNACHI\Modular\Support; +use Illuminate\Contracts\Support\Arrayable; use Illuminate\Support\LazyCollection; use Illuminate\Support\Traits\ForwardsCalls; +use IteratorAggregate; use Symfony\Component\Finder\Exception\DirectoryNotFoundException; use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; +use Traversable; /** * @mixin \Illuminate\Support\LazyCollection * @mixin \Symfony\Component\Finder\Finder */ -class FinderCollection +class FinderCollection implements Arrayable, IteratorAggregate { use ForwardsCalls; - protected const PREFER_COLLECTION_METHODS = ['filter', 'each', 'map']; + protected const array PREFER_COLLECTION_METHODS = ['filter', 'each', 'map']; public static function forFiles(): self { @@ -45,6 +49,21 @@ public function inOrEmpty(string|array $dirs): static } } + public function withModuleInfo(): static + { + return $this->map(fn(SplFileInfo $file) => new ModuleFileInfo($file)); + } + + public function getIterator(): Traversable + { + return $this->forwardCollection()->getIterator(); + } + + public function toArray(): array + { + return $this->forwardCollection()->toArray(); + } + public function __call($name, $arguments) { $result = $this->forwardCallTo($this->forwardCallTargetForMethod($name), $name, $arguments); diff --git a/src/Support/AutoDiscoveryHelper.php b/src/Support/FinderFactory.php similarity index 90% rename from src/Support/AutoDiscoveryHelper.php rename to src/Support/FinderFactory.php index a59996e..eba39ef 100644 --- a/src/Support/AutoDiscoveryHelper.php +++ b/src/Support/FinderFactory.php @@ -2,17 +2,19 @@ namespace InterNACHI\Modular\Support; -use Illuminate\Filesystem\Filesystem; - -class AutoDiscoveryHelper +class FinderFactory { - protected string $base_path; - public function __construct( - protected ModuleRegistry $module_registry, - protected Filesystem $filesystem + protected string $base_path ) { - $this->base_path = $module_registry->getModulesPath(); + } + + public function moduleComposerFileFinder(): FinderCollection + { + return FinderCollection::forFiles() + ->depth('== 1') + ->name('composer.json') + ->inOrEmpty($this->base_path); } public function commandFileFinder(): FinderCollection @@ -94,13 +96,13 @@ public function listenerDirectoryFinder(): FinderCollection public function livewireComponentFileFinder(): FinderCollection { $directory = $this->base_path.'/*/src'; - + if (str_contains(config('livewire.class_namespace'), '\\Http\\')) { $directory .= '/Http'; } - + $directory .= '/Livewire'; - + return FinderCollection::forFiles() ->name('*.php') ->inOrEmpty($directory); diff --git a/src/Support/ModularEventServiceProvider.php b/src/Support/ModularEventServiceProvider.php deleted file mode 100644 index e547af7..0000000 --- a/src/Support/ModularEventServiceProvider.php +++ /dev/null @@ -1,74 +0,0 @@ -app->booting(function() { - $events = $this->getEvents(); - $provider = Arr::first($this->app->getProviders(EventServiceProvider::class)); - - if (! $provider || empty($events)) { - return; - } - - $listen = new ReflectionProperty($provider, 'listen'); - $listen->setAccessible(true); - $listen->setValue($provider, array_merge_recursive($listen->getValue($provider), $events)); - }); - } - - public function getEvents(): array - { - // If events are cached, or Modular event discovery is disabled, then we'll - // just let the normal event service provider handle all the event loading. - if ($this->app->eventsAreCached() || ! $this->shouldDiscoverEvents()) { - return []; - } - - return $this->discoverEvents(); - } - - public function shouldDiscoverEvents(): bool - { - return config('app-modules.should_discover_events') - ?? $this->appIsConfiguredToDiscoverEvents(); - } - - public function discoverEvents() - { - $modules = $this->app->make(ModuleRegistry::class); - - return $this->app->make(AutoDiscoveryHelper::class) - ->listenerDirectoryFinder() - ->map(fn(SplFileInfo $directory) => $directory->getPathname()) - ->reduce(function($discovered, string $directory) use ($modules) { - $module = $modules->moduleForPath($directory); - return array_merge_recursive( - $discovered, - DiscoverEvents::within($directory, $module->path('src')) - ); - }, []); - } - - public function appIsConfiguredToDiscoverEvents(): bool - { - return collect($this->app->getProviders(EventServiceProvider::class)) - ->filter(fn(EventServiceProvider $provider) => $provider::class === EventServiceProvider::class - || str_starts_with(get_class($provider), $this->app->getNamespace())) - ->contains(fn(EventServiceProvider $provider) => $provider->shouldDiscoverEvents()); - } -} diff --git a/src/Support/ModularServiceProvider.php b/src/Support/ModularServiceProvider.php index 22120ca..34341ab 100644 --- a/src/Support/ModularServiceProvider.php +++ b/src/Support/ModularServiceProvider.php @@ -2,14 +2,16 @@ namespace InterNACHI\Modular\Support; -use Closure; use Illuminate\Console\Application as Artisan; -use Illuminate\Console\Command; use Illuminate\Contracts\Auth\Access\Gate; +use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Translation\Translator as TranslatorContract; use Illuminate\Database\Console\Migrations\MigrateMakeCommand; use Illuminate\Database\Eloquent\Factories\Factory as EloquentFactory; use Illuminate\Database\Migrations\Migrator; +use Illuminate\Filesystem\Filesystem; +use Illuminate\Foundation\Support\Providers\EventServiceProvider; use Illuminate\Support\Facades\Config; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; @@ -22,15 +24,13 @@ use InterNACHI\Modular\Console\Commands\ModulesClear; use InterNACHI\Modular\Console\Commands\ModulesList; use InterNACHI\Modular\Console\Commands\ModulesSync; -use Livewire\Livewire; -use ReflectionClass; -use Symfony\Component\Finder\SplFileInfo; +use Livewire\LivewireManager; class ModularServiceProvider extends ServiceProvider { protected ?ModuleRegistry $registry = null; - protected ?AutoDiscoveryHelper $auto_discovery_helper = null; + protected ?AutodiscoveryHelper $autodiscovery_helper = null; protected string $base_dir; @@ -45,31 +45,40 @@ public function __construct($app) public function register(): void { - $this->mergeConfigFrom("{$this->base_dir}/config.php", 'app-modules'); + $this->mergeConfigFrom("{$this->base_dir}/config/app-modules.php", 'app-modules'); - $this->app->singleton(ModuleRegistry::class, function() { + $this->app->singleton(ModuleRegistry::class, function(Application $app) { return new ModuleRegistry( $this->getModulesBasePath(), - $this->app->bootstrapPath('cache/modules.php') + $app->make(AutodiscoveryHelper::class), ); }); - $this->app->singleton(AutoDiscoveryHelper::class); + $this->app->singleton(FinderFactory::class, function() { + return new FinderFactory($this->getModulesBasePath()); + }); - $this->app->singleton(MakeMigration::class, function($app) { + $this->app->singleton(AutodiscoveryHelper::class, function(Application $app) { + return new AutodiscoveryHelper( + $app->make(FinderFactory::class), + $app->make(Filesystem::class), + $this->app->bootstrapPath('cache/app-modules.php') + ); + }); + + $this->app->singleton(MakeMigration::class, function(Application $app) { return new MigrateMakeCommand($app['migration.creator'], $app['composer']); }); $this->registerEloquentFactories(); - // Set up lazy registrations for things that only need to run if we're using - // that functionality (e.g. we only need to look for and register migrations - // if we're running the migrator) - $this->registerLazily(Migrator::class, [$this, 'registerMigrations']); - $this->registerLazily(Gate::class, [$this, 'registerPolicies']); + $this->app->resolving(Migrator::class, fn(Migrator $migrator) => $this->autodiscover()->migrations($migrator)); + $this->app->resolving(Gate::class, fn(Gate $gate) => $this->autodiscover()->policies($gate)); - // Look for and register all our commands in the CLI context - Artisan::starting(Closure::fromCallable([$this, 'onArtisanStart'])); + Artisan::starting(function(Artisan $artisan) { + $this->autodiscover()->commands($artisan); + $this->registerNamespacesInTinker(); + }); } public function boot(): void @@ -78,10 +87,10 @@ public function boot(): void $this->bootPackageCommands(); $this->bootRoutes(); - $this->bootBreadcrumbs(); $this->bootViews(); $this->bootBladeComponents(); $this->bootTranslations(); + $this->bootEvents(); $this->bootLivewireComponents(); } @@ -90,153 +99,73 @@ protected function registry(): ModuleRegistry return $this->registry ??= $this->app->make(ModuleRegistry::class); } - protected function autoDiscoveryHelper(): AutoDiscoveryHelper + protected function autodiscover(): AutodiscoveryHelper { - return $this->auto_discovery_helper ??= $this->app->make(AutoDiscoveryHelper::class); + return $this->autodiscovery_helper ??= $this->app->make(AutodiscoveryHelper::class); } protected function publishVendorFiles(): void { $this->publishes([ - "{$this->base_dir}/config.php" => $this->app->configPath('app-modules.php'), + "{$this->base_dir}/config/app-modules.php" => $this->app->configPath('app-modules.php'), ], 'modular-config'); } protected function bootPackageCommands(): void { - if (! $this->app->runningInConsole()) { - return; + if ($this->app->runningInConsole()) { + $this->commands([ + MakeModule::class, + ModulesCache::class, + ModulesClear::class, + ModulesSync::class, + ModulesList::class, + ]); } - - $this->commands([ - MakeModule::class, - ModulesCache::class, - ModulesClear::class, - ModulesSync::class, - ModulesList::class, - ]); } protected function bootRoutes(): void { - if ($this->app->routesAreCached()) { - return; + if (! $this->app->routesAreCached()) { + $this->autodiscover()->routes(); } - - $this->autoDiscoveryHelper() - ->routeFileFinder() - ->each(function(SplFileInfo $file) { - require $file->getRealPath(); - }); } protected function bootViews(): void { - $this->callAfterResolving('view', function(ViewFactory $view_factory) { - $this->autoDiscoveryHelper() - ->viewDirectoryFinder() - ->each(function(SplFileInfo $directory) use ($view_factory) { - $module = $this->registry()->moduleForPathOrFail($directory->getPath()); - $view_factory->addNamespace($module->name, $directory->getRealPath()); - }); + $this->callAfterResolving('view', function(ViewFactory $factory) { + $this->autodiscover()->views($factory); }); } protected function bootBladeComponents(): void { $this->callAfterResolving(BladeCompiler::class, function(BladeCompiler $blade) { - // Boot individual Blade components (old syntax: ``) - $this->autoDiscoveryHelper() - ->bladeComponentFileFinder() - ->each(function(SplFileInfo $component) use ($blade) { - $module = $this->registry()->moduleForPathOrFail($component->getPath()); - $fully_qualified_component = $module->pathToFullyQualifiedClassName($component->getPathname()); - $blade->component($fully_qualified_component, null, $module->name); - }); - - // Boot Blade component namespaces (new syntax: ``) - $this->autoDiscoveryHelper() - ->bladeComponentDirectoryFinder() - ->each(function(SplFileInfo $component) use ($blade) { - $module = $this->registry()->moduleForPathOrFail($component->getPath()); - $blade->componentNamespace($module->qualify('View\\Components'), $module->name); - }); + $this->autodiscover()->blade($blade); }); } protected function bootTranslations(): void { $this->callAfterResolving('translator', function(TranslatorContract $translator) { - if (! $translator instanceof Translator) { - return; + if ($translator instanceof Translator) { + $this->autodiscover()->translations($translator); } - - $this->autoDiscoveryHelper() - ->langDirectoryFinder() - ->each(function(SplFileInfo $directory) use ($translator) { - $module = $this->registry()->moduleForPathOrFail($directory->getPath()); - $path = $directory->getRealPath(); - - $translator->addNamespace($module->name, $path); - $translator->addJsonPath($path); - }); }); } - /** - * This functionality is likely to go away at some point so don't rely - * on it too much. The package has been abandoned. - */ - protected function bootBreadcrumbs(): void + protected function bootEvents(): void { - $class_name = 'Diglactic\\Breadcrumbs\\Manager'; - - if (! class_exists($class_name)) { - return; - } - - // The breadcrumbs package makes $breadcrumbs available in the scope of breadcrumb - // files, so we'll do the same for consistency-sake - $breadcrumbs = $this->app->make($class_name); - - $files = glob($this->getModulesBasePath().'/*/routes/breadcrumbs/*.php'); - - foreach ($files as $file) { - require_once $file; - } + $this->callAfterResolving(Dispatcher::class, function(Dispatcher $events) { + $this->autodiscover()->events($events, $this->shouldDiscoverEvents()); + }); } protected function bootLivewireComponents(): void { - if (! class_exists(Livewire::class)) { - return; + if (class_exists(LivewireManager::class)) { + $this->autodiscover()->livewire($this->app->make(LivewireManager::class)); } - - $this->autoDiscoveryHelper() - ->livewireComponentFileFinder() - ->each(function(SplFileInfo $component) { - $module = $this->registry()->moduleForPathOrFail($component->getPath()); - - $component_name = Str::of($component->getRelativePath()) - ->explode('/') - ->filter() - ->push($component->getBasename('.php')) - ->map([Str::class, 'kebab']) - ->implode('.'); - - $fully_qualified_component = $module->pathToFullyQualifiedClassName($component->getPathname()); - - Livewire::component("{$module->name}::{$component_name}", $fully_qualified_component); - }); - } - - protected function registerMigrations(Migrator $migrator): void - { - $this->autoDiscoveryHelper() - ->migrationDirectoryFinder() - ->each(function(SplFileInfo $path) use ($migrator) { - $migrator->path($path->getRealPath()); - }); } protected function registerEloquentFactories(): void @@ -247,61 +176,13 @@ protected function registerEloquentFactories(): void EloquentFactory::guessFactoryNamesUsing($helper->factoryNameResolver()); } - protected function registerPolicies(Gate $gate): void - { - $this->autoDiscoveryHelper() - ->modelFileFinder() - ->each(function(SplFileInfo $file) use ($gate) { - $module = $this->registry()->moduleForPathOrFail($file->getPath()); - $fully_qualified_model = $module->pathToFullyQualifiedClassName($file->getPathname()); - - // First, check for a policy that maps to the full namespace of the model - // i.e. Models/Foo/Bar -> Policies/Foo/BarPolicy - $namespaced_model = Str::after($fully_qualified_model, 'Models\\'); - $namespaced_policy = rtrim($module->namespaces->first(), '\\').'\\Policies\\'.$namespaced_model.'Policy'; - if (class_exists($namespaced_policy)) { - $gate->policy($fully_qualified_model, $namespaced_policy); - } - - // If that doesn't match, try the simple mapping as well - // i.e. Models/Foo/Bar -> Policies/BarPolicy - if (false !== strpos($namespaced_model, '\\')) { - $simple_model = Str::afterLast($fully_qualified_model, '\\'); - $simple_policy = rtrim($module->namespaces->first(), '\\').'\\Policies\\'.$simple_model.'Policy'; - - if (class_exists($simple_policy)) { - $gate->policy($fully_qualified_model, $simple_policy); - } - } - }); - } - - protected function onArtisanStart(Artisan $artisan): void - { - $this->registerCommands($artisan); - $this->registerNamespacesInTinker(); - } - - protected function registerCommands(Artisan $artisan): void - { - $this->autoDiscoveryHelper() - ->commandFileFinder() - ->each(function(SplFileInfo $file) use ($artisan) { - $module = $this->registry()->moduleForPathOrFail($file->getPath()); - $class_name = $module->pathToFullyQualifiedClassName($file->getPathname()); - if ($this->isInstantiableCommand($class_name)) { - $artisan->resolve($class_name); - } - }); - } - - protected function registerNamespacesInTinker() + protected function registerNamespacesInTinker(): void { if (! class_exists('Laravel\\Tinker\\TinkerServiceProvider')) { return; } - $namespaces = app(ModuleRegistry::class) + $namespaces = $this->registry() ->modules() ->flatMap(fn(ModuleConfig $config) => $config->namespaces) ->reject(fn($ns) => Str::endsWith($ns, ['Tests\\', 'Database\\Factories\\', 'Database\\Seeders\\'])) @@ -311,13 +192,6 @@ protected function registerNamespacesInTinker() Config::set('tinker.alias', array_merge($namespaces, Config::get('tinker.alias', []))); } - protected function registerLazily(string $class_name, callable $callback): self - { - $this->app->resolving($class_name, Closure::fromCallable($callback)); - - return $this; - } - protected function getModulesBasePath(): string { if (null === $this->modules_path) { @@ -328,9 +202,17 @@ protected function getModulesBasePath(): string return $this->modules_path; } - protected function isInstantiableCommand($command): bool + protected function shouldDiscoverEvents(): bool + { + return $this->app->make('config') + ->get('app-modules.should_discover_events') ?? $this->appIsConfiguredToDiscoverEvents(); + } + + protected function appIsConfiguredToDiscoverEvents(): bool { - return is_subclass_of($command, Command::class) - && ! (new ReflectionClass($command))->isAbstract(); + return collect($this->app->getProviders(EventServiceProvider::class)) + ->filter(fn(EventServiceProvider $provider) => $provider::class === EventServiceProvider::class + || str_starts_with(get_class($provider), $this->app->getNamespace())) + ->contains(fn(EventServiceProvider $provider) => $provider->shouldDiscoverEvents()); } } diff --git a/src/Support/ModuleConfig.php b/src/Support/ModuleConfig.php index b7eb46c..59807be 100644 --- a/src/Support/ModuleConfig.php +++ b/src/Support/ModuleConfig.php @@ -10,8 +10,6 @@ class ModuleConfig implements Arrayable { - public Collection $namespaces; - public static function fromComposerFile(SplFileInfo $composer_file): self { $composer_config = json_decode($composer_file->getContents(), true, 16, JSON_THROW_ON_ERROR); @@ -32,9 +30,8 @@ public static function fromComposerFile(SplFileInfo $composer_file): self public function __construct( public string $name, public string $base_path, - ?Collection $namespaces = null + public Collection $namespaces = new Collection(), ) { - $this->namespaces = $namespaces ?? new Collection(); } public function path(string $to = ''): string diff --git a/src/Support/ModuleFileInfo.php b/src/Support/ModuleFileInfo.php new file mode 100644 index 0000000..a296d4c --- /dev/null +++ b/src/Support/ModuleFileInfo.php @@ -0,0 +1,37 @@ +module()->pathToFullyQualifiedClassName($this->getPathname()); + } + + public function module(): ModuleConfig + { + return $this->module ??= Container::getInstance() + ->make(ModuleRegistry::class) + ->moduleForPathOrFail($this->getPath()); + } + + public function __call(string $name, array $arguments) + { + return $this->forwardDecoratedCallTo($this->file, $name, $arguments); + } +} diff --git a/src/Support/ModuleRegistry.php b/src/Support/ModuleRegistry.php index 82c09c9..528177d 100644 --- a/src/Support/ModuleRegistry.php +++ b/src/Support/ModuleRegistry.php @@ -5,7 +5,6 @@ use Illuminate\Support\Collection; use Illuminate\Support\Str; use InterNACHI\Modular\Exceptions\CannotFindModuleForPathException; -use Symfony\Component\Finder\SplFileInfo; class ModuleRegistry { @@ -13,7 +12,7 @@ class ModuleRegistry public function __construct( protected string $modules_path, - protected string $cache_path + protected AutodiscoveryHelper $autodiscovery_helper, ) { } @@ -22,11 +21,6 @@ public function getModulesPath(): string return $this->modules_path; } - public function getCachePath(): string - { - return $this->cache_path; - } - public function module(?string $name = null): ?ModuleConfig { // We want to allow for gracefully handling empty/null names @@ -64,39 +58,14 @@ public function moduleForClass(string $fqcn): ?ModuleConfig public function modules(): Collection { - return $this->modules ??= $this->loadModules(); + return $this->modules ??= $this->autodiscovery_helper->modules(); } public function reload(): Collection { $this->modules = null; - return $this->loadModules(); - } - - protected function loadModules(): Collection - { - if (file_exists($this->cache_path)) { - return Collection::make(require $this->cache_path) - ->mapWithKeys(function(array $cached) { - $config = new ModuleConfig($cached['name'], $cached['base_path'], new Collection($cached['namespaces'])); - return [$config->name => $config]; - }); - } - - if (! is_dir($this->modules_path)) { - return new Collection(); - } - - return FinderCollection::forFiles() - ->depth('== 1') - ->name('composer.json') - ->in($this->modules_path) - ->collect() - ->mapWithKeys(function(SplFileInfo $path) { - $config = ModuleConfig::fromComposerFile($path); - return [$config->name => $config]; - }); + return $this->modules ??= $this->autodiscovery_helper->modules(reload: true); } protected function extractModuleNameFromPath(string $path): string diff --git a/src/Support/PhpStorm/ConfigWriter.php b/src/Support/PhpStorm/ConfigWriter.php index a0b9732..cb80180 100644 --- a/src/Support/PhpStorm/ConfigWriter.php +++ b/src/Support/PhpStorm/ConfigWriter.php @@ -23,14 +23,14 @@ abstract class ConfigWriter */ protected $module_registry; - abstract public function write(): bool; - public function __construct($config_path, ModuleRegistry $module_registry) { $this->config_path = $config_path; $this->module_registry = $module_registry; } + abstract public function write(): bool; + public function handle(): bool { if (! $this->checkConfigFilePermissions()) { diff --git a/tests/AutoDiscoveryHelperTest.php b/tests/AutoDiscoveryHelperTest.php index 4f4a55b..8d3865f 100644 --- a/tests/AutoDiscoveryHelperTest.php +++ b/tests/AutoDiscoveryHelperTest.php @@ -8,8 +8,7 @@ use InterNACHI\Modular\Console\Commands\Make\MakeListener; use InterNACHI\Modular\Console\Commands\Make\MakeLivewire; use InterNACHI\Modular\Console\Commands\Make\MakeModel; -use InterNACHI\Modular\Support\AutoDiscoveryHelper; -use InterNACHI\Modular\Support\ModuleRegistry; +use InterNACHI\Modular\Support\FinderFactory; use InterNACHI\Modular\Tests\Concerns\WritesToAppFilesystem; use Livewire\Livewire; use Livewire\LivewireServiceProvider; @@ -32,10 +31,7 @@ protected function setUp(): void $this->module1 = $this->makeModule('test-module'); $this->module2 = $this->makeModule('test-module-two'); - $this->helper = new AutoDiscoveryHelper( - new ModuleRegistry($this->getApplicationBasePath().'/app-modules', ''), - new Filesystem() - ); + $this->helper = new FinderFactory($this->getApplicationBasePath().'/app-modules'); } public function test_it_finds_commands(): void diff --git a/tests/Commands/ModulesCacheTest.php b/tests/Commands/ModulesCacheTest.php index 87cd1a0..c817683 100644 --- a/tests/Commands/ModulesCacheTest.php +++ b/tests/Commands/ModulesCacheTest.php @@ -12,18 +12,24 @@ class ModulesCacheTest extends TestCase public function test_it_writes_to_cache_file(): void { - $this->makeModule('test-module'); - $this->makeModule('test-module-two'); + $expected_path = $this->getApplicationBasePath().$this->normalizeDirectorySeparators('bootstrap/cache/app-modules.php'); - $this->artisan(ModulesCache::class); - - $expected_path = $this->getApplicationBasePath().$this->normalizeDirectorySeparators('bootstrap/cache/modules.php'); - - $this->assertFileExists($expected_path); - - $cache = include $expected_path; - - $this->assertArrayHasKey('test-module', $cache); - $this->assertArrayHasKey('test-module-two', $cache); + try { + $this->makeModule('test-module'); + $this->makeModule('test-module-two'); + + $this->artisan(ModulesCache::class); + + $this->assertFileExists($expected_path); + + $cache = include $expected_path; + + $this->assertArrayHasKey('test-module', $cache['modules']); + $this->assertArrayHasKey('test-module-two', $cache['modules']); + } finally { + if (file_exists($expected_path)) { + unlink($expected_path); + } + } } } diff --git a/tests/Commands/ModulesClearTest.php b/tests/Commands/ModulesClearTest.php index d89d6a5..21c1a87 100644 --- a/tests/Commands/ModulesClearTest.php +++ b/tests/Commands/ModulesClearTest.php @@ -15,7 +15,7 @@ public function test_it_writes_to_cache_file(): void { $this->artisan(ModulesCache::class); - $expected_path = $this->getApplicationBasePath().$this->normalizeDirectorySeparators('bootstrap/cache/modules.php'); + $expected_path = $this->getApplicationBasePath().$this->normalizeDirectorySeparators('bootstrap/cache/app-modules.php'); $this->assertFileExists($expected_path); diff --git a/tests/EventDiscovery/EventDiscoveryExplicitlyEnabledTest.php b/tests/EventDiscovery/EventDiscoveryExplicitlyEnabledTest.php index 68793d7..8a3ab00 100644 --- a/tests/EventDiscovery/EventDiscoveryExplicitlyEnabledTest.php +++ b/tests/EventDiscovery/EventDiscoveryExplicitlyEnabledTest.php @@ -6,6 +6,8 @@ namespace InterNACHI\Modular\Tests\EventDiscovery { use App\EventDiscoveryExplicitlyEnabledTestProvider; use Illuminate\Support\Facades\Event; + use InterNACHI\Modular\Console\Commands\ModulesCache; + use InterNACHI\Modular\Console\Commands\ModulesClear; use InterNACHI\Modular\Support\Facades\Modules; use InterNACHI\Modular\Tests\Concerns\PreloadsAppModules; use InterNACHI\Modular\Tests\TestCase; @@ -18,7 +20,7 @@ protected function setUp(): void { parent::setUp(); - $this->beforeApplicationDestroyed(fn() => $this->artisan('event:clear')); + $this->beforeApplicationDestroyed(fn() => $this->artisan(ModulesClear::class)); } public function test_it_auto_discovers_event_listeners(): void @@ -29,18 +31,16 @@ public function test_it_auto_discovers_event_listeners(): void // Also check that the events are cached correctly - $this->artisan('event:cache'); + $this->artisan(ModulesCache::class); - $cache = require $this->app->getCachedEventsPath(); + $cache = require $this->app->bootstrapPath('cache/app-modules.php'); - $this->assertArrayHasKey($module->qualify('Events\\TestEvent'), $cache[EventDiscoveryExplicitlyEnabledTestProvider::class]); + $this->assertArrayHasKey($module->qualify('Events\\TestEvent'), $cache['events']); $this->assertContains( $module->qualify('Listeners\\TestEventListener@handle'), - $cache[EventDiscoveryExplicitlyEnabledTestProvider::class][$module->qualify('Events\\TestEvent')] + $cache['events'][$module->qualify('Events\\TestEvent')] ); - - $this->artisan('event:clear'); } protected function getPackageProviders($app) diff --git a/tests/EventDiscovery/EventDiscoveryImplicitlyEnabledTest.php b/tests/EventDiscovery/EventDiscoveryImplicitlyEnabledTest.php index 966480c..6ad5514 100644 --- a/tests/EventDiscovery/EventDiscoveryImplicitlyEnabledTest.php +++ b/tests/EventDiscovery/EventDiscoveryImplicitlyEnabledTest.php @@ -6,6 +6,8 @@ namespace InterNACHI\Modular\Tests\EventDiscovery { use App\EventDiscoveryImplicitlyEnabledTestProvider; use Illuminate\Support\Facades\Event; + use InterNACHI\Modular\Console\Commands\ModulesCache; + use InterNACHI\Modular\Console\Commands\ModulesClear; use InterNACHI\Modular\Support\Facades\Modules; use InterNACHI\Modular\Tests\Concerns\PreloadsAppModules; use InterNACHI\Modular\Tests\TestCase; @@ -18,7 +20,7 @@ protected function setUp(): void { parent::setUp(); - $this->beforeApplicationDestroyed(fn() => $this->artisan('event:clear')); + $this->beforeApplicationDestroyed(fn() => $this->artisan(ModulesClear::class)); } public function test_it_auto_discovers_event_listeners(): void @@ -29,18 +31,16 @@ public function test_it_auto_discovers_event_listeners(): void // Also check that the events are cached correctly - $this->artisan('event:cache'); + $this->artisan(ModulesCache::class); - $cache = require $this->app->getCachedEventsPath(); + $cache = require $this->app->bootstrapPath('cache/app-modules.php'); - $this->assertArrayHasKey($module->qualify('Events\\TestEvent'), $cache[EventDiscoveryImplicitlyEnabledTestProvider::class]); + $this->assertArrayHasKey($module->qualify('Events\\TestEvent'), $cache['events']); $this->assertContains( $module->qualify('Listeners\\TestEventListener@handle'), - $cache[EventDiscoveryImplicitlyEnabledTestProvider::class][$module->qualify('Events\\TestEvent')] + $cache['events'][$module->qualify('Events\\TestEvent')] ); - - $this->artisan('event:clear'); } protected function getPackageProviders($app) diff --git a/tests/EventDiscovery/Laravel11EventDiscoveryImplicitlyEnabledTest.php b/tests/EventDiscovery/Laravel11EventDiscoveryImplicitlyEnabledTest.php index ea87b19..f5d83c1 100644 --- a/tests/EventDiscovery/Laravel11EventDiscoveryImplicitlyEnabledTest.php +++ b/tests/EventDiscovery/Laravel11EventDiscoveryImplicitlyEnabledTest.php @@ -4,6 +4,8 @@ use Illuminate\Foundation\Support\Providers\EventServiceProvider; use Illuminate\Support\Facades\Event; +use InterNACHI\Modular\Console\Commands\ModulesCache; +use InterNACHI\Modular\Console\Commands\ModulesClear; use InterNACHI\Modular\Support\Facades\Modules; use InterNACHI\Modular\Tests\Concerns\PreloadsAppModules; use InterNACHI\Modular\Tests\TestCase; @@ -16,7 +18,7 @@ protected function setUp(): void { parent::setUp(); - $this->beforeApplicationDestroyed(fn() => $this->artisan('event:clear')); + $this->beforeApplicationDestroyed(fn() => $this->artisan(ModulesClear::class)); $this->requiresLaravelVersion('11.0.0'); } @@ -28,16 +30,15 @@ public function test_it_auto_discovers_event_listeners(): void // Also check that the events are cached correctly - $this->artisan('event:cache'); + $this->artisan(ModulesCache::class); - $cache = require $this->app->getCachedEventsPath(); - $cached_listeners = collect($cache)->reduce(fn(array $listeners, $row) => array_merge_recursive($listeners, $row), []); + $cache = require $this->app->bootstrapPath('cache/app-modules.php'); - $this->assertArrayHasKey($module->qualify('Events\\TestEvent'), $cached_listeners); + $this->assertArrayHasKey($module->qualify('Events\\TestEvent'), $cache['events']); $this->assertContains( $module->qualify('Listeners\\TestEventListener').'@handle', - $cached_listeners[$module->qualify('Events\\TestEvent')] + $cache['events'][$module->qualify('Events\\TestEvent')] ); } diff --git a/tests/TestCase.php b/tests/TestCase.php index be96324..bfec4dc 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,7 +6,6 @@ use InterNACHI\Modular\Console\Commands\Make\MakeModule; use InterNACHI\Modular\Support\DatabaseFactoryHelper; use InterNACHI\Modular\Support\Facades\Modules; -use InterNACHI\Modular\Support\ModularEventServiceProvider; use InterNACHI\Modular\Support\ModularizedCommandsServiceProvider; use InterNACHI\Modular\Support\ModularServiceProvider; use InterNACHI\Modular\Support\ModuleConfig; @@ -60,7 +59,6 @@ protected function getPackageProviders($app) return [ ModularServiceProvider::class, ModularizedCommandsServiceProvider::class, - ModularEventServiceProvider::class, ]; }