diff --git a/src/Console/Commands/Make/MakeModule.php b/src/Console/Commands/Make/MakeModule.php
index 997f4d6..ab3338a 100644
--- a/src/Console/Commands/Make/MakeModule.php
+++ b/src/Console/Commands/Make/MakeModule.php
@@ -110,7 +110,7 @@ public function handle()
$this->line("Please run composer update {$this->composer_name}");
$this->newLine();
- $this->module_registry->reload();
+ $this->getLaravel()->make(ModuleRegistry::class)->reload();
return 0;
}
diff --git a/src/Console/Commands/ModulesCache.php b/src/Console/Commands/ModulesCache.php
index 47a93ab..0fbca74 100644
--- a/src/Console/Commands/ModulesCache.php
+++ b/src/Console/Commands/ModulesCache.php
@@ -15,7 +15,7 @@ public function handle(AutodiscoveryHelper $helper)
{
$this->call(ModulesClear::class);
- $helper->writeCache($this->getLaravel());
+ $helper->writeCache();
$this->info('Modules cached successfully!');
}
diff --git a/src/Support/Autodiscovery/ArtisanPlugin.php b/src/Support/Autodiscovery/ArtisanPlugin.php
new file mode 100644
index 0000000..84dcaf6
--- /dev/null
+++ b/src/Support/Autodiscovery/ArtisanPlugin.php
@@ -0,0 +1,62 @@
+commandFileFinder()
+ ->withModuleInfo()
+ ->values()
+ ->map(fn(ModuleFileInfo $file) => $file->fullyQualifiedClassName())
+ ->filter($this->isInstantiableCommand(...));
+ }
+
+ public function handle(Collection $data): void
+ {
+ $data->each(fn(string $fqcn) => $this->artisan->resolve($fqcn));
+
+ $this->registerNamespacesInTinker();
+ }
+
+ protected function registerNamespacesInTinker(): void
+ {
+ if (! class_exists('Laravel\\Tinker\\TinkerServiceProvider')) {
+ return;
+ }
+
+ $namespaces = $this->registry
+ ->modules()
+ ->flatMap(fn(ModuleConfig $config) => $config->namespaces)
+ ->reject(fn($ns) => Str::endsWith($ns, ['Tests\\', 'Database\\Factories\\', 'Database\\Seeders\\']))
+ ->values()
+ ->all();
+
+ Config::set('tinker.alias', array_merge($namespaces, Config::get('tinker.alias', [])));
+ }
+
+ protected function isInstantiableCommand($command): bool
+ {
+ return is_subclass_of($command, Command::class)
+ && ! (new ReflectionClass($command))->isAbstract();
+ }
+}
diff --git a/src/Support/Autodiscovery/Attributes/AfterResolving.php b/src/Support/Autodiscovery/Attributes/AfterResolving.php
new file mode 100644
index 0000000..3b22306
--- /dev/null
+++ b/src/Support/Autodiscovery/Attributes/AfterResolving.php
@@ -0,0 +1,14 @@
+bladeComponentFileFinder()
+ ->withModuleInfo()
+ ->values()
+ ->map(fn(ModuleFileInfo $component) => [
+ 'prefix' => $component->module()->name,
+ 'fqcn' => $component->fullyQualifiedClassName(),
+ ]);
+ }
+
+ public function handle(Collection $data)
+ {
+ $data->each(fn(array $row) => $this->blade->component($row['fqcn'], null, $row['prefix']));
+ }
+}
diff --git a/src/Support/Autodiscovery/EventsPlugin.php b/src/Support/Autodiscovery/EventsPlugin.php
new file mode 100644
index 0000000..312898b
--- /dev/null
+++ b/src/Support/Autodiscovery/EventsPlugin.php
@@ -0,0 +1,61 @@
+shouldDiscoverEvents()) {
+ return [];
+ }
+
+ return $finders
+ ->listenerDirectoryFinder()
+ ->withModuleInfo()
+ ->reduce(fn(array $discovered, ModuleFileInfo $file) => array_merge_recursive(
+ $discovered,
+ DiscoverEvents::within($file->getPathname(), $file->module()->path('src'))
+ ), []);
+ }
+
+ public function handle(Collection $data): void
+ {
+ $data->each(function(array $listeners, string $event) {
+ foreach (array_unique($listeners, SORT_REGULAR) as $listener) {
+ $this->events->listen($event, $listener);
+ }
+ });
+ }
+
+ protected function shouldDiscoverEvents(): bool
+ {
+ return $this->config->get('app-modules.should_discover_events') ?? $this->appIsConfiguredToDiscoverEvents();
+ }
+
+ protected 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/Autodiscovery/GatePlugin.php b/src/Support/Autodiscovery/GatePlugin.php
new file mode 100644
index 0000000..a50bba4
--- /dev/null
+++ b/src/Support/Autodiscovery/GatePlugin.php
@@ -0,0 +1,53 @@
+modelFileFinder()
+ ->withModuleInfo()
+ ->values()
+ ->map(function(ModuleFileInfo $file) {
+ $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();
+ }
+
+ public function handle(Collection $data): void
+ {
+ $data->each(fn(array $row) => $this->gate->policy($row['fqcn'], $row['policy']));
+ }
+}
diff --git a/src/Support/Autodiscovery/LivewirePlugin.php b/src/Support/Autodiscovery/LivewirePlugin.php
new file mode 100644
index 0000000..89d4f59
--- /dev/null
+++ b/src/Support/Autodiscovery/LivewirePlugin.php
@@ -0,0 +1,43 @@
+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(),
+ ]);
+ }
+
+ public function handle(Collection $data): void
+ {
+ $data->each(fn(array $d) => $this->livewire->component($d['name'], $d['fqcn']));
+ }
+}
diff --git a/src/Support/Autodiscovery/MigratorPlugin.php b/src/Support/Autodiscovery/MigratorPlugin.php
new file mode 100644
index 0000000..3e221b2
--- /dev/null
+++ b/src/Support/Autodiscovery/MigratorPlugin.php
@@ -0,0 +1,31 @@
+migrationDirectoryFinder()
+ ->values()
+ ->map(fn(SplFileInfo $file) => $file->getRealPath());
+ }
+
+ public function handle(Collection $data): void
+ {
+ $data->each(fn(string $path) => $this->migrator->path($path));
+ }
+}
diff --git a/src/Support/Autodiscovery/ModulesPlugin.php b/src/Support/Autodiscovery/ModulesPlugin.php
new file mode 100644
index 0000000..44beb2e
--- /dev/null
+++ b/src/Support/Autodiscovery/ModulesPlugin.php
@@ -0,0 +1,39 @@
+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 */
+ public function handle(Collection $data): Collection
+ {
+ return $data->map(fn(array $d) => new ModuleConfig($d['name'], $d['base_path'], new Collection($d['namespaces'])));
+ }
+}
diff --git a/src/Support/Autodiscovery/Plugin.php b/src/Support/Autodiscovery/Plugin.php
new file mode 100644
index 0000000..fd1528d
--- /dev/null
+++ b/src/Support/Autodiscovery/Plugin.php
@@ -0,0 +1,13 @@
+has(static::class)) {
+ $container->instance(static::class, new self());
+ }
+
+ return $container->make(static::class);
+ }
+
+ /** @param class-string<\InterNACHI\Modular\Support\Autodiscovery\Plugin> ...$class */
+ public static function register(string ...$class): void
+ {
+ static::instance()->add(...$class);
+ }
+
+ /** @param class-string<\InterNACHI\Modular\Support\Autodiscovery\Plugin> ...$class */
+ public function add(string ...$class): static
+ {
+ foreach ($class as $fqcn) {
+ $this->plugins[] = $fqcn;
+ }
+
+ return $this;
+ }
+
+ /** @return class-string<\InterNACHI\Modular\Support\Autodiscovery\Plugin>[] */
+ public function all(): array
+ {
+ return $this->plugins;
+ }
+}
diff --git a/src/Support/Autodiscovery/RoutesPlugin.php b/src/Support/Autodiscovery/RoutesPlugin.php
new file mode 100644
index 0000000..5de3ecc
--- /dev/null
+++ b/src/Support/Autodiscovery/RoutesPlugin.php
@@ -0,0 +1,23 @@
+routeFileFinder()
+ ->values()
+ ->map(fn(SplFileInfo $file) => $file->getRealPath());
+ }
+
+ public function handle(Collection $data): void
+ {
+ $data->each(fn(string $filename) => require $filename);
+ }
+}
diff --git a/src/Support/Autodiscovery/TranslatorPlugin.php b/src/Support/Autodiscovery/TranslatorPlugin.php
new file mode 100644
index 0000000..8eebbb6
--- /dev/null
+++ b/src/Support/Autodiscovery/TranslatorPlugin.php
@@ -0,0 +1,38 @@
+langDirectoryFinder()
+ ->withModuleInfo()
+ ->values()
+ ->map(fn(ModuleFileInfo $dir) => [
+ 'namespace' => $dir->module()->name,
+ 'path' => $dir->getRealPath(),
+ ]);
+ }
+
+ public function handle(Collection $data): void
+ {
+ $data->each(function(array $row) {
+ $this->translator->addNamespace($row['namespace'], $row['path']);
+ $this->translator->addJsonPath($row['path']);
+ });
+ }
+}
diff --git a/src/Support/Autodiscovery/ViewPlugin.php b/src/Support/Autodiscovery/ViewPlugin.php
new file mode 100644
index 0000000..a91fd2d
--- /dev/null
+++ b/src/Support/Autodiscovery/ViewPlugin.php
@@ -0,0 +1,35 @@
+viewDirectoryFinder()
+ ->withModuleInfo()
+ ->values()
+ ->map(fn(ModuleFileInfo $dir) => [
+ 'namespace' => $dir->module()->name,
+ 'path' => $dir->getRealPath(),
+ ]);
+ }
+
+ public function handle(Collection $data)
+ {
+ $data->each(fn(array $d) => $this->factory->addNamespace($d['namespace'], $d['path']));
+ }
+}
diff --git a/src/Support/AutodiscoveryHelper.php b/src/Support/AutodiscoveryHelper.php
index ccc9884..2816ff5 100644
--- a/src/Support/AutodiscoveryHelper.php
+++ b/src/Support/AutodiscoveryHelper.php
@@ -2,56 +2,36 @@
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\Container\BindingResolutionException;
use Illuminate\Contracts\Container\Container;
-use Illuminate\Contracts\Events\Dispatcher;
-use Illuminate\Database\Migrations\Migrator;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Collection;
-use Illuminate\Support\Str;
-use Illuminate\Translation\Translator;
-use Illuminate\View\Compilers\BladeCompiler;
-use Illuminate\View\Factory as ViewFactory;
-use Livewire\LivewireManager;
+use InterNACHI\Modular\Support\Autodiscovery\Attributes\AfterResolving;
+use InterNACHI\Modular\Support\Autodiscovery\Attributes\OnBoot;
+use InterNACHI\Modular\Support\Autodiscovery\Plugin;
use ReflectionClass;
use RuntimeException;
-use Symfony\Component\Finder\SplFileInfo;
use Throwable;
class AutodiscoveryHelper
{
protected ?array $data = null;
+ protected array $plugins = [];
+
+ protected array $handled = [];
+
public function __construct(
protected FinderFactory $finders,
protected Filesystem $fs,
+ protected Container $app,
protected string $cache_path,
) {
}
- public function writeCache(Container $app): void
+ public function writeCache(): void
{
- $helpers = [
- $this->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) {
- }
+ foreach (array_keys($this->plugins) as $plugin) {
+ $this->discover($plugin);
}
$cache = Collection::make($this->data)->toArray();
@@ -76,237 +56,80 @@ 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'])));
+ $this->handled = [];
+ $this->data = null;
}
- public function routes(): void
+ /** @param class-string $plugin */
+ public function register(string $plugin): static
{
- $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']),
- );
+ $this->plugins[$plugin] ??= null;
- // 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']);
- },
- );
+ return $this;
}
- public function migrations(Migrator $migrator): void
+ public function bootPlugins(): 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),
- );
+ foreach ($this->plugins as $class => $_) {
+ $attributes = (new ReflectionClass($class))->getAttributes();
+ foreach ($attributes as $attribute) {
+ if (AfterResolving::class === $attribute->getName()) {
+ $abstract = $attribute->getArguments()[0];
+ $this->app->afterResolving($abstract, fn($resolved) => $this->handle($class, $resolved));
+ if ($this->app->resolved($abstract)) {
+ $this->handle($class);
+ }
+ return;
+ }
+
+ if (OnBoot::class === $attribute->getName()) {
+ $this->handle($class);
+ return;
+ }
+ }
+ }
}
- public function commands(Artisan $artisan): void
+ /** @param class-string $name */
+ public function discover(string $name): Collection
{
- $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),
- );
+ $this->data ??= $this->readData();
+ $this->data[$name] ??= $this->plugin($name)->discover($this->finders);
+
+ return collect($this->data[$name]);
}
- public function policies(Gate $gate): void
+ /** @param class-string $name */
+ public function handle(string $name, ?object $dependency = null): mixed
{
- $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']),
- );
+ return $this->handled[$name] ??= $this->plugin($name, $dependency)->handle($this->discover($name));
}
- public function events(Dispatcher $events, bool $autodiscover = true): void
+ public function handleIf(string $name, bool $condition): mixed
{
- $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);
- }
- },
- );
+ if ($condition) {
+ return $this->handle($name);
+ }
+
+ return null;
}
- public function livewire(LivewireManager $livewire): void
+ /**
+ * @template TPlugin of Plugin
+ * @param class-string $plugin
+ * @return TPlugin
+ */
+ public function plugin(string $plugin, ?object $dependency = null): Plugin
{
- $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);
+ if (! isset($this->plugins[$plugin]) && $dependency) {
+ $this->app
+ ->when($plugin)
+ ->needs($dependency::class)
+ ->give(fn() => $dependency);
+ }
- return $each
- ? Collection::make($this->data[$key])->each($each)
- : $this->data[$key];
+ return $this->plugins[$plugin] ??= $this->app->make($plugin);
}
protected function readData(): array
@@ -319,10 +142,4 @@ protected function readData(): array
return [];
}
}
-
- protected function isInstantiableCommand($command): bool
- {
- return is_subclass_of($command, Command::class)
- && ! (new ReflectionClass($command))->isAbstract();
- }
}
diff --git a/src/Support/ModularServiceProvider.php b/src/Support/ModularServiceProvider.php
index 34341ab..81ae004 100644
--- a/src/Support/ModularServiceProvider.php
+++ b/src/Support/ModularServiceProvider.php
@@ -3,33 +3,31 @@
namespace InterNACHI\Modular\Support;
use Illuminate\Console\Application as Artisan;
-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;
-use Illuminate\Translation\Translator;
-use Illuminate\View\Compilers\BladeCompiler;
-use Illuminate\View\Factory as ViewFactory;
use InterNACHI\Modular\Console\Commands\Make\MakeMigration;
use InterNACHI\Modular\Console\Commands\Make\MakeModule;
use InterNACHI\Modular\Console\Commands\ModulesCache;
use InterNACHI\Modular\Console\Commands\ModulesClear;
use InterNACHI\Modular\Console\Commands\ModulesList;
use InterNACHI\Modular\Console\Commands\ModulesSync;
+use InterNACHI\Modular\Support\Autodiscovery\ArtisanPlugin;
+use InterNACHI\Modular\Support\Autodiscovery\BladePlugin;
+use InterNACHI\Modular\Support\Autodiscovery\EventsPlugin;
+use InterNACHI\Modular\Support\Autodiscovery\GatePlugin;
+use InterNACHI\Modular\Support\Autodiscovery\LivewirePlugin;
+use InterNACHI\Modular\Support\Autodiscovery\MigratorPlugin;
+use InterNACHI\Modular\Support\Autodiscovery\PluginRegistry;
+use InterNACHI\Modular\Support\Autodiscovery\RoutesPlugin;
+use InterNACHI\Modular\Support\Autodiscovery\TranslatorPlugin;
+use InterNACHI\Modular\Support\Autodiscovery\ViewPlugin;
use Livewire\LivewireManager;
class ModularServiceProvider extends ServiceProvider
{
- protected ?ModuleRegistry $registry = null;
-
protected ?AutodiscoveryHelper $autodiscovery_helper = null;
protected string $base_dir;
@@ -62,6 +60,7 @@ public function register(): void
return new AutodiscoveryHelper(
$app->make(FinderFactory::class),
$app->make(Filesystem::class),
+ $app,
$this->app->bootstrapPath('cache/app-modules.php')
);
});
@@ -72,47 +71,25 @@ public function register(): void
$this->registerEloquentFactories();
- $this->app->resolving(Migrator::class, fn(Migrator $migrator) => $this->autodiscover()->migrations($migrator));
- $this->app->resolving(Gate::class, fn(Gate $gate) => $this->autodiscover()->policies($gate));
+ PluginRegistry::register(
+ RoutesPlugin::class,
+ TranslatorPlugin::class,
+ ViewPlugin::class,
+ BladePlugin::class,
+ EventsPlugin::class,
+ MigratorPlugin::class,
+ GatePlugin::class,
+ );
- Artisan::starting(function(Artisan $artisan) {
- $this->autodiscover()->commands($artisan);
- $this->registerNamespacesInTinker();
- });
+ $this->app->booting($this->bootPlugins(...));
}
public function boot(): void
- {
- $this->publishVendorFiles();
- $this->bootPackageCommands();
-
- $this->bootRoutes();
- $this->bootViews();
- $this->bootBladeComponents();
- $this->bootTranslations();
- $this->bootEvents();
- $this->bootLivewireComponents();
- }
-
- protected function registry(): ModuleRegistry
- {
- return $this->registry ??= $this->app->make(ModuleRegistry::class);
- }
-
- protected function autodiscover(): AutodiscoveryHelper
- {
- return $this->autodiscovery_helper ??= $this->app->make(AutodiscoveryHelper::class);
- }
-
- protected function publishVendorFiles(): void
{
$this->publishes([
"{$this->base_dir}/config/app-modules.php" => $this->app->configPath('app-modules.php'),
], 'modular-config');
- }
-
- protected function bootPackageCommands(): void
- {
+
if ($this->app->runningInConsole()) {
$this->commands([
MakeModule::class,
@@ -124,74 +101,37 @@ protected function bootPackageCommands(): void
}
}
- protected function bootRoutes(): void
+ protected function bootPlugins(): void
{
- if (! $this->app->routesAreCached()) {
- $this->autodiscover()->routes();
+ $plugins = PluginRegistry::instance()->all();
+
+ // First register all plugins with the auto-discovery helper
+ foreach ($plugins as $class) {
+ $this->autodiscover()->register($class);
}
+
+ // Then boot all plugins that have annotations
+ $this->autodiscover()->bootPlugins();
+
+ // Finally, handle some special plugin cases
+ $this->autodiscover()->handleIf(RoutesPlugin::class, condition: ! $this->app->routesAreCached());
+ $this->autodiscover()->handleIf(LivewirePlugin::class, condition: class_exists(LivewireManager::class));
+ Artisan::starting(fn($artisan) => $this->autodiscover()->handle(ArtisanPlugin::class, $artisan));
}
- protected function bootViews(): void
- {
- $this->callAfterResolving('view', function(ViewFactory $factory) {
- $this->autodiscover()->views($factory);
- });
- }
-
- protected function bootBladeComponents(): void
- {
- $this->callAfterResolving(BladeCompiler::class, function(BladeCompiler $blade) {
- $this->autodiscover()->blade($blade);
- });
- }
-
- protected function bootTranslations(): void
- {
- $this->callAfterResolving('translator', function(TranslatorContract $translator) {
- if ($translator instanceof Translator) {
- $this->autodiscover()->translations($translator);
- }
- });
- }
-
- protected function bootEvents(): void
- {
- $this->callAfterResolving(Dispatcher::class, function(Dispatcher $events) {
- $this->autodiscover()->events($events, $this->shouldDiscoverEvents());
- });
- }
-
- protected function bootLivewireComponents(): void
+ protected function autodiscover(): AutodiscoveryHelper
{
- if (class_exists(LivewireManager::class)) {
- $this->autodiscover()->livewire($this->app->make(LivewireManager::class));
- }
+ return $this->autodiscovery_helper ??= $this->app->make(AutodiscoveryHelper::class);
}
protected function registerEloquentFactories(): void
{
- $helper = new DatabaseFactoryHelper($this->registry());
+ $helper = new DatabaseFactoryHelper($this->app->make(ModuleRegistry::class));
EloquentFactory::guessModelNamesUsing($helper->modelNameResolver());
EloquentFactory::guessFactoryNamesUsing($helper->factoryNameResolver());
}
- protected function registerNamespacesInTinker(): void
- {
- if (! class_exists('Laravel\\Tinker\\TinkerServiceProvider')) {
- return;
- }
-
- $namespaces = $this->registry()
- ->modules()
- ->flatMap(fn(ModuleConfig $config) => $config->namespaces)
- ->reject(fn($ns) => Str::endsWith($ns, ['Tests\\', 'Database\\Factories\\', 'Database\\Seeders\\']))
- ->values()
- ->all();
-
- Config::set('tinker.alias', array_merge($namespaces, Config::get('tinker.alias', [])));
- }
-
protected function getModulesBasePath(): string
{
if (null === $this->modules_path) {
@@ -201,18 +141,4 @@ protected function getModulesBasePath(): string
return $this->modules_path;
}
-
- protected function shouldDiscoverEvents(): bool
- {
- return $this->app->make('config')
- ->get('app-modules.should_discover_events') ?? $this->appIsConfiguredToDiscoverEvents();
- }
-
- protected 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/ModuleRegistry.php b/src/Support/ModuleRegistry.php
index 528177d..fd31c11 100644
--- a/src/Support/ModuleRegistry.php
+++ b/src/Support/ModuleRegistry.php
@@ -5,6 +5,7 @@
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use InterNACHI\Modular\Exceptions\CannotFindModuleForPathException;
+use InterNACHI\Modular\Support\Autodiscovery\ModulesPlugin;
class ModuleRegistry
{
@@ -56,16 +57,22 @@ public function moduleForClass(string $fqcn): ?ModuleConfig
});
}
+ public function setModules(Collection $modules): static
+ {
+ $this->modules = $modules->ensure(ModulesPlugin::class);
+
+ return $this;
+ }
+
+ /** @return Collection */
public function modules(): Collection
{
- return $this->modules ??= $this->autodiscovery_helper->modules();
+ return $this->modules ??= $this->autodiscovery_helper->handle(ModulesPlugin::class);
}
public function reload(): Collection
{
- $this->modules = null;
-
- return $this->modules ??= $this->autodiscovery_helper->modules(reload: true);
+ return $this->modules = $this->autodiscovery_helper->handle(ModulesPlugin::class);
}
protected function extractModuleNameFromPath(string $path): string