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();
+});