diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aaa0d6..b49a40b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to `laravel-ddd` will be documented in this file. +## [Unversioned] +### Added +- Ability to ignore folders during autoloading via config(`ddd.autoload_ignore`), or register a custom filter callback via `DDD::filterAutoloadPathsUsing(callable $filter)`. + +### Changed +- Internal: Domain cache is no longer quietly cleared on laravel's `cache:clearing` event, so that `ddd:cache` yields consistent results no matter which order it runs in production (before or after `cache:clear` or `optimize:clear` commands). + ## [1.1.0] - 2024-04-07 ### Added - Add `ddd:class` generator extending Laravel's `make:class` (Laravel 11 only). diff --git a/README.md b/README.md index c7e19d2..13a2f1b 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,27 @@ When `ddd.autoload.factories` is enabled, the package will register a custom fac If your application implements its own factory discovery using `Factory::guessFactoryNamesUsing()`, you should set `ddd.autoload.factories` to `false` to ensure it is not overridden. +### Ignoring Paths During Autoloading +To specify folders or paths that should be skipped during autoloading discovery, add them to the `ddd.autoload_ignore` configuration option. By default, the `Tests` and `Migrations` folders are ignored. +```php +'autoload_ignore' => [ + 'Tests', + 'Database/Migrations', +], +``` + +Paths specified here are relative to the root of each domain. e.g., `src/Domain/Invoicing/{path-to-ignore}`. If more advanced filtering is needed, a callback can be registered using `DDD::filterAutoloadPathsUsing(callback $filter)` in your AppServiceProvider's boot method: +```php +use Lunarstorm\LaravelDDD\Facades\DDD; +use Symfony\Component\Finder\SplFileInfo; + +DDD::filterAutoloadPathsUsing(function (SplFileInfo $file) { + if (basename($file->getRelativePathname()) === 'functions.php') { + return false; + } +}); +``` + ### Disabling Autoloading You may disable autoloading by setting the respective autoload options to `false` in the configuration file as needed, or by commenting out the autoload configuration entirely. ```php @@ -221,11 +242,14 @@ You may disable autoloading by setting the respective autoload options to `false // 'factories' => true, // ], ``` + + ## Autoloading in Production In production, you should cache the autoload manifests using the `ddd:cache` command as part of your application's deployment process. This will speed up the auto-discovery and registration of domain providers and commands. The `ddd:clear` command may be used to clear the cache if needed. + ## Configuration File This is the content of the published config file (`ddd.php`): @@ -356,34 +380,50 @@ return [ */ 'autoload' => [ /** - * When enabled, any class in the domain layer extending - * `Illuminate\Support\ServiceProvider` will be - * auto-registered as a service provider + * When enabled, any class within the domain layer extending `Illuminate\Support\ServiceProvider` + * will be auto-registered as a service provider */ 'providers' => true, /** - * When enabled, any class in the domain layer extending - * `Illuminate\Console\Command` will be auto-registered - * as a command when running in console. + * When enabled, any class within the domain layer extending `Illuminate\Console\Command` + * will be auto-registered as a command when running in console. */ 'commands' => true, /** - * When enabled, a custom policy discovery callback will be - * registered to resolve policy names for domain models, - * or fallback to Laravel's default otherwise. + * When enabled, the package will register a custom policy discovery callback to resolve policy names + * for domain models, and fallback to Laravel's default for all other cases. */ 'policies' => true, /** - * When enabled, a custom policy discovery callback will be - * registered to resolve factory names for domain models, - * or fallback to Laravel's default otherwise. + * When enabled, the package will register a custom factory discovery callback to resolve factory names + * for domain models, and fallback to Laravel's default for all other cases. */ 'factories' => true, ], + /* + |-------------------------------------------------------------------------- + | Autoload Ignore Folders + |-------------------------------------------------------------------------- + | + | Folders that should be skipped during autoloading discovery, + | relative to the root of each domain. + | + | e.g., src/Domain/Invoicing/ + | + | If more advanced filtering is needed, a callback can be registered + | using the `DDD::filterAutoloadPathsUsing(callback $filter)` in + | the AppServiceProvider's boot method. + | + */ + 'autoload_ignore' => [ + 'Tests', + 'Database/Migrations', + ], + /* |-------------------------------------------------------------------------- | Caching diff --git a/config/ddd.php b/config/ddd.php index ae7acb4..66d8901 100644 --- a/config/ddd.php +++ b/config/ddd.php @@ -150,6 +150,26 @@ 'factories' => true, ], + /* + |-------------------------------------------------------------------------- + | Autoload Ignore Folders + |-------------------------------------------------------------------------- + | + | Folders that should be skipped during autoloading discovery, + | relative to the root of each domain. + | + | e.g., src/Domain/Invoicing/ + | + | If more advanced filtering is needed, a callback can be registered + | using `DDD::filterAutoloadPathsUsing(callback $filter)` in + | the AppServiceProvider's boot method. + | + */ + 'autoload_ignore' => [ + 'Tests', + 'Database/Migrations', + ], + /* |-------------------------------------------------------------------------- | Caching diff --git a/config/ddd.php.stub b/config/ddd.php.stub index cd6a2bb..6c10645 100644 --- a/config/ddd.php.stub +++ b/config/ddd.php.stub @@ -150,6 +150,26 @@ return [ 'factories' => true, ], + /* + |-------------------------------------------------------------------------- + | Autoload Ignore Folders + |-------------------------------------------------------------------------- + | + | Folders that should be skipped during autoloading discovery, + | relative to the root of each domain. + | + | e.g., src/Domain/Invoicing/ + | + | If more advanced filtering is needed, a callback can be registered + | using `DDD::filterAutoloadPathsUsing(callback $filter)` in + | the AppServiceProvider's boot method. + | + */ + 'autoload_ignore' => [ + 'Tests', + 'Database/Migrations', + ], + /* |-------------------------------------------------------------------------- | Caching diff --git a/src/DomainManager.php b/src/DomainManager.php new file mode 100755 index 0000000..2de6b0f --- /dev/null +++ b/src/DomainManager.php @@ -0,0 +1,28 @@ +autoloadFilter = null; + } + + public function filterAutoloadPathsUsing(callable $filter): void + { + $this->autoloadFilter = $filter; + } + + public function getAutoloadFilter(): ?callable + { + return $this->autoloadFilter; + } +} diff --git a/src/Facades/DDD.php b/src/Facades/DDD.php new file mode 100644 index 0000000..5164cda --- /dev/null +++ b/src/Facades/DDD.php @@ -0,0 +1,18 @@ +app->scoped(DomainManager::class, function () { + return new DomainManager(); + }); + + $this->app->bind('ddd', DomainManager::class); + /* * This class is a Package Service Provider * @@ -69,7 +73,5 @@ public function packageBooted() public function packageRegistered() { (new DomainAutoloader())->autoload(); - - Event::subscribe(CacheClearSubscriber::class); } } diff --git a/src/Support/DomainAutoloader.php b/src/Support/DomainAutoloader.php index ca75ec8..f107dc0 100644 --- a/src/Support/DomainAutoloader.php +++ b/src/Support/DomainAutoloader.php @@ -16,6 +16,7 @@ use Lunarstorm\LaravelDDD\Factories\DomainFactory; use Lunarstorm\LaravelDDD\ValueObjects\DomainObject; use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; use Throwable; class DomainAutoloader @@ -122,6 +123,28 @@ protected function handleFactories(): void }); } + protected static function finder($paths) + { + $filter = app('ddd')->getAutoloadFilter() ?? function (SplFileInfo $file) { + $pathAfterDomain = str($file->getRelativePath()) + ->replace('\\', '/') + ->after('/') + ->finish('/'); + + $ignoredFolders = collect(config('ddd.autoload_ignore', [])) + ->map(fn ($path) => Str::finish($path, '/')); + + if ($pathAfterDomain->startsWith($ignoredFolders)) { + return false; + } + }; + + return Finder::create() + ->files() + ->in($paths) + ->filter($filter); + } + protected static function discoverProviders(): array { $configValue = config('ddd.autoload.providers'); @@ -138,7 +161,7 @@ protected static function discoverProviders(): array return []; } - return Lody::classesFromFinder(Finder::create()->files()->in($paths)) + return Lody::classesFromFinder(static::finder($paths)) ->isNotAbstract() ->isInstanceOf(ServiceProvider::class) ->toArray(); @@ -162,7 +185,7 @@ protected static function discoverCommands(): array return []; } - return Lody::classesFromFinder(Finder::create()->files()->in($paths)) + return Lody::classesFromFinder(static::finder($paths)) ->isNotAbstract() ->isInstanceOf(Command::class) ->toArray(); diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php new file mode 100644 index 0000000..0e94256 --- /dev/null +++ b/tests/Autoload/IgnoreTest.php @@ -0,0 +1,102 @@ +setupTestApplication(); +}); + +it('can ignore folders when autoloading', function () { + Artisan::call('ddd:cache'); + + $expected = [ + 'Domain\Invoicing\Providers\InvoiceServiceProvider', + 'Domain\Invoicing\Commands\InvoiceDeliver', + ]; + + $cached = [ + ...DomainCache::get('domain-providers'), + ...DomainCache::get('domain-commands'), + ]; + + expect($cached)->toEqual($expected); + + Config::set('ddd.autoload_ignore', ['Commands']); + + Artisan::call('ddd:cache'); + + $expected = [ + 'Domain\Invoicing\Providers\InvoiceServiceProvider', + ]; + + $cached = [ + ...DomainCache::get('domain-providers'), + ...DomainCache::get('domain-commands'), + ]; + + expect($cached)->toEqual($expected); + + Config::set('ddd.autoload_ignore', ['Providers']); + + Artisan::call('ddd:cache'); + + $expected = [ + 'Domain\Invoicing\Commands\InvoiceDeliver', + ]; + + $cached = [ + ...DomainCache::get('domain-providers'), + ...DomainCache::get('domain-commands'), + ]; + + expect($cached)->toEqual($expected); +}); + +it('can register a custom autoload filter', function () { + Artisan::call('ddd:cache'); + + $expected = [ + 'Domain\Invoicing\Providers\InvoiceServiceProvider', + 'Domain\Invoicing\Commands\InvoiceDeliver', + ]; + + $cached = [ + ...DomainCache::get('domain-providers'), + ...DomainCache::get('domain-commands'), + ]; + + expect($cached)->toEqual($expected); + + $secret = null; + + DDD::filterAutoloadPathsUsing(function (SplFileInfo $file) use (&$secret) { + $ignoredFiles = [ + 'InvoiceServiceProvider.php', + 'InvoiceDeliver.php', + ]; + + $secret = 'i-was-invoked'; + + if (Str::endsWith($file->getRelativePathname(), $ignoredFiles)) { + return false; + } + }); + + Artisan::call('ddd:cache'); + + $cached = [ + ...DomainCache::get('domain-providers'), + ...DomainCache::get('domain-commands'), + ]; + + expect($cached)->toEqual([]); + + expect($secret)->toEqual('i-was-invoked'); +}); diff --git a/tests/Command/CacheTest.php b/tests/Command/CacheTest.php index 029e4c2..81a3587 100644 --- a/tests/Command/CacheTest.php +++ b/tests/Command/CacheTest.php @@ -40,3 +40,25 @@ expect(DomainCache::get('domain-providers'))->toBeNull(); expect(DomainCache::get('domain-commands'))->toBeNull(); }); + +it('will not be cleared by laravel cache clearing', function () { + config(['cache.default' => 'file']); + + expect(DomainCache::get('domain-providers'))->toBeNull(); + expect(DomainCache::get('domain-commands'))->toBeNull(); + + $this->artisan('ddd:cache')->execute(); + + expect(DomainCache::get('domain-providers'))->not->toBeNull(); + expect(DomainCache::get('domain-commands'))->not->toBeNull(); + + $this->artisan('cache:clear')->execute(); + + expect(DomainCache::get('domain-providers'))->not->toBeNull(); + expect(DomainCache::get('domain-commands'))->not->toBeNull(); + + $this->artisan('optimize:clear')->execute(); + + expect(DomainCache::get('domain-providers'))->not->toBeNull(); + expect(DomainCache::get('domain-commands'))->not->toBeNull(); +});