From 497b8b09c2eae60054579f2d11a7ab761317d1d8 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 16 May 2024 08:20:31 -0400 Subject: [PATCH 001/169] WIP experimentation --- src/Commands/Concerns/OverridesHandle.php | 25 ++++ .../Concerns/ResolvesDomainFromInput.php | 9 +- src/Commands/DomainMailMakeCommand.php | 87 ++++++++++++++ src/Commands/DomainModelMakeCommand.php | 93 +-------------- src/Commands/DomainModelMakeLegacyCommand.php | 108 ++++++++++++++++++ 5 files changed, 229 insertions(+), 93 deletions(-) create mode 100644 src/Commands/Concerns/OverridesHandle.php create mode 100644 src/Commands/DomainModelMakeLegacyCommand.php diff --git a/src/Commands/Concerns/OverridesHandle.php b/src/Commands/Concerns/OverridesHandle.php new file mode 100644 index 0000000..0240142 --- /dev/null +++ b/src/Commands/Concerns/OverridesHandle.php @@ -0,0 +1,25 @@ +beforeHandle(); + + parent::handle(); + + $this->afterHandle(); + } +} diff --git a/src/Commands/Concerns/ResolvesDomainFromInput.php b/src/Commands/Concerns/ResolvesDomainFromInput.php index 9f323b6..1f2f723 100644 --- a/src/Commands/Concerns/ResolvesDomainFromInput.php +++ b/src/Commands/Concerns/ResolvesDomainFromInput.php @@ -10,7 +10,8 @@ trait ResolvesDomainFromInput { - use CanPromptForDomain; + use OverridesHandle, + CanPromptForDomain; protected $nameIsAbsolute = false; @@ -66,7 +67,7 @@ protected function getPath($name) return parent::getPath($name); } - public function handle() + protected function beforeHandle() { $nameInput = $this->getNameInput(); @@ -90,7 +91,7 @@ public function handle() }; // If the domain is not set, prompt for it - if (! $this->domain) { + if (!$this->domain) { $this->domain = new Domain($this->promptForDomainName()); } @@ -105,7 +106,5 @@ public function handle() } $this->input->setArgument('name', $nameInput); - - parent::handle(); } } diff --git a/src/Commands/DomainMailMakeCommand.php b/src/Commands/DomainMailMakeCommand.php index 15f9508..6021c70 100644 --- a/src/Commands/DomainMailMakeCommand.php +++ b/src/Commands/DomainMailMakeCommand.php @@ -3,6 +3,7 @@ namespace Lunarstorm\LaravelDDD\Commands; use Illuminate\Foundation\Console\MailMakeCommand; +use Illuminate\Support\Str; use Lunarstorm\LaravelDDD\Commands\Concerns\ResolvesDomainFromInput; class DomainMailMakeCommand extends MailMakeCommand @@ -10,4 +11,90 @@ class DomainMailMakeCommand extends MailMakeCommand use ResolvesDomainFromInput; protected $name = 'ddd:mail'; + + /** + * Create a model factory for the model. + * + * @return void + */ + protected function createFactory() + { + $factory = Str::studly($this->argument('name')); + + $this->call('ddd:factory', [ + 'name' => "{$factory}Factory", + '--domain' => $this->domain->dotName, + '--model' => $this->qualifyClass($this->getNameInput()), + ]); + } + + /** + * Create a migration file for the model. + * + * @return void + */ + protected function createMigration() + { + $table = Str::snake(Str::pluralStudly(class_basename($this->argument('name')))); + + if ($this->option('pivot')) { + $table = Str::singular($table); + } + + $this->call('make:migration', [ + 'name' => "create_{$table}_table", + '--create' => $table, + ]); + } + + /** + * Create a seeder file for the model. + * + * @return void + */ + protected function createSeeder() + { + $seeder = Str::studly(class_basename($this->argument('name'))); + + $this->call('make:seeder', [ + 'name' => "{$seeder}Seeder", + ]); + } + + /** + * Create a controller for the model. + * + * @return void + */ + protected function createController() + { + $controller = Str::studly(class_basename($this->argument('name'))); + + $modelName = $this->qualifyClass($this->getNameInput()); + + $this->call('make:controller', array_filter([ + 'name' => "{$controller}Controller", + '--model' => $this->option('resource') || $this->option('api') ? $modelName : null, + '--api' => $this->option('api'), + '--requests' => $this->option('requests') || $this->option('all'), + '--test' => $this->option('test'), + '--pest' => $this->option('pest'), + ])); + } + + /** + * Create a policy file for the model. + * + * @return void + */ + protected function createPolicy() + { + $policy = Str::studly(class_basename($this->argument('name'))); + + $this->call('ddd:policy', [ + 'name' => "{$policy}Policy", + '--domain' => $this->domain->dotName, + '--model' => $this->qualifyClass($this->getNameInput()), + ]); + } } diff --git a/src/Commands/DomainModelMakeCommand.php b/src/Commands/DomainModelMakeCommand.php index 18430c2..1f7a4e4 100644 --- a/src/Commands/DomainModelMakeCommand.php +++ b/src/Commands/DomainModelMakeCommand.php @@ -2,100 +2,17 @@ namespace Lunarstorm\LaravelDDD\Commands; +use Illuminate\Foundation\Console\ModelMakeCommand; use Illuminate\Support\Str; +use Lunarstorm\LaravelDDD\Commands\Concerns\ResolvesDomainFromInput; use Lunarstorm\LaravelDDD\Support\DomainResolver; use Symfony\Component\Console\Input\InputOption; -class DomainModelMakeCommand extends DomainGeneratorCommand +class DomainModelMakeCommand extends ModelMakeCommand { - protected $name = 'ddd:model'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Generate a domain model'; - - protected $type = 'Model'; - - protected function getOptions() - { - return [ - ...parent::getOptions(), - ['factory', 'f', InputOption::VALUE_NONE, 'Create a new factory for the domain model'], - ]; - } - - protected function getStub() - { - return $this->resolveStubPath('model.php.stub'); - } - - protected function preparePlaceholders(): array - { - $baseClass = config('ddd.base_model'); - $baseClassName = class_basename($baseClass); - - return [ - 'extends' => filled($baseClass) ? " extends {$baseClassName}" : '', - 'baseClassImport' => filled($baseClass) ? "use {$baseClass};" : '', - ]; - } - - public function handle() - { - $this->createBaseModelIfNeeded(); - - parent::handle(); - - if ($this->option('factory')) { - $this->createFactory(); - } - } - - protected function createBaseModelIfNeeded() - { - if (! $this->shouldCreateModel()) { - return; - } - - $baseModel = config('ddd.base_model'); - - $this->warn("Base model {$baseModel} doesn't exist, generating..."); + use ResolvesDomainFromInput; - $domain = DomainResolver::guessDomainFromClass($baseModel); - - $name = Str::after($baseModel, $domain); - - $this->call(DomainBaseModelMakeCommand::class, [ - '--domain' => $domain, - 'name' => $name, - ]); - } - - protected function shouldCreateModel(): bool - { - $baseModel = config('ddd.base_model'); - - // If the class exists, we don't need to create it. - if (class_exists($baseModel)) { - return false; - } - - // If the class is outside of the domain layer, we won't attempt to create it. - if (! DomainResolver::isDomainClass($baseModel)) { - return false; - } - - // At this point the class is probably a domain object, but we should - // check if the expected path exists. - if (file_exists(app()->basePath(DomainResolver::guessPathFromClass($baseModel)))) { - return false; - } - - return true; - } + protected $name = 'ddd:model'; protected function createFactory() { diff --git a/src/Commands/DomainModelMakeLegacyCommand.php b/src/Commands/DomainModelMakeLegacyCommand.php new file mode 100644 index 0000000..d649796 --- /dev/null +++ b/src/Commands/DomainModelMakeLegacyCommand.php @@ -0,0 +1,108 @@ +resolveStubPath('model.php.stub'); + } + + protected function preparePlaceholders(): array + { + $baseClass = config('ddd.base_model'); + $baseClassName = class_basename($baseClass); + + return [ + 'extends' => filled($baseClass) ? " extends {$baseClassName}" : '', + 'baseClassImport' => filled($baseClass) ? "use {$baseClass};" : '', + ]; + } + + public function handle() + { + $this->createBaseModelIfNeeded(); + + parent::handle(); + + if ($this->option('factory')) { + $this->createFactory(); + } + } + + protected function createBaseModelIfNeeded() + { + if (! $this->shouldCreateModel()) { + return; + } + + $baseModel = config('ddd.base_model'); + + $this->warn("Base model {$baseModel} doesn't exist, generating..."); + + $domain = DomainResolver::guessDomainFromClass($baseModel); + + $name = Str::after($baseModel, $domain); + + $this->call(DomainBaseModelMakeCommand::class, [ + '--domain' => $domain, + 'name' => $name, + ]); + } + + protected function shouldCreateModel(): bool + { + $baseModel = config('ddd.base_model'); + + // If the class exists, we don't need to create it. + if (class_exists($baseModel)) { + return false; + } + + // If the class is outside of the domain layer, we won't attempt to create it. + if (! DomainResolver::isDomainClass($baseModel)) { + return false; + } + + // At this point the class is probably a domain object, but we should + // check if the expected path exists. + if (file_exists(app()->basePath(DomainResolver::guessPathFromClass($baseModel)))) { + return false; + } + + return true; + } + + protected function createFactory() + { + $this->call(DomainFactoryMakeCommand::class, [ + 'name' => $this->getNameInput().'Factory', + '--domain' => $this->domain->dotName, + '--model' => $this->qualifyClass($this->getNameInput()), + ]); + } +} From 186bab2edf57c729350502ca88127f6e53378128 Mon Sep 17 00:00:00 2001 From: JasperTey Date: Thu, 16 May 2024 12:21:26 +0000 Subject: [PATCH 002/169] Fix styling --- src/Commands/Concerns/ResolvesDomainFromInput.php | 6 +++--- src/Commands/DomainModelMakeCommand.php | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Commands/Concerns/ResolvesDomainFromInput.php b/src/Commands/Concerns/ResolvesDomainFromInput.php index 1f2f723..9a4ddd8 100644 --- a/src/Commands/Concerns/ResolvesDomainFromInput.php +++ b/src/Commands/Concerns/ResolvesDomainFromInput.php @@ -10,8 +10,8 @@ trait ResolvesDomainFromInput { - use OverridesHandle, - CanPromptForDomain; + use CanPromptForDomain, + OverridesHandle; protected $nameIsAbsolute = false; @@ -91,7 +91,7 @@ protected function beforeHandle() }; // If the domain is not set, prompt for it - if (!$this->domain) { + if (! $this->domain) { $this->domain = new Domain($this->promptForDomainName()); } diff --git a/src/Commands/DomainModelMakeCommand.php b/src/Commands/DomainModelMakeCommand.php index 1f7a4e4..e443bed 100644 --- a/src/Commands/DomainModelMakeCommand.php +++ b/src/Commands/DomainModelMakeCommand.php @@ -3,10 +3,7 @@ namespace Lunarstorm\LaravelDDD\Commands; use Illuminate\Foundation\Console\ModelMakeCommand; -use Illuminate\Support\Str; use Lunarstorm\LaravelDDD\Commands\Concerns\ResolvesDomainFromInput; -use Lunarstorm\LaravelDDD\Support\DomainResolver; -use Symfony\Component\Console\Input\InputOption; class DomainModelMakeCommand extends ModelMakeCommand { From baf8ab3f9d3abbfd7b19019120f0d00196352ecd Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 18 May 2024 11:58:34 -0400 Subject: [PATCH 003/169] Refactor OverridesHandle to HandleHooks --- .../Concerns/{OverridesHandle.php => HandleHooks.php} | 2 +- src/Commands/Concerns/ResolvesDomainFromInput.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/Commands/Concerns/{OverridesHandle.php => HandleHooks.php} (93%) diff --git a/src/Commands/Concerns/OverridesHandle.php b/src/Commands/Concerns/HandleHooks.php similarity index 93% rename from src/Commands/Concerns/OverridesHandle.php rename to src/Commands/Concerns/HandleHooks.php index 0240142..9a1ed27 100644 --- a/src/Commands/Concerns/OverridesHandle.php +++ b/src/Commands/Concerns/HandleHooks.php @@ -2,7 +2,7 @@ namespace Lunarstorm\LaravelDDD\Commands\Concerns; -trait OverridesHandle +trait HandleHooks { protected function beforeHandle() { diff --git a/src/Commands/Concerns/ResolvesDomainFromInput.php b/src/Commands/Concerns/ResolvesDomainFromInput.php index 9a4ddd8..5258ab3 100644 --- a/src/Commands/Concerns/ResolvesDomainFromInput.php +++ b/src/Commands/Concerns/ResolvesDomainFromInput.php @@ -11,7 +11,7 @@ trait ResolvesDomainFromInput { use CanPromptForDomain, - OverridesHandle; + HandleHooks; protected $nameIsAbsolute = false; @@ -91,7 +91,7 @@ protected function beforeHandle() }; // If the domain is not set, prompt for it - if (! $this->domain) { + if (!$this->domain) { $this->domain = new Domain($this->promptForDomainName()); } From 95de39917421550513892a5a294bafad601fcde2 Mon Sep 17 00:00:00 2001 From: JasperTey Date: Sat, 18 May 2024 15:58:53 +0000 Subject: [PATCH 004/169] Fix styling --- src/Commands/Concerns/ResolvesDomainFromInput.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/Concerns/ResolvesDomainFromInput.php b/src/Commands/Concerns/ResolvesDomainFromInput.php index 5258ab3..2bdc537 100644 --- a/src/Commands/Concerns/ResolvesDomainFromInput.php +++ b/src/Commands/Concerns/ResolvesDomainFromInput.php @@ -91,7 +91,7 @@ protected function beforeHandle() }; // If the domain is not set, prompt for it - if (!$this->domain) { + if (! $this->domain) { $this->domain = new Domain($this->promptForDomainName()); } From 0ade9ef31a3384a6f3339650a694582f823cbd59 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 18 May 2024 14:49:51 -0400 Subject: [PATCH 005/169] WIP --- config/ddd.php | 2 + src/Commands/DomainControllerMakeCommand.php | 36 ++++ src/Commands/DomainMigrateMakeCommand.php | 13 ++ src/Commands/DomainModelMakeCommand.php | 71 ++++++- src/Commands/DomainSeederMakeCommand.php | 13 ++ src/LaravelDDDServiceProvider.php | 3 + .../{MakeModelTest.php => Model/MakeTest.php} | 0 tests/Generator/Model/OptionsTest.php | 174 ++++++++++++++++++ 8 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 src/Commands/DomainControllerMakeCommand.php create mode 100644 src/Commands/DomainMigrateMakeCommand.php create mode 100644 src/Commands/DomainSeederMakeCommand.php rename tests/Generator/{MakeModelTest.php => Model/MakeTest.php} (100%) create mode 100644 tests/Generator/Model/OptionsTest.php diff --git a/config/ddd.php b/config/ddd.php index 66d8901..2f967b2 100644 --- a/config/ddd.php +++ b/config/ddd.php @@ -56,6 +56,7 @@ 'job' => 'Jobs', 'listener' => 'Listeners', 'mail' => 'Mail', + 'migration' => 'Database\Migrations', 'notification' => 'Notifications', 'observer' => 'Observers', 'policy' => 'Policies', @@ -63,6 +64,7 @@ 'resource' => 'Resources', 'rule' => 'Rules', 'scope' => 'Scopes', + 'seeder' => 'Database\Seeders', 'trait' => '', ], diff --git a/src/Commands/DomainControllerMakeCommand.php b/src/Commands/DomainControllerMakeCommand.php new file mode 100644 index 0000000..6e1284e --- /dev/null +++ b/src/Commands/DomainControllerMakeCommand.php @@ -0,0 +1,36 @@ +parseModel($this->option('model')); + + // if (! class_exists($modelClass) && confirm("A {$modelClass} model does not exist. Do you want to generate it?", default: true)) { + // $this->call('make:model', ['name' => $modelClass]); + // } + + $replace = $this->buildFormRequestReplacements($replace, $modelClass); + + return array_merge($replace, [ + 'DummyFullModelClass' => $modelClass, + '{{ namespacedModel }}' => $modelClass, + '{{namespacedModel}}' => $modelClass, + 'DummyModelClass' => class_basename($modelClass), + '{{ model }}' => class_basename($modelClass), + '{{model}}' => class_basename($modelClass), + 'DummyModelVariable' => lcfirst(class_basename($modelClass)), + '{{ modelVariable }}' => lcfirst(class_basename($modelClass)), + '{{modelVariable}}' => lcfirst(class_basename($modelClass)), + ]); + } +} diff --git a/src/Commands/DomainMigrateMakeCommand.php b/src/Commands/DomainMigrateMakeCommand.php new file mode 100644 index 0000000..887f9e1 --- /dev/null +++ b/src/Commands/DomainMigrateMakeCommand.php @@ -0,0 +1,13 @@ +argument('name')); + $this->call(DomainFactoryMakeCommand::class, [ - 'name' => $this->getNameInput().'Factory', + 'name' => $factory.'Factory', + '--domain' => $this->domain->dotName, + '--model' => $this->qualifyClass($this->getNameInput()), + ]); + } + + // protected function createMigration() + // { + // $table = Str::snake(Str::pluralStudly(class_basename($this->argument('name')))); + + // if ($this->option('pivot')) { + // $table = Str::singular($table); + // } + + // $this->call('make:migration', [ + // 'name' => "create_{$table}_table", + // '--create' => $table, + // ]); + // } + + /** + * Create a seeder file for the model. + * + * @return void + */ + protected function createSeeder() + { + $seeder = Str::studly(class_basename($this->argument('name'))); + + $this->call(DomainSeederMakeCommand::class, [ + 'name' => "{$seeder}Seeder", + '--domain' => $this->domain->dotName, + ]); + } + + /** + * Create a controller for the model. + * + * @return void + */ + protected function createController() + { + $controller = Str::studly(class_basename($this->argument('name'))); + + $modelName = $this->qualifyClass($this->getNameInput()); + + $this->call(DomainControllerMakeCommand::class, array_filter([ + 'name' => "{$controller}Controller", + '--model' => $this->option('resource') || $this->option('api') ? $modelName : null, + '--api' => $this->option('api'), + '--requests' => $this->option('requests') || $this->option('all'), + '--test' => $this->option('test'), + '--pest' => $this->option('pest'), + ])); + } + + /** + * Create a policy file for the model. + * + * @return void + */ + protected function createPolicy() + { + $policy = Str::studly(class_basename($this->argument('name'))); + + $this->call(DomainPolicyMakeCommand::class, [ + 'name' => "{$policy}Policy", '--domain' => $this->domain->dotName, '--model' => $this->qualifyClass($this->getNameInput()), ]); diff --git a/src/Commands/DomainSeederMakeCommand.php b/src/Commands/DomainSeederMakeCommand.php new file mode 100644 index 0000000..6ee413d --- /dev/null +++ b/src/Commands/DomainSeederMakeCommand.php @@ -0,0 +1,13 @@ +version() >= 11) { diff --git a/tests/Generator/MakeModelTest.php b/tests/Generator/Model/MakeTest.php similarity index 100% rename from tests/Generator/MakeModelTest.php rename to tests/Generator/Model/MakeTest.php diff --git a/tests/Generator/Model/OptionsTest.php b/tests/Generator/Model/OptionsTest.php new file mode 100644 index 0000000..c6a19c7 --- /dev/null +++ b/tests/Generator/Model/OptionsTest.php @@ -0,0 +1,174 @@ +model($modelName); + +// $domainFactory = $domain->factory($factoryName); + +// $expectedModelPath = base_path($domainModel->path); + +// if (file_exists($expectedModelPath)) { +// unlink($expectedModelPath); +// } + +// $expectedFactoryPath = base_path($domainFactory->path); + +// if (file_exists($expectedFactoryPath)) { +// unlink($expectedFactoryPath); +// } + +// Artisan::call('ddd:model', [ +// 'name' => $modelName, +// '--domain' => $domain->dotName, +// '--factory' => true, +// ]); + +// $output = Artisan::output(); + +// expect($output)->toContainFilepath($domainModel->path); + +// expect(file_exists($expectedModelPath))->toBeTrue("Expecting model file to be generated at {$expectedModelPath}"); +// expect(file_exists($expectedFactoryPath))->toBeTrue("Expecting factory file to be generated at {$expectedFactoryPath}"); + +// expect(file_get_contents($expectedFactoryPath)) +// ->toContain("use {$domainModel->fullyQualifiedName};") +// ->toContain("protected \$model = {$modelName}::class;"); +// }); + +it('can generate domain model with options', function ($options, $objectType, $objectName, $expectedObjectPath) { + $domainName = 'World'; + $modelName = 'Record'; + + $domain = new Domain($domainName); + + $domainModel = $domain->model($modelName); + + $expectedModelPath = base_path($domainModel->path); + + if (file_exists($expectedModelPath)) { + unlink($expectedModelPath); + } + + if (file_exists($expectedObjectPath)) { + unlink($expectedObjectPath); + } + + $command = [ + 'ddd:model', [ + 'name' => $modelName, + '--domain' => $domain->dotName, + ...$options, + ], + ]; + + $this->artisan(...$command) + ->expectsOutputToContain(Path::normalize($domainModel->path)) + ->assertExitCode(0); + + $path = base_path($expectedObjectPath); + + expect(file_exists($path))->toBeTrue("Expecting {$objectType} to be generated at {$path}"); +})->with([ + '--factory' => [ + ['--factory' => true], + 'factory', + 'RecordFactory', + 'src/Domain/World/Database/Factories/RecordFactory.php', + ], + + '--seed' => [ + ['--seed' => true], + 'seeder', + 'RecordSeeder', + 'src/Domain/World/Database/Seeders/RecordSeeder.php', + ], + + '--policy' => [ + ['--policy' => true], + 'policy', + 'RecordPolicy', + 'src/Domain/World/Policies/RecordPolicy.php', + ], +]); + +it('can generate domain model with controller', function ($options, $objectType, $objectName, $expectedObjectPath) { + $domainName = 'World'; + $modelName = 'Record'; + + $domain = new Domain($domainName); + + $domainModel = $domain->model($modelName); + + $expectedModelPath = base_path($domainModel->path); + + if (file_exists($expectedModelPath)) { + unlink($expectedModelPath); + } + + if (file_exists($expectedObjectPath)) { + unlink($expectedObjectPath); + } + + $command = [ + 'ddd:model', [ + 'name' => $modelName, + '--domain' => $domain->dotName, + ...$options, + ], + ]; + + $this->artisan(...$command) + ->expectsOutputToContain(Path::normalize($domainModel->path)) + ->assertExitCode(0); + + // $output = Artisan::output(); + + // $outputPath = Path::normalize($domainModel->path); + + // expect($output)->toContainFilepath($domainModel->path); + + $path = base_path($expectedObjectPath); + + expect(file_exists($path))->toBeTrue("Expecting {$objectType} to be generated at {$path}"); + + $contents = file_get_contents($path); + dump($contents); +})->with([ + '--controller' => [ + ['--controller' => true], + 'controller', + 'RecordController', + 'app/Http/Controllers/RecordController.php', + ], + + '--controller --api' => [ + ['--controller' => true, '--api' => true], + 'controller', + 'RecordController', + 'app/Http/Controllers/RecordController.php', + ], + + '--controller --requests' => [ + ['--controller' => true, '--requests' => true], + 'controller', + 'RecordController', + 'app/Http/Controllers/RecordController.php', + ], +]); From 33e906cc695691b2da9a782af6f412599e20a39d Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 10:39:35 -0400 Subject: [PATCH 006/169] WIP: Refactoring ddd:model, generate application layer objects, and more. --- CHANGELOG.md | 34 ++++ config/ddd.php | 16 ++ src/Commands/Concerns/CallsDomainCommands.php | 31 +++ .../Concerns/QualifiesDomainModels.php | 37 ++++ .../Concerns/ResolvesDomainFromInput.php | 6 +- src/Commands/DomainControllerMakeCommand.php | 57 +++++- src/Commands/DomainMigrateMakeCommand.php | 13 -- src/Commands/DomainModelMakeCommand.php | 120 ++++++++++-- src/Commands/DomainModelMakeLegacyCommand.php | 4 +- src/Commands/DomainRequestMakeCommand.php | 27 +++ .../Migration/BaseMigrateMakeCommand.php | 37 ++++ .../Migration/DomainMigrateMakeCommand.php | 28 +++ src/LaravelDDDServiceProvider.php | 21 +- src/Support/Domain.php | 34 +++- src/Support/DomainAutoloader.php | 19 +- src/Support/DomainMigration.php | 95 ++++++++++ src/Support/DomainResolver.php | 58 +++++- src/Support/Path.php | 5 + ...{MakeActionTest.php => ActionMakeTest.php} | 0 ...aseModelTest.php => BaseModelMakeTest.php} | 0 ...odelTest.php => BaseViewModelMakeTest.php} | 0 tests/Generator/ControllerMakeTest.php | 179 ++++++++++++++++++ ...sferObjectTest.php => DtoMakeTestTest.php} | 0 tests/Generator/ExtendedCommandsTest.php | 3 + ...akeFactoryTest.php => FactoryMakeTest.php} | 0 tests/Generator/MigrationMakeTest.php | 88 +++++++++ tests/Generator/Model/MakeTest.php | 33 +++- .../Model/MakeWithControllerTest.php | 91 +++++++++ ...ptionsTest.php => MakeWithOptionsTest.php} | 75 +------- tests/Generator/PromptTest.php | 1 + tests/Generator/RequestMakeTest.php | 54 ++++++ ...ObjectTest.php => ValueObjectMakeTest.php} | 0 ...iewModelTest.php => ViewModelMakeTest.php} | 0 tests/Support/DomainTest.php | 33 ++++ 34 files changed, 1078 insertions(+), 121 deletions(-) create mode 100644 src/Commands/Concerns/CallsDomainCommands.php create mode 100644 src/Commands/Concerns/QualifiesDomainModels.php delete mode 100644 src/Commands/DomainMigrateMakeCommand.php create mode 100644 src/Commands/DomainRequestMakeCommand.php create mode 100644 src/Commands/Migration/BaseMigrateMakeCommand.php create mode 100644 src/Commands/Migration/DomainMigrateMakeCommand.php create mode 100644 src/Support/DomainMigration.php rename tests/Generator/{MakeActionTest.php => ActionMakeTest.php} (100%) rename tests/Generator/{MakeBaseModelTest.php => BaseModelMakeTest.php} (100%) rename tests/Generator/{MakeBaseViewModelTest.php => BaseViewModelMakeTest.php} (100%) create mode 100644 tests/Generator/ControllerMakeTest.php rename tests/Generator/{MakeDataTransferObjectTest.php => DtoMakeTestTest.php} (100%) rename tests/Generator/{MakeFactoryTest.php => FactoryMakeTest.php} (100%) create mode 100644 tests/Generator/MigrationMakeTest.php create mode 100644 tests/Generator/Model/MakeWithControllerTest.php rename tests/Generator/Model/{OptionsTest.php => MakeWithOptionsTest.php} (58%) create mode 100644 tests/Generator/RequestMakeTest.php rename tests/Generator/{MakeValueObjectTest.php => ValueObjectMakeTest.php} (100%) rename tests/Generator/{MakeViewModelTest.php => ViewModelMakeTest.php} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2926890..39353f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,40 @@ All notable changes to `laravel-ddd` will be documented in this file. +## [Unreleased] +### Added +- Experimental: Ability to configure the Application Layer, to generate domain objects that don't typically belong inside the domain layer. + ```php + // In config/ddd.php + 'application_layer' => [ + 'path' => 'app/Modules', + 'namespace' => 'App\Modules', + 'objects' => [ + 'controller', + 'request', + ], + ], + ``` +- Added `ddd:controller` to generate domain-specific controllers in the application layer. +- Added `ddd:request` to generate domain-spefic requests in the application layer. +- Added `ddd:migration` to generate domain migrations. +- Migration folders across domains will be registered and scanned when running `php artisan migrate`, in addition to the standard application `database/migrations` path. +- Added `ddd:seeder` to generate domain seeders. + +### Changed +- `ddd:model` now internally extends Laravel's native `make:model` and inherits all standard options: + - `--migration|-m` + - `--factory|-f` + - `--seed|-s` + - `--controller --resource --requests|-crR` + - `--policy` + - `-mfsc` + - `--all|-a` + - `--pivot|-p` + +### Deprecated +- Domain base models are no longer required by default, and `config('ddd.base_model')` is now `null` by default. + ## [1.1.2] - 2024-09-02 ### Fixed - During domain factory autoloading, ensure that `guessFactoryNamesUsing` returns a string when a domain factory is resolved. diff --git a/config/ddd.php b/config/ddd.php index 2f967b2..dda2320 100644 --- a/config/ddd.php +++ b/config/ddd.php @@ -48,6 +48,7 @@ 'class' => '', 'channel' => 'Channels', 'command' => 'Commands', + 'controller' => 'Controllers', 'enum' => 'Enums', 'event' => 'Events', 'exception' => 'Exceptions', @@ -62,12 +63,22 @@ 'policy' => 'Policies', 'provider' => 'Providers', 'resource' => 'Resources', + 'request' => 'Requests', 'rule' => 'Rules', 'scope' => 'Scopes', 'seeder' => 'Database\Seeders', 'trait' => '', ], +'application_layer' => [ + 'path' => 'app/Modules', + 'namespace' => 'App\Modules', + 'objects' => [ + 'controller', + 'request', + ], +], + /* |-------------------------------------------------------------------------- | Base Model @@ -150,6 +161,11 @@ * for domain models, and fallback to Laravel's default for all other cases. */ 'factories' => true, + + /** + * When enabled, migration folders across all domains will be registered as a database migration path. + */ + 'migrations' => true, ], /* diff --git a/src/Commands/Concerns/CallsDomainCommands.php b/src/Commands/Concerns/CallsDomainCommands.php new file mode 100644 index 0000000..c29914d --- /dev/null +++ b/src/Commands/Concerns/CallsDomainCommands.php @@ -0,0 +1,31 @@ +getNameInput(), '/') + ? Str::beforeLast($this->getNameInput(), '/') + : null; + + $nameWithSubfolder = $subfolder ? "{$subfolder}/{$arguments['name']}" : $arguments['name']; + + return match ($command) { + 'make:request' => $this->runCommand('ddd:request', [ + 'name' => $nameWithSubfolder, + '--domain' => $this->domain->dotName, + ], $this->output), + + 'make:model' => $this->runCommand('ddd:model', [ + 'name' => $nameWithSubfolder, + '--domain' => $this->domain->dotName, + ], $this->output), + + default => $this->runCommand($command, $arguments, $this->output), + }; + } +} diff --git a/src/Commands/Concerns/QualifiesDomainModels.php b/src/Commands/Concerns/QualifiesDomainModels.php new file mode 100644 index 0000000..339f58f --- /dev/null +++ b/src/Commands/Concerns/QualifiesDomainModels.php @@ -0,0 +1,37 @@ +rootNamespace(); + + if (Str::startsWith($name, $rootNamespace)) { + return $name; + } + + // return $this->qualifyClass( + // $this->getDefaultNamespace(trim($rootNamespace, '\\')).'\\'.$name + // ); + return $this->getDefaultNamespace(trim($rootNamespace, '\\')).'\\'.$name; + } + + protected function qualifyModel(string $model) + { + if($domain = $this->domain) { + $domainModel = $domain->model($model); + + return $domainModel->fullyQualifiedName; + } + + return parent::qualifyModel($model); + } +} diff --git a/src/Commands/Concerns/ResolvesDomainFromInput.php b/src/Commands/Concerns/ResolvesDomainFromInput.php index 2bdc537..2f11895 100644 --- a/src/Commands/Concerns/ResolvesDomainFromInput.php +++ b/src/Commands/Concerns/ResolvesDomainFromInput.php @@ -11,6 +11,7 @@ trait ResolvesDomainFromInput { use CanPromptForDomain, + QualifiesDomainModels, HandleHooks; protected $nameIsAbsolute = false; @@ -27,7 +28,9 @@ protected function getOptions() protected function rootNamespace() { - return Str::finish(DomainResolver::domainRootNamespace(), '\\'); + $type = $this->guessObjectType(); + + return Str::finish(DomainResolver::resolveRootNamespace($type), '\\'); } protected function guessObjectType(): string @@ -37,6 +40,7 @@ protected function guessObjectType(): string 'ddd:base-model' => 'model', 'ddd:value' => 'value_object', 'ddd:dto' => 'data_transfer_object', + 'ddd:migration' => 'migration', default => str($this->name)->after(':')->snake()->toString(), }; } diff --git a/src/Commands/DomainControllerMakeCommand.php b/src/Commands/DomainControllerMakeCommand.php index 6e1284e..dc628e6 100644 --- a/src/Commands/DomainControllerMakeCommand.php +++ b/src/Commands/DomainControllerMakeCommand.php @@ -3,11 +3,18 @@ namespace Lunarstorm\LaravelDDD\Commands; use Illuminate\Routing\Console\ControllerMakeCommand; +use Illuminate\Support\Str; +use Lunarstorm\LaravelDDD\Commands\Concerns\CallsDomainCommands; use Lunarstorm\LaravelDDD\Commands\Concerns\ResolvesDomainFromInput; +use Lunarstorm\LaravelDDD\Support\DomainResolver; +use Lunarstorm\LaravelDDD\Support\Path; + +use function Laravel\Prompts\confirm; class DomainControllerMakeCommand extends ControllerMakeCommand { - use ResolvesDomainFromInput; + use ResolvesDomainFromInput, + CallsDomainCommands; protected $name = 'ddd:controller'; @@ -15,9 +22,13 @@ protected function buildModelReplacements(array $replace) { $modelClass = $this->parseModel($this->option('model')); - // if (! class_exists($modelClass) && confirm("A {$modelClass} model does not exist. Do you want to generate it?", default: true)) { - // $this->call('make:model', ['name' => $modelClass]); - // } + if ( + !app()->runningUnitTests() + && ! class_exists($modelClass) + && confirm("A {$modelClass} model does not exist. Do you want to generate it?", default: true) + ) { + $this->call('make:model', ['name' => $modelClass]); + } $replace = $this->buildFormRequestReplacements($replace, $modelClass); @@ -33,4 +44,42 @@ protected function buildModelReplacements(array $replace) '{{modelVariable}}' => lcfirst(class_basename($modelClass)), ]); } + + protected function buildFormRequestReplacements(array $replace, $modelClass) + { + [$namespace, $storeRequestClass, $updateRequestClass] = [ + 'Illuminate\\Http', + 'Request', + 'Request', + ]; + + if ($this->option('requests')) { + $namespace = $this->domain->namespaceFor('request'); + + [$storeRequestClass, $updateRequestClass] = $this->generateFormRequests( + $modelClass, + $storeRequestClass, + $updateRequestClass + ); + } + + $namespacedRequests = $namespace . '\\' . $storeRequestClass . ';'; + + if ($storeRequestClass !== $updateRequestClass) { + $namespacedRequests .= PHP_EOL . 'use ' . $namespace . '\\' . $updateRequestClass . ';'; + } + + return array_merge($replace, [ + '{{ storeRequest }}' => $storeRequestClass, + '{{storeRequest}}' => $storeRequestClass, + '{{ updateRequest }}' => $updateRequestClass, + '{{updateRequest}}' => $updateRequestClass, + '{{ namespacedStoreRequest }}' => $namespace . '\\' . $storeRequestClass, + '{{namespacedStoreRequest}}' => $namespace . '\\' . $storeRequestClass, + '{{ namespacedUpdateRequest }}' => $namespace . '\\' . $updateRequestClass, + '{{namespacedUpdateRequest}}' => $namespace . '\\' . $updateRequestClass, + '{{ namespacedRequests }}' => $namespacedRequests, + '{{namespacedRequests}}' => $namespacedRequests, + ]); + } } diff --git a/src/Commands/DomainMigrateMakeCommand.php b/src/Commands/DomainMigrateMakeCommand.php deleted file mode 100644 index 887f9e1..0000000 --- a/src/Commands/DomainMigrateMakeCommand.php +++ /dev/null @@ -1,13 +0,0 @@ -argument('name')); + } + + public function handle() + { + $this->beforeHandle(); + + $this->createBaseModelIfNeeded(); + + parent::handle(); + + $this->afterHandle(); + } + + protected function buildClass($name) + { + $stub = parent::buildClass($name); + + if ($baseModel = $this->getBaseModel()) { + $baseModelClass = class_basename($baseModel); + + $replacements = [ + 'use Illuminate\Database\Eloquent\Model;' => "use {$baseModel};", + 'extends Model' => "extends {$baseModelClass}", + ]; + + $stub = str_replace( + array_keys($replacements), + array_values($replacements), + $stub + ); + + $stub = $this->sortImports($stub); + } + + return $stub; + } + protected function createFactory() { $factory = Str::studly($this->argument('name')); $this->call(DomainFactoryMakeCommand::class, [ - 'name' => $factory.'Factory', + 'name' => $factory . 'Factory', '--domain' => $this->domain->dotName, '--model' => $this->qualifyClass($this->getNameInput()), ]); } - // protected function createMigration() - // { - // $table = Str::snake(Str::pluralStudly(class_basename($this->argument('name')))); + protected function createMigration() + { + $table = Str::snake(Str::pluralStudly(class_basename($this->argument('name')))); - // if ($this->option('pivot')) { - // $table = Str::singular($table); - // } + if ($this->option('pivot')) { + $table = Str::singular($table); + } - // $this->call('make:migration', [ - // 'name' => "create_{$table}_table", - // '--create' => $table, - // ]); - // } + $this->call(DomainMigrateMakeCommand::class, [ + 'name' => "create_{$table}_table", + '--domain' => $this->domain->dotName, + '--create' => $table, + ]); + } /** * Create a seeder file for the model. @@ -63,8 +106,11 @@ protected function createController() $modelName = $this->qualifyClass($this->getNameInput()); + $controllerName = "{$controller}Controller"; + $this->call(DomainControllerMakeCommand::class, array_filter([ - 'name' => "{$controller}Controller", + 'name' => $controllerName, + '--domain' => $this->domain->dotName, '--model' => $this->option('resource') || $this->option('api') ? $modelName : null, '--api' => $this->option('api'), '--requests' => $this->option('requests') || $this->option('all'), @@ -88,4 +134,52 @@ protected function createPolicy() '--model' => $this->qualifyClass($this->getNameInput()), ]); } + + protected function createBaseModelIfNeeded() + { + if (! $this->shouldCreateBaseModel()) { + return; + } + + $baseModel = config('ddd.base_model'); + + $this->warn("Base model {$baseModel} doesn't exist, generating..."); + + $domain = DomainResolver::guessDomainFromClass($baseModel); + + $name = Str::after($baseModel, $domain); + + $this->call(DomainBaseModelMakeCommand::class, [ + '--domain' => $domain, + 'name' => $name, + ]); + } + + protected function getBaseModel(): ?string + { + return config('ddd.base_model', null); + } + + protected function shouldCreateBaseModel(): bool + { + $baseModel = config('ddd.base_model'); + + // If the class exists, we don't need to create it. + if (class_exists($baseModel)) { + return false; + } + + // If the class is outside of the domain layer, we won't attempt to create it. + if (! DomainResolver::isDomainClass($baseModel)) { + return false; + } + + // At this point the class is probably a domain object, but we should + // check if the expected path exists. + if (file_exists(app()->basePath(DomainResolver::guessPathFromClass($baseModel)))) { + return false; + } + + return true; + } } diff --git a/src/Commands/DomainModelMakeLegacyCommand.php b/src/Commands/DomainModelMakeLegacyCommand.php index d649796..37c8164 100644 --- a/src/Commands/DomainModelMakeLegacyCommand.php +++ b/src/Commands/DomainModelMakeLegacyCommand.php @@ -56,7 +56,7 @@ public function handle() protected function createBaseModelIfNeeded() { - if (! $this->shouldCreateModel()) { + if (! $this->shouldCreateBaseModel()) { return; } @@ -74,7 +74,7 @@ protected function createBaseModelIfNeeded() ]); } - protected function shouldCreateModel(): bool + protected function shouldCreateBaseModel(): bool { $baseModel = config('ddd.base_model'); diff --git a/src/Commands/DomainRequestMakeCommand.php b/src/Commands/DomainRequestMakeCommand.php new file mode 100644 index 0000000..a6a1ddf --- /dev/null +++ b/src/Commands/DomainRequestMakeCommand.php @@ -0,0 +1,27 @@ +guessObjectType(); + + return Str::finish(DomainResolver::resolveRootNamespace($type), '\\'); + } +} diff --git a/src/Commands/Migration/BaseMigrateMakeCommand.php b/src/Commands/Migration/BaseMigrateMakeCommand.php new file mode 100644 index 0000000..d84ed3d --- /dev/null +++ b/src/Commands/Migration/BaseMigrateMakeCommand.php @@ -0,0 +1,37 @@ +argument('name')); + + return $name; + } +} diff --git a/src/Commands/Migration/DomainMigrateMakeCommand.php b/src/Commands/Migration/DomainMigrateMakeCommand.php new file mode 100644 index 0000000..61f5115 --- /dev/null +++ b/src/Commands/Migration/DomainMigrateMakeCommand.php @@ -0,0 +1,28 @@ +domain) { + return $this->laravel->basePath($this->domain->migrationPath); + } + + return $this->laravel->databasePath() . DIRECTORY_SEPARATOR . 'migrations'; + } +} diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index b7eea4b..73e8b56 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -3,6 +3,7 @@ namespace Lunarstorm\LaravelDDD; use Lunarstorm\LaravelDDD\Support\DomainAutoloader; +use Lunarstorm\LaravelDDD\Support\DomainMigration; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -47,15 +48,16 @@ public function configurePackage(Package $package): void Commands\DomainJobMakeCommand::class, Commands\DomainListenerMakeCommand::class, Commands\DomainMailMakeCommand::class, - // Commands\DomainMigrateMakeCommand::class, Commands\DomainNotificationMakeCommand::class, Commands\DomainObserverMakeCommand::class, Commands\DomainPolicyMakeCommand::class, Commands\DomainProviderMakeCommand::class, Commands\DomainResourceMakeCommand::class, + Commands\DomainRequestMakeCommand::class, Commands\DomainRuleMakeCommand::class, Commands\DomainScopeMakeCommand::class, Commands\DomainSeederMakeCommand::class, + Commands\Migration\DomainMigrateMakeCommand::class, ]); if (app()->version() >= 11) { @@ -64,6 +66,21 @@ public function configurePackage(Package $package): void $package->hasCommand(Commands\DomainInterfaceMakeCommand::class); $package->hasCommand(Commands\DomainTraitMakeCommand::class); } + + $this->registerDomainMigrateMakeCommand(); + } + + protected function registerDomainMigrateMakeCommand() + { + $this->app->singleton(Commands\Migration\DomainMigrateMakeCommand::class, function ($app) { + // Once we have the migration creator registered, we will create the command + // and inject the creator. The creator is responsible for the actual file + // creation of the migrations, and may be extended by these developers. + $creator = $app['migration.creator']; + $composer = $app['composer']; + + return new Commands\Migration\DomainMigrateMakeCommand($creator, $composer); + }); } public function packageBooted() @@ -71,6 +88,8 @@ public function packageBooted() $this->publishes([ $this->package->basePath('/../stubs') => resource_path("stubs/{$this->package->shortName()}"), ], "{$this->package->shortName()}-stubs"); + + $this->loadMigrationsFrom(DomainMigration::paths()); } public function packageRegistered() diff --git a/src/Support/Domain.php b/src/Support/Domain.php index 062aedc..7e8c3b8 100644 --- a/src/Support/Domain.php +++ b/src/Support/Domain.php @@ -11,6 +11,8 @@ class Domain public readonly string $path; + public readonly string $migrationPath; + public readonly string $domain; public readonly ?string $subdomain; @@ -43,7 +45,7 @@ public function __construct(string $domain, ?string $subdomain = null) $subdomain = str($subdomain)->trim('\\/')->toString(); $this->domainWithSubdomain = str($domain) - ->when($subdomain, fn ($domain) => $domain->append("\\{$subdomain}")) + ->when($subdomain, fn($domain) => $domain->append("\\{$subdomain}")) ->toString(); $this->domain = $domain; @@ -57,6 +59,8 @@ public function __construct(string $domain, ?string $subdomain = null) $this->namespace = DomainNamespaces::from($this->domain, $this->subdomain); $this->path = Path::join(DomainResolver::domainPath(), $this->domainWithSubdomain); + + $this->migrationPath = Path::join($this->path, config('ddd.namespaces.migration', 'Database/Migrations')); } protected function getDomainBasePath() @@ -79,6 +83,21 @@ public function path(?string $path = null): string return Path::join($this->path, $path); } + public function pathInApplicationLayer(?string $path = null): string + { + if (is_null($path)) { + return $this->path; + } + + $path = str($path) + ->replace(app()->getNamespace(), '') + ->replace(['\\', '/'], DIRECTORY_SEPARATOR) + ->append('.php') + ->toString(); + + return Path::join('app', $path); + } + public function relativePath(string $path = ''): string { return collect([$this->domain, $path])->filter()->implode(DIRECTORY_SEPARATOR); @@ -96,7 +115,7 @@ public function guessNamespaceFromName(string $name): string return str($name) ->before($baseName) ->trim('\\') - ->prepend(DomainResolver::domainRootNamespace().'\\'.$this->domainWithSubdomain.'\\') + ->prepend(DomainResolver::domainRootNamespace() . '\\' . $this->domainWithSubdomain . '\\') ->toString(); } @@ -108,14 +127,19 @@ public function object(string $type, string $name, bool $absolute = false): Doma default => $this->namespaceFor($type), }; - $baseName = str($name)->replace($namespace, '')->trim('\\')->toString(); + $baseName = str($name)->replace($namespace, '') + ->replace(['\\', '/'], '\\') + ->trim('\\') + ->toString(); return new DomainObject( name: $baseName, domain: $this->domain, namespace: $namespace, - fullyQualifiedName: $namespace.'\\'.$baseName, - path: $this->path($namespace.'\\'.$baseName), + fullyQualifiedName: $namespace . '\\' . $baseName, + path: DomainResolver::isApplicationLayer($type) + ? $this->pathInApplicationLayer($namespace . '\\' . $baseName) + : $this->path($namespace . '\\' . $baseName), type: $type ); } diff --git a/src/Support/DomainAutoloader.php b/src/Support/DomainAutoloader.php index beae39e..e125286 100644 --- a/src/Support/DomainAutoloader.php +++ b/src/Support/DomainAutoloader.php @@ -9,6 +9,7 @@ use Illuminate\Foundation\Application; use Illuminate\Support\Arr; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; @@ -50,7 +51,7 @@ public function autoload(): void protected static function normalizePaths($path): array { return collect($path) - ->filter(fn ($path) => is_dir($path)) + ->filter(fn($path) => is_dir($path)) ->toArray(); } @@ -99,10 +100,10 @@ protected function handlePolicies(): void return Arr::wrap(Collection::times(count($classDirnameSegments), function ($index) use ($class, $classDirnameSegments) { $classDirname = implode('\\', array_slice($classDirnameSegments, 0, $index)); - return $classDirname.'\\Policies\\'.class_basename($class).'Policy'; + return $classDirname . '\\Policies\\' . class_basename($class) . 'Policy'; })->reverse()->values()->first(function ($class) { return class_exists($class); - }) ?: [$classDirname.'\\Policies\\'.class_basename($class).'Policy']); + }) ?: [$classDirname . '\\Policies\\' . class_basename($class) . 'Policy']); }); } @@ -115,11 +116,11 @@ protected function handleFactories(): void $appNamespace = static::appNamespace(); - $modelName = Str::startsWith($modelName, $appNamespace.'Models\\') - ? Str::after($modelName, $appNamespace.'Models\\') + $modelName = Str::startsWith($modelName, $appNamespace . 'Models\\') + ? Str::after($modelName, $appNamespace . 'Models\\') : Str::after($modelName, $appNamespace); - return 'Database\\Factories\\'.$modelName.'Factory'; + return 'Database\\Factories\\' . $modelName . 'Factory'; }); } @@ -132,7 +133,7 @@ protected static function finder($paths) ->finish('/'); $ignoredFolders = collect(config('ddd.autoload_ignore', [])) - ->map(fn ($path) => Str::finish($path, '/')); + ->map(fn($path) => Str::finish($path, '/')); if ($pathAfterDomain->startsWith($ignoredFolders)) { return false; @@ -154,7 +155,9 @@ protected static function discoverProviders(): array } $paths = static::normalizePaths( - $configValue === true ? app()->basePath(DomainResolver::domainPath()) : $configValue + $configValue === true + ? app()->basePath(DomainResolver::domainPath()) + : $configValue ); if (empty($paths)) { diff --git a/src/Support/DomainMigration.php b/src/Support/DomainMigration.php new file mode 100644 index 0000000..d81f6fd --- /dev/null +++ b/src/Support/DomainMigration.php @@ -0,0 +1,95 @@ +filter(fn($path) => is_dir($path)) + ->toArray(); + } + + public static function discoverPaths(): array + { + $configValue = config('ddd.autoload.migrations', true); + + if ($configValue === false) { + return []; + } + + $paths = static::normalizePaths([ + app()->basePath(DomainResolver::domainPath()), + ]); + + if (empty($paths)) { + return []; + } + + $finder = static::finder($paths); + + return Lody::filesFromFinder($finder) + ->map(fn($file) => $file->getPath()) + ->unique() + ->values() + ->toArray(); + } + + protected static function finder(array $paths) + { + $filter = function (SplFileInfo $file) { + $configuredMigrationFolder = static::domainMigrationFolder(); + + $relativePath = Path::normalize($file->getRelativePath()); + + return Str::endsWith($relativePath, $configuredMigrationFolder); + }; + + return Finder::create() + ->files() + ->in($paths) + ->filter($filter); + } +} diff --git a/src/Support/DomainResolver.php b/src/Support/DomainResolver.php index 9d52db8..8b1d998 100644 --- a/src/Support/DomainResolver.php +++ b/src/Support/DomainResolver.php @@ -2,6 +2,7 @@ namespace Lunarstorm\LaravelDDD\Support; +use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Str; class DomainResolver @@ -11,10 +12,10 @@ class DomainResolver */ public static function domainChoices(): array { - $folders = glob(app()->basePath(static::domainPath().'/*'), GLOB_ONLYDIR); + $folders = glob(app()->basePath(static::domainPath() . '/*'), GLOB_ONLYDIR); return collect($folders) - ->map(fn ($path) => basename($path)) + ->map(fn($path) => basename($path)) ->sort() ->toArray(); } @@ -35,6 +36,11 @@ public static function domainRootNamespace(): ?string return config('ddd.domain_namespace'); } + public static function applicationLayerRootNamespace(): ?string + { + return config('ddd.application_layer.namespace', 'App\Modules'); + } + /** * Resolve the relative domain object namespace. * @@ -45,10 +51,40 @@ public static function getRelativeObjectNamespace(string $type): string return config("ddd.namespaces.{$type}", str($type)->plural()->studly()->toString()); } + public static function isApplicationLayer(string $type): bool + { + $applicationObjects = config('ddd.application_layer.objects', ['controller', 'request']); + + return in_array($type, $applicationObjects); + } + + // public static function getDomainControllerNamespace(string $domain, ?string $controller = null): string + // { + // $controllerRootNamespace = app()->getNamespace() . 'Http\Controllers'; + + // $namespace = collect([ + // $controllerRootNamespace, + // $domain, + // ])->filter()->implode('\\'); + + // if ($controller) { + // $namespace .= "\\{$controller}"; + // } + + // return $namespace; + // } + + public static function resolveRootNamespace(string $type): ?string + { + return static::isApplicationLayer($type) + ? static::applicationLayerRootNamespace() + : static::domainRootNamespace(); + } + public static function getDomainObjectNamespace(string $domain, string $type, ?string $object = null): string { $namespace = collect([ - static::domainRootNamespace(), + static::resolveRootNamespace($type), $domain, static::getRelativeObjectNamespace($type), ])->filter()->implode('\\'); @@ -95,6 +131,22 @@ public static function guessPathFromClass(string $class): ?string return Path::join(...[static::domainPath(), "{$classWithoutDomainRoot}.php"]); } + /** + * Attempt to resolve the folder of a given domain class. + */ + public static function guessFolderFromClass(string $class): ?string + { + $path = static::guessPathFromClass($class); + + if (! $path) { + return null; + } + + $filenamePortion = basename($path); + + return Str::beforeLast($path, $filenamePortion); + } + /** * Determine whether a class is an object within the domain layer. * diff --git a/src/Support/Path.php b/src/Support/Path.php index 931230c..7fbdb86 100644 --- a/src/Support/Path.php +++ b/src/Support/Path.php @@ -26,4 +26,9 @@ public static function filePathToNamespace(string $path, string $namespacePath, $path ); } + + public static function normalizeNamespace(string $namespace): string + { + return str_replace(['\\', '/'], '\\', $namespace); + } } diff --git a/tests/Generator/MakeActionTest.php b/tests/Generator/ActionMakeTest.php similarity index 100% rename from tests/Generator/MakeActionTest.php rename to tests/Generator/ActionMakeTest.php diff --git a/tests/Generator/MakeBaseModelTest.php b/tests/Generator/BaseModelMakeTest.php similarity index 100% rename from tests/Generator/MakeBaseModelTest.php rename to tests/Generator/BaseModelMakeTest.php diff --git a/tests/Generator/MakeBaseViewModelTest.php b/tests/Generator/BaseViewModelMakeTest.php similarity index 100% rename from tests/Generator/MakeBaseViewModelTest.php rename to tests/Generator/BaseViewModelMakeTest.php diff --git a/tests/Generator/ControllerMakeTest.php b/tests/Generator/ControllerMakeTest.php new file mode 100644 index 0000000..af3e8e5 --- /dev/null +++ b/tests/Generator/ControllerMakeTest.php @@ -0,0 +1,179 @@ + 'app/Modules', + 'namespace' => 'App\Modules', + 'objects' => ['controller', 'request'], + ]); + + $this->setupTestApplication(); +}); + +it('can generate domain controller', function ($domainName, $controllerName, $relativePath, $expectedNamespace) { + $expectedPath = base_path($relativePath); + + if (file_exists($expectedPath)) { + unlink($expectedPath); + } + + expect(file_exists($expectedPath))->toBeFalse(); + + Artisan::call("ddd:controller {$domainName}:{$controllerName}"); + + expect($output = Artisan::output())->when( + Feature::IncludeFilepathInGeneratorCommandOutput->exists(), + fn($output) => $output->toContainFilepath($relativePath), + ); + + expect(file_exists($expectedPath))->toBeTrue(); + + expect(file_get_contents($expectedPath)) + ->toContain("namespace {$expectedNamespace};"); +})->with([ + 'Invoicing:InvoiceController' => [ + 'Invoicing', + 'InvoiceController', + 'app/Modules/Invoicing/Controllers/InvoiceController.php', + 'App\Modules\Invoicing\Controllers', + ], + + 'Reporting.Internal:ReportSubmissionController' => [ + 'Reporting.Internal', + 'ReportSubmissionController', + 'app/Modules/Reporting/Internal/Controllers/ReportSubmissionController.php', + 'App\Modules\Reporting\Internal\Controllers', + ], +]); + +it('can generate domain resource controller from model', function ($domainName, $controllerName, $relativePath, $modelName, $modelClass) { + $expectedPath = base_path($relativePath); + + if (file_exists($expectedPath)) { + unlink($expectedPath); + } + + expect(file_exists($expectedPath))->toBeFalse(); + + Artisan::call("ddd:controller",[ + 'name' => $controllerName, + '--domain' => $domainName, + '--model' => $modelName, + ]); + + expect(file_exists($expectedPath))->toBeTrue(); + + $modelVariable = lcfirst(class_basename($modelClass)); + + expect(file_get_contents($expectedPath)) + ->toContain("use {$modelClass};") + ->toContain("{$modelName} \${$modelVariable})"); +})->with([ + 'Invoicing:InvoiceController --model=Invoice' => [ + 'Invoicing', + 'InvoiceController', + 'app/Modules/Invoicing/Controllers/InvoiceController.php', + 'Invoice', + 'Domain\Invoicing\Models\Invoice', + ], + + 'Invoicing:Payment/InvoicePaymentController --model=InvoicePayment' => [ + 'Invoicing', + 'Payment/InvoicePaymentController', + 'app/Modules/Invoicing/Controllers/Payment/InvoicePaymentController.php', + 'InvoicePayment', + 'Domain\Invoicing\Models\InvoicePayment', + ], + + 'Reporting.Internal:Archived/ReportArchiveController --model=ReportArchive' => [ + 'Reporting.Internal', + 'Archived/ReportArchiveController', + 'app/Modules/Reporting/Internal/Controllers/Archived/ReportArchiveController.php', + 'ReportArchive', + 'Domain\Reporting\Internal\Models\ReportArchive', + ], +]); + +it('can generate domain controller with requests', function ($domainName, $controllerName, $controllerPath, $modelName, $modelClass, $generatedPaths) { + $generatedPaths = [ + $controllerPath, + ...$generatedPaths, + ]; + + foreach ($generatedPaths as $path) { + $path = base_path($path); + + if (file_exists($path)) { + unlink($path); + } + } + + Artisan::call("ddd:controller", [ + 'name' => $controllerName, + '--domain' => $domainName, + '--model' => $modelName, + '--requests' => true, + ]); + + $output = Artisan::output(); + + foreach ($generatedPaths as $path) { + if(Feature::IncludeFilepathInGeneratorCommandOutput->exists()){ + expect($output)->toContainFilepath($path); + } + + expect(file_exists(base_path($path)))->toBeTrue("Expecting {$path} to exist"); + } + + $modelVariable = lcfirst(class_basename($modelClass)); + + expect(file_get_contents(base_path($controllerPath))) + ->toContain("use {$modelClass};") + ->toContain("store(Store{$modelName}Request \$request)") + ->toContain("update(Update{$modelName}Request \$request, {$modelName} \${$modelVariable})"); +})->with([ + 'Invoicing:InvoiceController --model=Invoice' => [ + 'Invoicing', + 'InvoiceController', + 'app/Modules/Invoicing/Controllers/InvoiceController.php', + 'Invoice', + 'Domain\Invoicing\Models\Invoice', + [ + 'app/Modules/Invoicing/Requests/StoreInvoiceRequest.php', + 'app/Modules/Invoicing/Requests/UpdateInvoiceRequest.php', + ], + ], + + 'Invoicing:Payment/InvoicePaymentController --model=InvoicePayment' => [ + 'Invoicing', + 'Payment/InvoicePaymentController', + 'app/Modules/Invoicing/Controllers/Payment/InvoicePaymentController.php', + 'InvoicePayment', + 'Domain\Invoicing\Models\InvoicePayment', + [ + 'app/Modules/Invoicing/Requests/Payment/StoreInvoicePaymentRequest.php', + 'app/Modules/Invoicing/Requests/Payment/UpdateInvoicePaymentRequest.php', + ], + ], + + 'Reporting.Internal:Archived/ReportArchiveController --model=ReportArchive' => [ + 'Reporting.Internal', + 'Archived/ReportArchiveController', + 'app/Modules/Reporting/Internal/Controllers/Archived/ReportArchiveController.php', + 'ReportArchive', + 'Domain\Reporting\Internal\Models\ReportArchive', + [ + 'app/Modules/Reporting/Internal/Requests/Archived/StoreReportArchiveRequest.php', + 'app/Modules/Reporting/Internal/Requests/Archived/UpdateReportArchiveRequest.php', + ], + ], +]); diff --git a/tests/Generator/MakeDataTransferObjectTest.php b/tests/Generator/DtoMakeTestTest.php similarity index 100% rename from tests/Generator/MakeDataTransferObjectTest.php rename to tests/Generator/DtoMakeTestTest.php diff --git a/tests/Generator/ExtendedCommandsTest.php b/tests/Generator/ExtendedCommandsTest.php index 4c0819f..fbba5ed 100644 --- a/tests/Generator/ExtendedCommandsTest.php +++ b/tests/Generator/ExtendedCommandsTest.php @@ -38,6 +38,7 @@ 'cast' => ['cast', 'SomeCast'], 'channel' => ['channel', 'SomeChannel'], 'command' => ['command', 'SomeCommand'], + 'controller' => ['controller', 'SomeController'], 'event' => ['event', 'SomeEvent'], 'exception' => ['exception', 'SomeException'], 'job' => ['job', 'SomeJob'], @@ -48,8 +49,10 @@ 'policy' => ['policy', 'SomePolicy'], 'provider' => ['provider', 'SomeProvider'], 'resource' => ['resource', 'SomeResource'], + 'request' => ['request', 'SomeRequest'], 'rule' => ['rule', 'SomeRule'], 'scope' => ['scope', 'SomeScope'], + 'seeder' => ['seeder', 'SomeSeeder'], 'class' => ['class', 'SomeClass'], 'enum' => ['enum', 'SomeEnum'], 'interface' => ['interface', 'SomeInterface'], diff --git a/tests/Generator/MakeFactoryTest.php b/tests/Generator/FactoryMakeTest.php similarity index 100% rename from tests/Generator/MakeFactoryTest.php rename to tests/Generator/FactoryMakeTest.php diff --git a/tests/Generator/MigrationMakeTest.php b/tests/Generator/MigrationMakeTest.php new file mode 100644 index 0000000..fdf285e --- /dev/null +++ b/tests/Generator/MigrationMakeTest.php @@ -0,0 +1,88 @@ + true, + ]); + + DomainCache::clear(); +}); + +it('can generate domain migrations', function ($domainPath, $domainRoot) { + Config::set('ddd.domain_path', $domainPath); + Config::set('ddd.domain_namespace', $domainRoot); + + $domain = 'Invoicing'; + + $relativePath = implode('/', [ + $domainPath, + $domain, + config('ddd.namespaces.migration'), + ]); + + $migrationFolder = base_path(Path::normalize($relativePath)); + + $filesBefore = glob("{$migrationFolder}/*"); + + expect(count($filesBefore))->toBe(0); + + Artisan::call("ddd:migration {$domain}:CreateInvoicesTable"); + + expect($output = Artisan::output())->when( + Feature::IncludeFilepathInGeneratorCommandOutput->exists(), + fn($output) => $output + ->toContainFilepath($relativePath) + ->toContain('_create_invoices_table.php'), + ); + + $filesAfter = glob("{$migrationFolder}/*"); + + $createdMigrationFile = Arr::last($filesAfter); + + expect($createdMigrationFile)->toEndWith('_create_invoices_table.php'); + + expect(file_get_contents($createdMigrationFile)) + ->toContain("return new class extends Migration"); +})->with('domainPaths'); + +it('discovers domain migration folders', function ($domainPath, $domainRoot) { + Config::set('ddd.domain_path', $domainPath); + Config::set('ddd.domain_namespace', $domainRoot); + + $discoveredPaths = DomainMigration::discoverPaths(); + + expect($discoveredPaths)->toHaveCount(0); + + Artisan::call("ddd:migration Invoicing:" . uniqid('migration')); + Artisan::call("ddd:migration Shared:" . uniqid('migration')); + Artisan::call("ddd:migration Reporting:" . uniqid('migration')); + Artisan::call("ddd:migration Reporting:" . uniqid('migration')); + Artisan::call("ddd:migration Reporting:" . uniqid('migration')); + + + $discoveredPaths = DomainMigration::discoverPaths(); + + expect($discoveredPaths)->toHaveCount(3); + + $expectedFolderPatterns = [ + 'Invoicing/Database/Migrations', + 'Shared/Database/Migrations', + 'Reporting/Database/Migrations', + ]; + + foreach ($discoveredPaths as $path) { + expect(str($path)->contains($expectedFolderPatterns)) + ->toBeTrue("Expecting path to contain one of the expected folder patterns"); + } +})->with('domainPaths'); diff --git a/tests/Generator/Model/MakeTest.php b/tests/Generator/Model/MakeTest.php index 8772f20..4f4bce1 100644 --- a/tests/Generator/Model/MakeTest.php +++ b/tests/Generator/Model/MakeTest.php @@ -31,7 +31,7 @@ expect(Artisan::output())->when( Feature::IncludeFilepathInGeneratorCommandOutput->exists(), - fn ($output) => $output->toContainFilepath($relativePath), + fn($output) => $output->toContainFilepath($relativePath), ); expect(file_exists($expectedModelPath))->toBeTrue(); @@ -179,3 +179,34 @@ ['Illuminate\Database\Eloquent\Model'], ['Lunarstorm\LaravelDDD\Models\DomainModel'], ]); + +it('extends custom base models when applicable', function ($baseModelClass, $baseModelName) { + Config::set('ddd.base_model', $baseModelClass); + + $domain = 'Fruits'; + $modelName = 'Lemon'; + + $expectedModelPath = base_path(implode('/', [ + config('ddd.domain_path'), + $domain, + config('ddd.namespaces.model'), + "{$modelName}.php", + ])); + + if (file_exists($expectedModelPath)) { + unlink($expectedModelPath); + } + + Artisan::call("ddd:model {$domain}:{$modelName}"); + + expect(file_exists($expectedModelPath))->toBeTrue(); + + expect(file_get_contents($expectedModelPath)) + ->toContain("use {$baseModelClass};") + ->toContain("extends {$baseModelName}"); +})->with([ + ['Domain\Shared\Models\BaseModel', 'BaseModel'], + ['Lunarstorm\LaravelDDD\Models\DomainModel', 'DomainModel'], + ['Illuminate\Database\Eloquent\NonExistentModel', 'NonExistentModel'], + ['OtherVendor\OtherPackage\Models\NonExistentModel', 'NonExistentModel'], +]); diff --git a/tests/Generator/Model/MakeWithControllerTest.php b/tests/Generator/Model/MakeWithControllerTest.php new file mode 100644 index 0000000..52d68fb --- /dev/null +++ b/tests/Generator/Model/MakeWithControllerTest.php @@ -0,0 +1,91 @@ +setupTestApplication(); +}); + +it('can generate domain model with controller', function ($domainName, $modelName, $controllerName, $generatedPaths) { + $domain = new Domain($domainName); + + foreach ($generatedPaths as $path) { + $path = base_path($path); + + if (file_exists($path)) { + unlink($path); + } + } + + $command = [ + 'ddd:model', + [ + 'name' => $modelName, + '--domain' => $domain->dotName, + '--controller' => true, + ], + ]; + + Artisan::call(...$command); + + $output = Artisan::output(); + + foreach ($generatedPaths as $path) { + if (Feature::IncludeFilepathInGeneratorCommandOutput->exists()) { + expect($output)->toContainFilepath($path); + } + + expect(file_exists(base_path($path)))->toBeTrue("Expecting {$path} to exist"); + } +})->with([ + 'Invoicing:Record' => [ + 'Invoicing', + 'Record', + 'RecordController', + [ + 'src/Domain/Invoicing/Models/Record.php', + 'app/Modules/Invoicing/Controllers/RecordController.php' + ], + ], + + 'Invoicing:RecordEntry' => [ + 'Invoicing', + 'RecordEntry', + 'RecordEntryController', + [ + 'src/Domain/Invoicing/Models/RecordEntry.php', + 'app/Modules/Invoicing/Controllers/RecordEntryController.php' + ], + ], + + 'Reporting.Internal:ReportSubmission' => [ + 'Reporting.Internal', + 'ReportSubmission', + 'ReportSubmissionController', + [ + 'src/Domain/Reporting/Internal/Models/ReportSubmission.php', + 'app/Modules/Reporting/Internal/Controllers/ReportSubmissionController.php' + ], + ], + + // '--controller --api' => [ + // ['--controller' => true, '--api' => true], + // 'RecordController', + // 'app/Http/Controllers/Invoicing/RecordController.php', + // ], + + // '--controller --requests' => [ + // ['--controller' => true, '--requests' => true], + // 'RecordController', + // 'app/Http/Controllers/Invoicing/RecordController.php', + // ], +]); diff --git a/tests/Generator/Model/OptionsTest.php b/tests/Generator/Model/MakeWithOptionsTest.php similarity index 58% rename from tests/Generator/Model/OptionsTest.php rename to tests/Generator/Model/MakeWithOptionsTest.php index c6a19c7..09cb201 100644 --- a/tests/Generator/Model/OptionsTest.php +++ b/tests/Generator/Model/MakeWithOptionsTest.php @@ -11,7 +11,7 @@ }); // it('can generate a domain model with factory', function () { -// $domainName = 'World'; +// $domainName = 'Invoicing'; // $modelName = 'Record'; // $domain = new Domain($domainName); @@ -53,7 +53,7 @@ // }); it('can generate domain model with options', function ($options, $objectType, $objectName, $expectedObjectPath) { - $domainName = 'World'; + $domainName = 'Invoicing'; $modelName = 'Record'; $domain = new Domain($domainName); @@ -90,85 +90,20 @@ ['--factory' => true], 'factory', 'RecordFactory', - 'src/Domain/World/Database/Factories/RecordFactory.php', + 'src/Domain/Invoicing/Database/Factories/RecordFactory.php', ], '--seed' => [ ['--seed' => true], 'seeder', 'RecordSeeder', - 'src/Domain/World/Database/Seeders/RecordSeeder.php', + 'src/Domain/Invoicing/Database/Seeders/RecordSeeder.php', ], '--policy' => [ ['--policy' => true], 'policy', 'RecordPolicy', - 'src/Domain/World/Policies/RecordPolicy.php', - ], -]); - -it('can generate domain model with controller', function ($options, $objectType, $objectName, $expectedObjectPath) { - $domainName = 'World'; - $modelName = 'Record'; - - $domain = new Domain($domainName); - - $domainModel = $domain->model($modelName); - - $expectedModelPath = base_path($domainModel->path); - - if (file_exists($expectedModelPath)) { - unlink($expectedModelPath); - } - - if (file_exists($expectedObjectPath)) { - unlink($expectedObjectPath); - } - - $command = [ - 'ddd:model', [ - 'name' => $modelName, - '--domain' => $domain->dotName, - ...$options, - ], - ]; - - $this->artisan(...$command) - ->expectsOutputToContain(Path::normalize($domainModel->path)) - ->assertExitCode(0); - - // $output = Artisan::output(); - - // $outputPath = Path::normalize($domainModel->path); - - // expect($output)->toContainFilepath($domainModel->path); - - $path = base_path($expectedObjectPath); - - expect(file_exists($path))->toBeTrue("Expecting {$objectType} to be generated at {$path}"); - - $contents = file_get_contents($path); - dump($contents); -})->with([ - '--controller' => [ - ['--controller' => true], - 'controller', - 'RecordController', - 'app/Http/Controllers/RecordController.php', - ], - - '--controller --api' => [ - ['--controller' => true, '--api' => true], - 'controller', - 'RecordController', - 'app/Http/Controllers/RecordController.php', - ], - - '--controller --requests' => [ - ['--controller' => true, '--requests' => true], - 'controller', - 'RecordController', - 'app/Http/Controllers/RecordController.php', + 'src/Domain/Invoicing/Policies/RecordPolicy.php', ], ]); diff --git a/tests/Generator/PromptTest.php b/tests/Generator/PromptTest.php index 3dea905..53bb086 100644 --- a/tests/Generator/PromptTest.php +++ b/tests/Generator/PromptTest.php @@ -23,6 +23,7 @@ it('[model] prompts for missing input', function () { $this->artisan('ddd:model') ->expectsQuestion('What should the model be named?', 'Belt') + ->expectsQuestion('Would you like any of the following?', []) ->expectsQuestion('What is the domain?', 'Utility') ->assertExitCode(0); }); diff --git a/tests/Generator/RequestMakeTest.php b/tests/Generator/RequestMakeTest.php new file mode 100644 index 0000000..9d2ad9a --- /dev/null +++ b/tests/Generator/RequestMakeTest.php @@ -0,0 +1,54 @@ + 'app/Modules', + 'namespace' => 'App\Modules', + 'objects' => ['controller', 'request'], + ]); + + $this->setupTestApplication(); +}); + +it('can generate domain request', function ($domainName, $requestName, $relativePath, $expectedNamespace) { + $expectedPath = base_path($relativePath); + + if (file_exists($expectedPath)) { + unlink($expectedPath); + } + + expect(file_exists($expectedPath))->toBeFalse(); + + Artisan::call("ddd:request {$domainName}:{$requestName}"); + + expect($output = Artisan::output())->when( + Feature::IncludeFilepathInGeneratorCommandOutput->exists(), + fn($output) => $output->toContainFilepath($relativePath), + ); + + expect(file_exists($expectedPath))->toBeTrue(); + + expect(file_get_contents($expectedPath)) + ->toContain("namespace {$expectedNamespace};"); +})->with([ + 'Invoicing:StoreInvoiceRequest' => [ + 'Invoicing', + 'StoreInvoiceRequest', + 'app/Modules/Invoicing/Requests/StoreInvoiceRequest.php', + 'App\Modules\Invoicing\Requests', + ], + + 'Reporting.Internal:UpdateReportRequest' => [ + 'Reporting.Internal', + 'UpdateReportRequest', + 'app/Modules/Reporting/Internal/Requests/UpdateReportRequest.php', + 'App\Modules\Reporting\Internal\Requests', + ], +]); diff --git a/tests/Generator/MakeValueObjectTest.php b/tests/Generator/ValueObjectMakeTest.php similarity index 100% rename from tests/Generator/MakeValueObjectTest.php rename to tests/Generator/ValueObjectMakeTest.php diff --git a/tests/Generator/MakeViewModelTest.php b/tests/Generator/ViewModelMakeTest.php similarity index 100% rename from tests/Generator/MakeViewModelTest.php rename to tests/Generator/ViewModelMakeTest.php diff --git a/tests/Support/DomainTest.php b/tests/Support/DomainTest.php index e263500..d2398d1 100644 --- a/tests/Support/DomainTest.php +++ b/tests/Support/DomainTest.php @@ -1,5 +1,6 @@ 'app/Modules', + 'namespace' => 'App\Modules', + 'objects' => ['controller', 'request'], + ]); + }); + + it('can describe objects in the application layer', function ($domainName, $objectType, $objectName, $expectedFQN, $expectedPath) { + expect((new Domain($domainName))->object($objectType, $objectName)) + ->name->toBe($objectName) + ->fullyQualifiedName->toBe($expectedFQN) + ->path->toBe(Path::normalize($expectedPath)); + })->with([ + ['Invoicing', 'controller', 'InvoiceController', 'App\\Modules\\Invoicing\\Controllers\\InvoiceController', 'app/Modules/Invoicing/Controllers/InvoiceController.php'], + ['Invoicing', 'controller', 'Nested\\InvoiceController', 'App\\Modules\\Invoicing\\Controllers\\Nested\\InvoiceController', 'app/Modules/Invoicing/Controllers/Nested/InvoiceController.php'], + ['Invoicing', 'request', 'StoreInvoiceRequest', 'App\\Modules\\Invoicing\\Requests\\StoreInvoiceRequest', 'app/Modules/Invoicing/Requests/StoreInvoiceRequest.php'], + ['Invoicing', 'request', 'Nested\\StoreInvoiceRequest', 'App\\Modules\\Invoicing\\Requests\\Nested\\StoreInvoiceRequest', 'app/Modules/Invoicing/Requests/Nested/StoreInvoiceRequest.php'], + ]); +}); + +it('normalizes slashes in nested objects', function ($nameInput, $normalized) { + expect((new Domain('Invoicing'))->object('class', $nameInput)) + ->name->toBe($normalized); +})->with([ + ['Nested\\Thing', 'Nested\\Thing'], + ['Nested/Thing', 'Nested\\Thing'], + ['Nested/Thing/Deeply', 'Nested\\Thing\\Deeply'], + ['Nested\\Thing/Deeply', 'Nested\\Thing\\Deeply'], +]); From c55cd08a9a13289a4fd42603f434c616656775c4 Mon Sep 17 00:00:00 2001 From: JasperTey Date: Mon, 14 Oct 2024 14:40:49 +0000 Subject: [PATCH 007/169] Fix styling --- config/ddd.php | 14 ++++++------- .../Concerns/QualifiesDomainModels.php | 2 +- .../Concerns/ResolvesDomainFromInput.php | 4 ++-- src/Commands/DomainControllerMakeCommand.php | 21 ++++++++----------- src/Commands/DomainModelMakeCommand.php | 2 +- .../Migration/BaseMigrateMakeCommand.php | 1 - .../Migration/DomainMigrateMakeCommand.php | 5 ++--- src/Support/Domain.php | 10 ++++----- src/Support/DomainAutoloader.php | 15 +++++++------ src/Support/DomainMigration.php | 18 ++-------------- src/Support/DomainResolver.php | 5 ++--- tests/Generator/ControllerMakeTest.php | 10 ++++----- tests/Generator/MigrationMakeTest.php | 20 +++++++----------- tests/Generator/Model/MakeTest.php | 2 +- .../Model/MakeWithControllerTest.php | 7 +++---- tests/Generator/RequestMakeTest.php | 2 +- tests/Support/DomainTest.php | 2 +- 17 files changed, 56 insertions(+), 84 deletions(-) diff --git a/config/ddd.php b/config/ddd.php index dda2320..030498f 100644 --- a/config/ddd.php +++ b/config/ddd.php @@ -70,14 +70,14 @@ 'trait' => '', ], -'application_layer' => [ - 'path' => 'app/Modules', - 'namespace' => 'App\Modules', - 'objects' => [ - 'controller', - 'request', + 'application_layer' => [ + 'path' => 'app/Modules', + 'namespace' => 'App\Modules', + 'objects' => [ + 'controller', + 'request', + ], ], -], /* |-------------------------------------------------------------------------- diff --git a/src/Commands/Concerns/QualifiesDomainModels.php b/src/Commands/Concerns/QualifiesDomainModels.php index 339f58f..92ad4ed 100644 --- a/src/Commands/Concerns/QualifiesDomainModels.php +++ b/src/Commands/Concerns/QualifiesDomainModels.php @@ -26,7 +26,7 @@ protected function qualifyClass($name) protected function qualifyModel(string $model) { - if($domain = $this->domain) { + if ($domain = $this->domain) { $domainModel = $domain->model($model); return $domainModel->fullyQualifiedName; diff --git a/src/Commands/Concerns/ResolvesDomainFromInput.php b/src/Commands/Concerns/ResolvesDomainFromInput.php index 2f11895..bd65dd8 100644 --- a/src/Commands/Concerns/ResolvesDomainFromInput.php +++ b/src/Commands/Concerns/ResolvesDomainFromInput.php @@ -11,8 +11,8 @@ trait ResolvesDomainFromInput { use CanPromptForDomain, - QualifiesDomainModels, - HandleHooks; + HandleHooks, + QualifiesDomainModels; protected $nameIsAbsolute = false; diff --git a/src/Commands/DomainControllerMakeCommand.php b/src/Commands/DomainControllerMakeCommand.php index dc628e6..751a303 100644 --- a/src/Commands/DomainControllerMakeCommand.php +++ b/src/Commands/DomainControllerMakeCommand.php @@ -3,18 +3,15 @@ namespace Lunarstorm\LaravelDDD\Commands; use Illuminate\Routing\Console\ControllerMakeCommand; -use Illuminate\Support\Str; use Lunarstorm\LaravelDDD\Commands\Concerns\CallsDomainCommands; use Lunarstorm\LaravelDDD\Commands\Concerns\ResolvesDomainFromInput; -use Lunarstorm\LaravelDDD\Support\DomainResolver; -use Lunarstorm\LaravelDDD\Support\Path; use function Laravel\Prompts\confirm; class DomainControllerMakeCommand extends ControllerMakeCommand { - use ResolvesDomainFromInput, - CallsDomainCommands; + use CallsDomainCommands, + ResolvesDomainFromInput; protected $name = 'ddd:controller'; @@ -23,7 +20,7 @@ protected function buildModelReplacements(array $replace) $modelClass = $this->parseModel($this->option('model')); if ( - !app()->runningUnitTests() + ! app()->runningUnitTests() && ! class_exists($modelClass) && confirm("A {$modelClass} model does not exist. Do you want to generate it?", default: true) ) { @@ -63,10 +60,10 @@ protected function buildFormRequestReplacements(array $replace, $modelClass) ); } - $namespacedRequests = $namespace . '\\' . $storeRequestClass . ';'; + $namespacedRequests = $namespace.'\\'.$storeRequestClass.';'; if ($storeRequestClass !== $updateRequestClass) { - $namespacedRequests .= PHP_EOL . 'use ' . $namespace . '\\' . $updateRequestClass . ';'; + $namespacedRequests .= PHP_EOL.'use '.$namespace.'\\'.$updateRequestClass.';'; } return array_merge($replace, [ @@ -74,10 +71,10 @@ protected function buildFormRequestReplacements(array $replace, $modelClass) '{{storeRequest}}' => $storeRequestClass, '{{ updateRequest }}' => $updateRequestClass, '{{updateRequest}}' => $updateRequestClass, - '{{ namespacedStoreRequest }}' => $namespace . '\\' . $storeRequestClass, - '{{namespacedStoreRequest}}' => $namespace . '\\' . $storeRequestClass, - '{{ namespacedUpdateRequest }}' => $namespace . '\\' . $updateRequestClass, - '{{namespacedUpdateRequest}}' => $namespace . '\\' . $updateRequestClass, + '{{ namespacedStoreRequest }}' => $namespace.'\\'.$storeRequestClass, + '{{namespacedStoreRequest}}' => $namespace.'\\'.$storeRequestClass, + '{{ namespacedUpdateRequest }}' => $namespace.'\\'.$updateRequestClass, + '{{namespacedUpdateRequest}}' => $namespace.'\\'.$updateRequestClass, '{{ namespacedRequests }}' => $namespacedRequests, '{{namespacedRequests}}' => $namespacedRequests, ]); diff --git a/src/Commands/DomainModelMakeCommand.php b/src/Commands/DomainModelMakeCommand.php index 277cb97..9f798e0 100644 --- a/src/Commands/DomainModelMakeCommand.php +++ b/src/Commands/DomainModelMakeCommand.php @@ -59,7 +59,7 @@ protected function createFactory() $factory = Str::studly($this->argument('name')); $this->call(DomainFactoryMakeCommand::class, [ - 'name' => $factory . 'Factory', + 'name' => $factory.'Factory', '--domain' => $this->domain->dotName, '--model' => $this->qualifyClass($this->getNameInput()), ]); diff --git a/src/Commands/Migration/BaseMigrateMakeCommand.php b/src/Commands/Migration/BaseMigrateMakeCommand.php index d84ed3d..789192f 100644 --- a/src/Commands/Migration/BaseMigrateMakeCommand.php +++ b/src/Commands/Migration/BaseMigrateMakeCommand.php @@ -3,7 +3,6 @@ namespace Lunarstorm\LaravelDDD\Commands\Migration; use Illuminate\Database\Console\Migrations\MigrateMakeCommand; -use Lunarstorm\LaravelDDD\Commands\Concerns\ResolvesDomainFromInput; use Symfony\Component\Console\Input\InputOption; class BaseMigrateMakeCommand extends MigrateMakeCommand diff --git a/src/Commands/Migration/DomainMigrateMakeCommand.php b/src/Commands/Migration/DomainMigrateMakeCommand.php index 61f5115..e0dcb73 100644 --- a/src/Commands/Migration/DomainMigrateMakeCommand.php +++ b/src/Commands/Migration/DomainMigrateMakeCommand.php @@ -2,9 +2,8 @@ namespace Lunarstorm\LaravelDDD\Commands\Migration; -use Lunarstorm\LaravelDDD\Support\Path; use Lunarstorm\LaravelDDD\Commands\Concerns\ResolvesDomainFromInput; -use Lunarstorm\LaravelDDD\Commands\Migration\BaseMigrateMakeCommand; +use Lunarstorm\LaravelDDD\Support\Path; class DomainMigrateMakeCommand extends BaseMigrateMakeCommand { @@ -23,6 +22,6 @@ protected function getMigrationPath() return $this->laravel->basePath($this->domain->migrationPath); } - return $this->laravel->databasePath() . DIRECTORY_SEPARATOR . 'migrations'; + return $this->laravel->databasePath().DIRECTORY_SEPARATOR.'migrations'; } } diff --git a/src/Support/Domain.php b/src/Support/Domain.php index 7e8c3b8..eb3fd32 100644 --- a/src/Support/Domain.php +++ b/src/Support/Domain.php @@ -45,7 +45,7 @@ public function __construct(string $domain, ?string $subdomain = null) $subdomain = str($subdomain)->trim('\\/')->toString(); $this->domainWithSubdomain = str($domain) - ->when($subdomain, fn($domain) => $domain->append("\\{$subdomain}")) + ->when($subdomain, fn ($domain) => $domain->append("\\{$subdomain}")) ->toString(); $this->domain = $domain; @@ -115,7 +115,7 @@ public function guessNamespaceFromName(string $name): string return str($name) ->before($baseName) ->trim('\\') - ->prepend(DomainResolver::domainRootNamespace() . '\\' . $this->domainWithSubdomain . '\\') + ->prepend(DomainResolver::domainRootNamespace().'\\'.$this->domainWithSubdomain.'\\') ->toString(); } @@ -136,10 +136,10 @@ public function object(string $type, string $name, bool $absolute = false): Doma name: $baseName, domain: $this->domain, namespace: $namespace, - fullyQualifiedName: $namespace . '\\' . $baseName, + fullyQualifiedName: $namespace.'\\'.$baseName, path: DomainResolver::isApplicationLayer($type) - ? $this->pathInApplicationLayer($namespace . '\\' . $baseName) - : $this->path($namespace . '\\' . $baseName), + ? $this->pathInApplicationLayer($namespace.'\\'.$baseName) + : $this->path($namespace.'\\'.$baseName), type: $type ); } diff --git a/src/Support/DomainAutoloader.php b/src/Support/DomainAutoloader.php index e125286..9e812d9 100644 --- a/src/Support/DomainAutoloader.php +++ b/src/Support/DomainAutoloader.php @@ -9,7 +9,6 @@ use Illuminate\Foundation\Application; use Illuminate\Support\Arr; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; @@ -51,7 +50,7 @@ public function autoload(): void protected static function normalizePaths($path): array { return collect($path) - ->filter(fn($path) => is_dir($path)) + ->filter(fn ($path) => is_dir($path)) ->toArray(); } @@ -100,10 +99,10 @@ protected function handlePolicies(): void return Arr::wrap(Collection::times(count($classDirnameSegments), function ($index) use ($class, $classDirnameSegments) { $classDirname = implode('\\', array_slice($classDirnameSegments, 0, $index)); - return $classDirname . '\\Policies\\' . class_basename($class) . 'Policy'; + return $classDirname.'\\Policies\\'.class_basename($class).'Policy'; })->reverse()->values()->first(function ($class) { return class_exists($class); - }) ?: [$classDirname . '\\Policies\\' . class_basename($class) . 'Policy']); + }) ?: [$classDirname.'\\Policies\\'.class_basename($class).'Policy']); }); } @@ -116,11 +115,11 @@ protected function handleFactories(): void $appNamespace = static::appNamespace(); - $modelName = Str::startsWith($modelName, $appNamespace . 'Models\\') - ? Str::after($modelName, $appNamespace . 'Models\\') + $modelName = Str::startsWith($modelName, $appNamespace.'Models\\') + ? Str::after($modelName, $appNamespace.'Models\\') : Str::after($modelName, $appNamespace); - return 'Database\\Factories\\' . $modelName . 'Factory'; + return 'Database\\Factories\\'.$modelName.'Factory'; }); } @@ -133,7 +132,7 @@ protected static function finder($paths) ->finish('/'); $ignoredFolders = collect(config('ddd.autoload_ignore', [])) - ->map(fn($path) => Str::finish($path, '/')); + ->map(fn ($path) => Str::finish($path, '/')); if ($pathAfterDomain->startsWith($ignoredFolders)) { return false; diff --git a/src/Support/DomainMigration.php b/src/Support/DomainMigration.php index d81f6fd..91e4a65 100644 --- a/src/Support/DomainMigration.php +++ b/src/Support/DomainMigration.php @@ -2,24 +2,10 @@ namespace Lunarstorm\LaravelDDD\Support; -use Illuminate\Console\Application as ConsoleApplication; -use Illuminate\Console\Command; -use Illuminate\Container\Container; -use Illuminate\Database\Eloquent\Factories\Factory; -use Illuminate\Database\Migrations\Migration; -use Illuminate\Foundation\Application; -use Illuminate\Support\Arr; -use Illuminate\Support\Collection; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Gate; -use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; use Lorisleiva\Lody\Lody; -use Lunarstorm\LaravelDDD\Factories\DomainFactory; -use Lunarstorm\LaravelDDD\ValueObjects\DomainObject; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; -use Throwable; class DomainMigration { @@ -48,7 +34,7 @@ public static function paths(): array protected static function normalizePaths($path): array { return collect($path) - ->filter(fn($path) => is_dir($path)) + ->filter(fn ($path) => is_dir($path)) ->toArray(); } @@ -71,7 +57,7 @@ public static function discoverPaths(): array $finder = static::finder($paths); return Lody::filesFromFinder($finder) - ->map(fn($file) => $file->getPath()) + ->map(fn ($file) => $file->getPath()) ->unique() ->values() ->toArray(); diff --git a/src/Support/DomainResolver.php b/src/Support/DomainResolver.php index 8b1d998..a7423f3 100644 --- a/src/Support/DomainResolver.php +++ b/src/Support/DomainResolver.php @@ -2,7 +2,6 @@ namespace Lunarstorm\LaravelDDD\Support; -use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Str; class DomainResolver @@ -12,10 +11,10 @@ class DomainResolver */ public static function domainChoices(): array { - $folders = glob(app()->basePath(static::domainPath() . '/*'), GLOB_ONLYDIR); + $folders = glob(app()->basePath(static::domainPath().'/*'), GLOB_ONLYDIR); return collect($folders) - ->map(fn($path) => basename($path)) + ->map(fn ($path) => basename($path)) ->sort() ->toArray(); } diff --git a/tests/Generator/ControllerMakeTest.php b/tests/Generator/ControllerMakeTest.php index af3e8e5..a4658f4 100644 --- a/tests/Generator/ControllerMakeTest.php +++ b/tests/Generator/ControllerMakeTest.php @@ -2,9 +2,7 @@ use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Config; -use Laravel\Prompts\Prompt; use Lunarstorm\LaravelDDD\Tests\Fixtures\Enums\Feature; -use Mockery\Mock; beforeEach(function () { Config::set('ddd.domain_path', 'src/Domain'); @@ -32,7 +30,7 @@ expect($output = Artisan::output())->when( Feature::IncludeFilepathInGeneratorCommandOutput->exists(), - fn($output) => $output->toContainFilepath($relativePath), + fn ($output) => $output->toContainFilepath($relativePath), ); expect(file_exists($expectedPath))->toBeTrue(); @@ -64,7 +62,7 @@ expect(file_exists($expectedPath))->toBeFalse(); - Artisan::call("ddd:controller",[ + Artisan::call('ddd:controller', [ 'name' => $controllerName, '--domain' => $domainName, '--model' => $modelName, @@ -117,7 +115,7 @@ } } - Artisan::call("ddd:controller", [ + Artisan::call('ddd:controller', [ 'name' => $controllerName, '--domain' => $domainName, '--model' => $modelName, @@ -127,7 +125,7 @@ $output = Artisan::output(); foreach ($generatedPaths as $path) { - if(Feature::IncludeFilepathInGeneratorCommandOutput->exists()){ + if (Feature::IncludeFilepathInGeneratorCommandOutput->exists()) { expect($output)->toContainFilepath($path); } diff --git a/tests/Generator/MigrationMakeTest.php b/tests/Generator/MigrationMakeTest.php index fdf285e..adfdf57 100644 --- a/tests/Generator/MigrationMakeTest.php +++ b/tests/Generator/MigrationMakeTest.php @@ -3,9 +3,6 @@ use Illuminate\Support\Arr; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Config; -use Illuminate\Support\Str; -use Lunarstorm\LaravelDDD\Support\Domain; -use Lunarstorm\LaravelDDD\Support\DomainAutoloader; use Lunarstorm\LaravelDDD\Support\DomainCache; use Lunarstorm\LaravelDDD\Support\DomainMigration; use Lunarstorm\LaravelDDD\Support\Path; @@ -41,7 +38,7 @@ expect($output = Artisan::output())->when( Feature::IncludeFilepathInGeneratorCommandOutput->exists(), - fn($output) => $output + fn ($output) => $output ->toContainFilepath($relativePath) ->toContain('_create_invoices_table.php'), ); @@ -53,7 +50,7 @@ expect($createdMigrationFile)->toEndWith('_create_invoices_table.php'); expect(file_get_contents($createdMigrationFile)) - ->toContain("return new class extends Migration"); + ->toContain('return new class extends Migration'); })->with('domainPaths'); it('discovers domain migration folders', function ($domainPath, $domainRoot) { @@ -64,12 +61,11 @@ expect($discoveredPaths)->toHaveCount(0); - Artisan::call("ddd:migration Invoicing:" . uniqid('migration')); - Artisan::call("ddd:migration Shared:" . uniqid('migration')); - Artisan::call("ddd:migration Reporting:" . uniqid('migration')); - Artisan::call("ddd:migration Reporting:" . uniqid('migration')); - Artisan::call("ddd:migration Reporting:" . uniqid('migration')); - + Artisan::call('ddd:migration Invoicing:'.uniqid('migration')); + Artisan::call('ddd:migration Shared:'.uniqid('migration')); + Artisan::call('ddd:migration Reporting:'.uniqid('migration')); + Artisan::call('ddd:migration Reporting:'.uniqid('migration')); + Artisan::call('ddd:migration Reporting:'.uniqid('migration')); $discoveredPaths = DomainMigration::discoverPaths(); @@ -83,6 +79,6 @@ foreach ($discoveredPaths as $path) { expect(str($path)->contains($expectedFolderPatterns)) - ->toBeTrue("Expecting path to contain one of the expected folder patterns"); + ->toBeTrue('Expecting path to contain one of the expected folder patterns'); } })->with('domainPaths'); diff --git a/tests/Generator/Model/MakeTest.php b/tests/Generator/Model/MakeTest.php index 4f4bce1..f465263 100644 --- a/tests/Generator/Model/MakeTest.php +++ b/tests/Generator/Model/MakeTest.php @@ -31,7 +31,7 @@ expect(Artisan::output())->when( Feature::IncludeFilepathInGeneratorCommandOutput->exists(), - fn($output) => $output->toContainFilepath($relativePath), + fn ($output) => $output->toContainFilepath($relativePath), ); expect(file_exists($expectedModelPath))->toBeTrue(); diff --git a/tests/Generator/Model/MakeWithControllerTest.php b/tests/Generator/Model/MakeWithControllerTest.php index 52d68fb..629933f 100644 --- a/tests/Generator/Model/MakeWithControllerTest.php +++ b/tests/Generator/Model/MakeWithControllerTest.php @@ -4,7 +4,6 @@ use Illuminate\Support\Facades\Config; use Lunarstorm\LaravelDDD\Models\DomainModel; use Lunarstorm\LaravelDDD\Support\Domain; -use Lunarstorm\LaravelDDD\Support\Path; use Lunarstorm\LaravelDDD\Tests\Fixtures\Enums\Feature; beforeEach(function () { @@ -53,7 +52,7 @@ 'RecordController', [ 'src/Domain/Invoicing/Models/Record.php', - 'app/Modules/Invoicing/Controllers/RecordController.php' + 'app/Modules/Invoicing/Controllers/RecordController.php', ], ], @@ -63,7 +62,7 @@ 'RecordEntryController', [ 'src/Domain/Invoicing/Models/RecordEntry.php', - 'app/Modules/Invoicing/Controllers/RecordEntryController.php' + 'app/Modules/Invoicing/Controllers/RecordEntryController.php', ], ], @@ -73,7 +72,7 @@ 'ReportSubmissionController', [ 'src/Domain/Reporting/Internal/Models/ReportSubmission.php', - 'app/Modules/Reporting/Internal/Controllers/ReportSubmissionController.php' + 'app/Modules/Reporting/Internal/Controllers/ReportSubmissionController.php', ], ], diff --git a/tests/Generator/RequestMakeTest.php b/tests/Generator/RequestMakeTest.php index 9d2ad9a..2872b79 100644 --- a/tests/Generator/RequestMakeTest.php +++ b/tests/Generator/RequestMakeTest.php @@ -30,7 +30,7 @@ expect($output = Artisan::output())->when( Feature::IncludeFilepathInGeneratorCommandOutput->exists(), - fn($output) => $output->toContainFilepath($relativePath), + fn ($output) => $output->toContainFilepath($relativePath), ); expect(file_exists($expectedPath))->toBeTrue(); diff --git a/tests/Support/DomainTest.php b/tests/Support/DomainTest.php index d2398d1..459af6f 100644 --- a/tests/Support/DomainTest.php +++ b/tests/Support/DomainTest.php @@ -115,7 +115,7 @@ it('normalizes slashes in nested objects', function ($nameInput, $normalized) { expect((new Domain('Invoicing'))->object('class', $nameInput)) - ->name->toBe($normalized); + ->name->toBe($normalized); })->with([ ['Nested\\Thing', 'Nested\\Thing'], ['Nested/Thing', 'Nested\\Thing'], From 8d444cdc4874b0a48d7e49ae085df77d0aba314f Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 11:03:19 -0400 Subject: [PATCH 008/169] Replace HasFactory with HasDomainFactory. --- src/Commands/DomainModelMakeCommand.php | 26 ++++++++++++++----------- tests/Generator/Model/MakeTest.php | 3 ++- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/Commands/DomainModelMakeCommand.php b/src/Commands/DomainModelMakeCommand.php index 9f798e0..b623b0b 100644 --- a/src/Commands/DomainModelMakeCommand.php +++ b/src/Commands/DomainModelMakeCommand.php @@ -34,22 +34,26 @@ protected function buildClass($name) { $stub = parent::buildClass($name); + $replacements = [ + 'use Illuminate\Database\Eloquent\Factories\HasFactory;' => "use Lunarstorm\LaravelDDD\Factories\HasDomainFactory as HasFactory;", + ]; + if ($baseModel = $this->getBaseModel()) { $baseModelClass = class_basename($baseModel); - $replacements = [ - 'use Illuminate\Database\Eloquent\Model;' => "use {$baseModel};", + $replacements = array_merge($replacements, [ 'extends Model' => "extends {$baseModelClass}", - ]; + 'use Illuminate\Database\Eloquent\Model;' => "use {$baseModel};", + ]); + } - $stub = str_replace( - array_keys($replacements), - array_values($replacements), - $stub - ); + $stub = str_replace( + array_keys($replacements), + array_values($replacements), + $stub + ); - $stub = $this->sortImports($stub); - } + $stub = $this->sortImports($stub); return $stub; } @@ -59,7 +63,7 @@ protected function createFactory() $factory = Str::studly($this->argument('name')); $this->call(DomainFactoryMakeCommand::class, [ - 'name' => $factory.'Factory', + 'name' => $factory . 'Factory', '--domain' => $this->domain->dotName, '--model' => $this->qualifyClass($this->getNameInput()), ]); diff --git a/tests/Generator/Model/MakeTest.php b/tests/Generator/Model/MakeTest.php index f465263..84bca9f 100644 --- a/tests/Generator/Model/MakeTest.php +++ b/tests/Generator/Model/MakeTest.php @@ -42,7 +42,8 @@ config('ddd.namespaces.model'), ]); - expect(file_get_contents($expectedModelPath))->toContain("namespace {$expectedNamespace};"); + expect(file_get_contents($expectedModelPath)) + ->toContain("namespace {$expectedNamespace};"); })->with('domainPaths'); it('can generate a domain model with factory', function ($domainPath, $domainRoot, $domainName, $subdomain) { From a7ddad510651dcbc58089829e9eccbf32a7c3948 Mon Sep 17 00:00:00 2001 From: JasperTey Date: Mon, 14 Oct 2024 15:03:41 +0000 Subject: [PATCH 009/169] Fix styling --- src/Commands/DomainModelMakeCommand.php | 2 +- tests/Generator/Model/MakeTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Commands/DomainModelMakeCommand.php b/src/Commands/DomainModelMakeCommand.php index b623b0b..f954699 100644 --- a/src/Commands/DomainModelMakeCommand.php +++ b/src/Commands/DomainModelMakeCommand.php @@ -63,7 +63,7 @@ protected function createFactory() $factory = Str::studly($this->argument('name')); $this->call(DomainFactoryMakeCommand::class, [ - 'name' => $factory . 'Factory', + 'name' => $factory.'Factory', '--domain' => $this->domain->dotName, '--model' => $this->qualifyClass($this->getNameInput()), ]); diff --git a/tests/Generator/Model/MakeTest.php b/tests/Generator/Model/MakeTest.php index 84bca9f..4b841a5 100644 --- a/tests/Generator/Model/MakeTest.php +++ b/tests/Generator/Model/MakeTest.php @@ -43,7 +43,7 @@ ]); expect(file_get_contents($expectedModelPath)) - ->toContain("namespace {$expectedNamespace};"); + ->toContain("namespace {$expectedNamespace};"); })->with('domainPaths'); it('can generate a domain model with factory', function ($domainPath, $domainRoot, $domainName, $subdomain) { From dca028205c824c2155f845cebb0edb042f44d6d4 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 11:14:08 -0400 Subject: [PATCH 010/169] Normalize paths for tests on Windows. --- tests/Generator/MigrationMakeTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Generator/MigrationMakeTest.php b/tests/Generator/MigrationMakeTest.php index adfdf57..c32c614 100644 --- a/tests/Generator/MigrationMakeTest.php +++ b/tests/Generator/MigrationMakeTest.php @@ -72,9 +72,9 @@ expect($discoveredPaths)->toHaveCount(3); $expectedFolderPatterns = [ - 'Invoicing/Database/Migrations', - 'Shared/Database/Migrations', - 'Reporting/Database/Migrations', + Path::normalize('Invoicing/Database/Migrations'), + Path::normalize('Shared/Database/Migrations'), + Path::normalize('Reporting/Database/Migrations'), ]; foreach ($discoveredPaths as $path) { From cb8c93b3c60529ac3a2bc3421f6eef8630e6ca0a Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 11:27:01 -0400 Subject: [PATCH 011/169] Keep phpstan happy. --- .../Concerns/QualifiesDomainModels.php | 20 ------------------- .../Migration/BaseMigrateMakeCommand.php | 15 ++++++++++++++ 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/Commands/Concerns/QualifiesDomainModels.php b/src/Commands/Concerns/QualifiesDomainModels.php index 92ad4ed..8e5af0a 100644 --- a/src/Commands/Concerns/QualifiesDomainModels.php +++ b/src/Commands/Concerns/QualifiesDomainModels.php @@ -2,28 +2,8 @@ namespace Lunarstorm\LaravelDDD\Commands\Concerns; -use Illuminate\Support\Str; - trait QualifiesDomainModels { - protected function qualifyClass($name) - { - $name = ltrim($name, '\\/'); - - $name = str_replace('/', '\\', $name); - - $rootNamespace = $this->rootNamespace(); - - if (Str::startsWith($name, $rootNamespace)) { - return $name; - } - - // return $this->qualifyClass( - // $this->getDefaultNamespace(trim($rootNamespace, '\\')).'\\'.$name - // ); - return $this->getDefaultNamespace(trim($rootNamespace, '\\')).'\\'.$name; - } - protected function qualifyModel(string $model) { if ($domain = $this->domain) { diff --git a/src/Commands/Migration/BaseMigrateMakeCommand.php b/src/Commands/Migration/BaseMigrateMakeCommand.php index 789192f..739de18 100644 --- a/src/Commands/Migration/BaseMigrateMakeCommand.php +++ b/src/Commands/Migration/BaseMigrateMakeCommand.php @@ -33,4 +33,19 @@ protected function getNameInput() return $name; } + + protected function qualifyModel(string $model) + { + return; + } + + protected function getDefaultNamespace($rootNamespace) + { + return; + } + + protected function getPath($name) + { + return; + } } From f0f297c50486203ac39dca16d07bc5442c95d467 Mon Sep 17 00:00:00 2001 From: JasperTey Date: Mon, 14 Oct 2024 15:27:26 +0000 Subject: [PATCH 012/169] Fix styling --- src/Commands/Migration/BaseMigrateMakeCommand.php | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Commands/Migration/BaseMigrateMakeCommand.php b/src/Commands/Migration/BaseMigrateMakeCommand.php index 739de18..de095eb 100644 --- a/src/Commands/Migration/BaseMigrateMakeCommand.php +++ b/src/Commands/Migration/BaseMigrateMakeCommand.php @@ -34,18 +34,9 @@ protected function getNameInput() return $name; } - protected function qualifyModel(string $model) - { - return; - } + protected function qualifyModel(string $model) {} - protected function getDefaultNamespace($rootNamespace) - { - return; - } + protected function getDefaultNamespace($rootNamespace) {} - protected function getPath($name) - { - return; - } + protected function getPath($name) {} } From 1239847c4a742545eae7309aba498f0c603d964c Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 11:36:47 -0400 Subject: [PATCH 013/169] Add header block. --- config/ddd.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config/ddd.php b/config/ddd.php index 030498f..5446018 100644 --- a/config/ddd.php +++ b/config/ddd.php @@ -70,6 +70,14 @@ 'trait' => '', ], + /* + |-------------------------------------------------------------------------- + | Application Layer + |-------------------------------------------------------------------------- + | + | Configure domain objects in the application layer. + | + */ 'application_layer' => [ 'path' => 'app/Modules', 'namespace' => 'App\Modules', From a383b87c34b579f4483e40376bcaa2a8f0c70322 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 12:10:18 -0400 Subject: [PATCH 014/169] Remove stray methods. --- src/Commands/DomainMailMakeCommand.php | 87 -------------------------- 1 file changed, 87 deletions(-) diff --git a/src/Commands/DomainMailMakeCommand.php b/src/Commands/DomainMailMakeCommand.php index 6021c70..15f9508 100644 --- a/src/Commands/DomainMailMakeCommand.php +++ b/src/Commands/DomainMailMakeCommand.php @@ -3,7 +3,6 @@ namespace Lunarstorm\LaravelDDD\Commands; use Illuminate\Foundation\Console\MailMakeCommand; -use Illuminate\Support\Str; use Lunarstorm\LaravelDDD\Commands\Concerns\ResolvesDomainFromInput; class DomainMailMakeCommand extends MailMakeCommand @@ -11,90 +10,4 @@ class DomainMailMakeCommand extends MailMakeCommand use ResolvesDomainFromInput; protected $name = 'ddd:mail'; - - /** - * Create a model factory for the model. - * - * @return void - */ - protected function createFactory() - { - $factory = Str::studly($this->argument('name')); - - $this->call('ddd:factory', [ - 'name' => "{$factory}Factory", - '--domain' => $this->domain->dotName, - '--model' => $this->qualifyClass($this->getNameInput()), - ]); - } - - /** - * Create a migration file for the model. - * - * @return void - */ - protected function createMigration() - { - $table = Str::snake(Str::pluralStudly(class_basename($this->argument('name')))); - - if ($this->option('pivot')) { - $table = Str::singular($table); - } - - $this->call('make:migration', [ - 'name' => "create_{$table}_table", - '--create' => $table, - ]); - } - - /** - * Create a seeder file for the model. - * - * @return void - */ - protected function createSeeder() - { - $seeder = Str::studly(class_basename($this->argument('name'))); - - $this->call('make:seeder', [ - 'name' => "{$seeder}Seeder", - ]); - } - - /** - * Create a controller for the model. - * - * @return void - */ - protected function createController() - { - $controller = Str::studly(class_basename($this->argument('name'))); - - $modelName = $this->qualifyClass($this->getNameInput()); - - $this->call('make:controller', array_filter([ - 'name' => "{$controller}Controller", - '--model' => $this->option('resource') || $this->option('api') ? $modelName : null, - '--api' => $this->option('api'), - '--requests' => $this->option('requests') || $this->option('all'), - '--test' => $this->option('test'), - '--pest' => $this->option('pest'), - ])); - } - - /** - * Create a policy file for the model. - * - * @return void - */ - protected function createPolicy() - { - $policy = Str::studly(class_basename($this->argument('name'))); - - $this->call('ddd:policy', [ - 'name' => "{$policy}Policy", - '--domain' => $this->domain->dotName, - '--model' => $this->qualifyClass($this->getNameInput()), - ]); - } } From 3668e0533ef36e777156759775033fb7dfb7db49 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 18:39:39 -0400 Subject: [PATCH 015/169] Update readme. --- README.md | 142 +++++++++++++++++++++++-------------------------- config/ddd.php | 70 +++++++++--------------- 2 files changed, 91 insertions(+), 121 deletions(-) diff --git a/README.md b/README.md index fd1c833..68831fd 100644 --- a/README.md +++ b/README.md @@ -53,55 +53,40 @@ php artisan ddd:{object} {name} ## Available Commands ### Generators -The following generators are currently available, shown using short-hand syntax: -```bash -# Generate a domain model -php artisan ddd:model Invoicing:Invoice - -# Generate a domain model with factory -php artisan ddd:model Invoicing:Invoice -f -php artisan ddd:model Invoicing:Invoice --factory - -# Generate a domain factory -php artisan ddd:factory Invoicing:InvoiceFactory -php artisan ddd:factory Invoicing:InvoiceFactory --model=Invoice # optionally specifying the model - -# Generate a data transfer object -php artisan ddd:dto Invoicing:LineItemPayload - -# Generates a value object -php artisan ddd:value Shared:DollarAmount - -# Generates a view model -php artisan ddd:view-model Invoicing:ShowInvoiceViewModel - -# Generates an action -php artisan ddd:action Invoicing:SendInvoiceToCustomer - -# Extended Commands -# These extend Laravel's respective make:* commands and places the objects into the domain layer -php artisan ddd:cast Invoicing:MoneyCast -php artisan ddd:channel Invoicing:InvoiceChannel -php artisan ddd:command Invoicing:InvoiceDeliver -php artisan ddd:event Invoicing:PaymentWasReceived -php artisan ddd:exception Invoicing:InvoiceNotFoundException -php artisan ddd:job Invoicing:GenerateInvoicePdf -php artisan ddd:listener Invoicing:HandlePaymentReceived -php artisan ddd:mail Invoicing:OverduePaymentReminderEmail -php artisan ddd:notification Invoicing:YourPaymentWasReceived -php artisan ddd:observer Invoicing:InvoiceObserver -php artisan ddd:policy Invoicing:InvoicePolicy -php artisan ddd:provider Invoicing:InvoiceServiceProvider -php artisan ddd:resource Invoicing:InvoiceResource -php artisan ddd:rule Invoicing:ValidPaymentMethod -php artisan ddd:scope Invoicing:ArchivedInvoicesScope - -# Laravel 11+ only -php artisan ddd:class Invoicing:Support/InvoiceBuilder -php artisan ddd:enum Customer:CustomerType -php artisan ddd:interface Customer:Contracts/Invoiceable -php artisan ddd:trait Customer:Concerns/HasInvoices -``` +The following generators are currently available: +| Command | Description | Usage | +|---|---|---| +| ddd:model [options] | Generate a domain model | `php artisan ddd:model Invoicing:Invoice`

Options:
`--migration\|-m`
`--factory\|-f`
`--seed\|-s`
`--controller --resource --requests\|-crR`
`--policy`
`-mfsc`
`--all\|-a`
`--pivot\|-p`
| +| ddd:factory | Generate a domain factory | `php artisan ddd:factory Invoicing:InvoiceFactory` | +| ddd:dto | Generate a data transfer object | `php artisan ddd:dto Invoicing:LineItemPayload` | +| ddd:value | Generate a value object | `php artisan ddd:value Shared:DollarAmount` | +| ddd:view-model | Generate a view model | `php artisan ddd:view-model Invoicing:ShowInvoiceViewModel` | +| ddd:action | Generate an action | `php artisan ddd:action Invoicing:SendInvoiceToCustomer` | +| ddd:cast | Generate a cast | `php artisan ddd:cast Invoicing:MoneyCast` | +| ddd:channel | Generate a channel | `php artisan ddd:channel Invoicing:InvoiceChannel` | +| ddd:command | Generate a command | `php artisan ddd:command Invoicing:InvoiceDeliver` | +| ddd:controller [options] | Generate a controller | `php artisan ddd:controller Invoicing:InvoiceController`

Options: supports the standard options as `make:controller` | +| ddd:event | Generate an event | `php artisan ddd:event Invoicing:PaymentWasReceived` | +| ddd:exception | Generate an exception | `php artisan ddd:exception Invoicing:InvoiceNotFoundException` | +| ddd:job | Generate a job | `php artisan ddd:job Invoicing:GenerateInvoicePdf` | +| ddd:listener | Generate a listener | `php artisan ddd:listener Invoicing:HandlePaymentReceived` | +| ddd:mail | Generate a mail | `php artisan ddd:mail Invoicing:OverduePaymentReminderEmail` | +| ddd:middleware | Generate a middleware | `php artisan ddd:middleware Invoicing:VerifiedCustomerMiddleware` | +| ddd:migration | Generate a migration | `php artisan ddd:migration Invoicing:CreateInvoicesTable` | +| ddd:notification | Generate a notification | `php artisan ddd:notification Invoicing:YourPaymentWasReceived` | +| ddd:observer | Generate an observer | `php artisan ddd:observer Invoicing:InvoiceObserver` | +| ddd:policy | Generate a policy | `php artisan ddd:policy Invoicing:InvoicePolicy` | +| ddd:provider | Generate a provider | `php artisan ddd:provider Invoicing:InvoiceServiceProvider` | +| ddd:resource | Generate a resource | `php artisan ddd:resource Invoicing:InvoiceResource` | +| ddd:rule | Generate a rule | `php artisan ddd:rule Invoicing:ValidPaymentMethod` | +| ddd:request | Generate a form request | `php artisan ddd:request Invoicing:StoreInvoiceRequest` | +| ddd:scope | Generate a scope | `php artisan ddd:scope Invoicing:ArchivedInvoicesScope` | +| ddd:seeder | Generate a seeder | `php artisan ddd:seeder Invoicing:InvoiceSeeder` | +| ddd:class | Generate a class (Laravel 11+) | `php artisan ddd:class Invoicing:Support/InvoiceBuilder` | +| ddd:enum | Generate an enum (Laravel 11+) | `php artisan ddd:enum Customer:CustomerType` | +| ddd:interface | Generate an interface (Laravel 11+) | `php artisan ddd:interface Customer:Contracts/Invoiceable` | +| ddd:trait | Generate a trait (Laravel 11+) | `php artisan ddd:trait Customer:Concerns/HasInvoices` | + Generated objects will be placed in the appropriate domain namespace as specified by `ddd.namespaces.*` in the [config file](#config-file). ### Other Commands @@ -240,6 +225,7 @@ You may disable autoloading by setting the respective autoload options to `false // 'commands' => true, // 'policies' => true, // 'factories' => true, +// 'migrations' => true, // ], ``` @@ -254,6 +240,8 @@ In production, you should cache the autoload manifests using the `ddd:cache` com This is the content of the published config file (`ddd.php`): ```php + 'Domain', + /* + |-------------------------------------------------------------------------- + | Application Layer + |-------------------------------------------------------------------------- + | + | Configure domain objects in the application layer. + | + */ + 'application_layer' => [ + 'path' => 'app/Modules', + 'namespace' => 'App\Modules', + 'objects' => [ + 'controller', + 'request', + 'middleware', + ], + ], + /* |-------------------------------------------------------------------------- | Domain Object Namespaces @@ -302,6 +308,7 @@ return [ 'class' => '', 'channel' => 'Channels', 'command' => 'Commands', + 'controller' => 'Controllers', 'enum' => 'Enums', 'event' => 'Events', 'exception' => 'Exceptions', @@ -310,13 +317,17 @@ return [ 'job' => 'Jobs', 'listener' => 'Listeners', 'mail' => 'Mail', + 'middleware' => 'Middleware', + 'migration' => 'Database\Migrations', 'notification' => 'Notifications', 'observer' => 'Observers', 'policy' => 'Policies', 'provider' => 'Providers', 'resource' => 'Resources', + 'request' => 'Requests', 'rule' => 'Rules', 'scope' => 'Scopes', + 'seeder' => 'Database\Seeders', 'trait' => '', ], @@ -325,12 +336,11 @@ return [ | Base Model |-------------------------------------------------------------------------- | - | The base class which generated domain models should extend. By default, - | generated domain models will extend `Domain\Shared\Models\BaseModel`, - | which will be created if it doesn't already exist. + | The base model class which generated domain models should extend. If + | set to null, the generated models will extend Laravel's default. | */ - 'base_model' => 'Domain\Shared\Models\BaseModel', + 'base_model' => null, /* |-------------------------------------------------------------------------- @@ -374,34 +384,16 @@ return [ | Autoloading |-------------------------------------------------------------------------- | - | Configure whether domain providers, commands, policies, and factories - | should be auto-discovered and registered. + | Configure whether domain providers, commands, policies, factories, + | and migrations should be auto-discovered and registered. | */ 'autoload' => [ - /** - * 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 within the domain layer extending `Illuminate\Console\Command` - * will be auto-registered as a command when running in console. - */ 'commands' => true, - - /** - * 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, 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, + 'migrations' => true, ], /* @@ -415,7 +407,7 @@ return [ | e.g., src/Domain/Invoicing/ | | If more advanced filtering is needed, a callback can be registered - | using the `DDD::filterAutoloadPathsUsing(callback $filter)` in + | using `DDD::filterAutoloadPathsUsing(callback $filter)` in | the AppServiceProvider's boot method. | */ diff --git a/config/ddd.php b/config/ddd.php index 5446018..e6b707a 100644 --- a/config/ddd.php +++ b/config/ddd.php @@ -22,6 +22,24 @@ */ 'domain_namespace' => 'Domain', + /* + |-------------------------------------------------------------------------- + | Application Layer + |-------------------------------------------------------------------------- + | + | Configure domain objects in the application layer. + | + */ + 'application_layer' => [ + 'path' => 'app/Modules', + 'namespace' => 'App\Modules', + 'objects' => [ + 'controller', + 'request', + 'middleware', + ], + ], + /* |-------------------------------------------------------------------------- | Domain Object Namespaces @@ -57,6 +75,7 @@ 'job' => 'Jobs', 'listener' => 'Listeners', 'mail' => 'Mail', + 'middleware' => 'Middleware', 'migration' => 'Database\Migrations', 'notification' => 'Notifications', 'observer' => 'Observers', @@ -70,34 +89,16 @@ 'trait' => '', ], - /* - |-------------------------------------------------------------------------- - | Application Layer - |-------------------------------------------------------------------------- - | - | Configure domain objects in the application layer. - | - */ - 'application_layer' => [ - 'path' => 'app/Modules', - 'namespace' => 'App\Modules', - 'objects' => [ - 'controller', - 'request', - ], - ], - /* |-------------------------------------------------------------------------- | Base Model |-------------------------------------------------------------------------- | - | The base class which generated domain models should extend. By default, - | generated domain models will extend `Domain\Shared\Models\BaseModel`, - | which will be created if it doesn't already exist. + | The base model class which generated domain models should extend. If + | set to null, the generated models will extend Laravel's default. | */ - 'base_model' => 'Domain\Shared\Models\BaseModel', + 'base_model' => null, /* |-------------------------------------------------------------------------- @@ -141,38 +142,15 @@ | Autoloading |-------------------------------------------------------------------------- | - | Configure whether domain providers, commands, policies, and factories - | should be auto-discovered and registered. + | Configure whether domain providers, commands, policies, factories, + | and migrations should be auto-discovered and registered. | */ 'autoload' => [ - /** - * 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 within the domain layer extending `Illuminate\Console\Command` - * will be auto-registered as a command when running in console. - */ 'commands' => true, - - /** - * 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, 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, - - /** - * When enabled, migration folders across all domains will be registered as a database migration path. - */ 'migrations' => true, ], From 86baa31366abcae318bc16313acd97a86aa54ee5 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 18:41:25 -0400 Subject: [PATCH 016/169] Further refinements and WIP. --- README.md | 4 +- composer.json | 3 +- config/ddd.php | 2 +- src/Commands/CacheCommand.php | 13 ++-- src/Commands/DomainMiddlewareMakeCommand.php | 13 ++++ src/Commands/DomainModelMakeCommand.php | 4 ++ src/DomainManager.php | 36 ++++++++++ src/Facades/DDD.php | 1 + src/LaravelDDDServiceProvider.php | 11 +-- src/Support/Domain.php | 15 +++- src/Support/DomainMigration.php | 8 +-- src/Support/DomainResolver.php | 68 +++++++++++-------- .../2024_10_14_215911_do_nothing.php | 24 +++++++ .../NamespaceResolverTest.php | 27 ++++++++ tests/Command/CacheTest.php | 14 +++- tests/Command/ListTest.php | 5 ++ tests/Generator/ExtendedCommandsTest.php | 1 + tests/Support/DomainResolverTest.php | 5 ++ 18 files changed, 199 insertions(+), 55 deletions(-) create mode 100644 src/Commands/DomainMiddlewareMakeCommand.php create mode 100644 tests/.skeleton/src/Domain/Invoicing/Database/Migrations/2024_10_14_215911_do_nothing.php create mode 100644 tests/ApplicationLayer/NamespaceResolverTest.php diff --git a/README.md b/README.md index 68831fd..520acb3 100644 --- a/README.md +++ b/README.md @@ -240,8 +240,6 @@ In production, you should cache the autoload manifests using the `ddd:cache` com This is the content of the published config file (`ddd.php`): ```php - 'Domain', - /* + /* |-------------------------------------------------------------------------- | Application Layer |-------------------------------------------------------------------------- diff --git a/composer.json b/composer.json index 243d503..c7fa76b 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,8 @@ "pestphp/pest-plugin-laravel": "^2.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0" + "phpstan/phpstan-phpunit": "^1.0", + "spatie/laravel-data": "^4.10" }, "autoload": { "psr-4": { diff --git a/config/ddd.php b/config/ddd.php index e6b707a..08016f3 100644 --- a/config/ddd.php +++ b/config/ddd.php @@ -22,7 +22,7 @@ */ 'domain_namespace' => 'Domain', - /* + /* |-------------------------------------------------------------------------- | Application Layer |-------------------------------------------------------------------------- diff --git a/src/Commands/CacheCommand.php b/src/Commands/CacheCommand.php index 4d4fe68..e6bd6cf 100644 --- a/src/Commands/CacheCommand.php +++ b/src/Commands/CacheCommand.php @@ -4,21 +4,22 @@ use Illuminate\Console\Command; use Lunarstorm\LaravelDDD\Support\DomainAutoloader; +use Lunarstorm\LaravelDDD\Support\DomainMigration; class CacheCommand extends Command { protected $name = 'ddd:cache'; - protected $description = 'Cache auto-discovered domain objects used for autoloading.'; + protected $description = 'Cache auto-discovered domain objects and migration paths.'; public function handle() { - DomainAutoloader::cacheProviders(); + $this->components->info('Caching DDD providers, commands, migration paths.'); - $this->components->info('Domain providers cached successfully.'); + $this->components->task('domain providers', fn () => DomainAutoloader::cacheProviders()); + $this->components->task('domain commands', fn () => DomainAutoloader::cacheCommands()); + $this->components->task('domain migration paths', fn () => DomainMigration::cachePaths()); - DomainAutoloader::cacheCommands(); - - $this->components->info('Domain commands cached successfully.'); + $this->newLine(); } } diff --git a/src/Commands/DomainMiddlewareMakeCommand.php b/src/Commands/DomainMiddlewareMakeCommand.php new file mode 100644 index 0000000..2c6b2af --- /dev/null +++ b/src/Commands/DomainMiddlewareMakeCommand.php @@ -0,0 +1,13 @@ +autoloadFilter = null; + $this->applicationLayerFilter = null; + $this->applicationLayerNamespaceResolver = null; } public function filterAutoloadPathsUsing(callable $filter): void @@ -25,4 +41,24 @@ public function getAutoloadFilter(): ?callable { return $this->autoloadFilter; } + + public function filterApplicationLayerUsing(callable $filter): void + { + $this->applicationLayerFilter = $filter; + } + + public function getApplicationLayerFilter(): ?callable + { + return $this->applicationLayerFilter; + } + + public function resolveApplicationLayerNamespaceUsing(callable $resolver): void + { + $this->applicationLayerNamespaceResolver = $resolver; + } + + public function getApplicationLayerNamespaceResolver(): ?callable + { + return $this->applicationLayerNamespaceResolver; + } } diff --git a/src/Facades/DDD.php b/src/Facades/DDD.php index 5164cda..d3bd5ea 100644 --- a/src/Facades/DDD.php +++ b/src/Facades/DDD.php @@ -8,6 +8,7 @@ * @see \Lunarstorm\LaravelDDD\DomainManager * * @method static void filterAutoloadPathsUsing(callable $filter) + * @method static void resolveApplicationLayerNamespaceUsing(callable $resolver) */ class DDD extends Facade { diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index 73e8b56..82cd2f5 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -48,6 +48,7 @@ public function configurePackage(Package $package): void Commands\DomainJobMakeCommand::class, Commands\DomainListenerMakeCommand::class, Commands\DomainMailMakeCommand::class, + Commands\DomainMiddlewareMakeCommand::class, Commands\DomainNotificationMakeCommand::class, Commands\DomainObserverMakeCommand::class, Commands\DomainPolicyMakeCommand::class, @@ -66,11 +67,9 @@ public function configurePackage(Package $package): void $package->hasCommand(Commands\DomainInterfaceMakeCommand::class); $package->hasCommand(Commands\DomainTraitMakeCommand::class); } - - $this->registerDomainMigrateMakeCommand(); } - protected function registerDomainMigrateMakeCommand() + protected function registerMigrations() { $this->app->singleton(Commands\Migration\DomainMigrateMakeCommand::class, function ($app) { // Once we have the migration creator registered, we will create the command @@ -81,6 +80,8 @@ protected function registerDomainMigrateMakeCommand() return new Commands\Migration\DomainMigrateMakeCommand($creator, $composer); }); + + $this->loadMigrationsFrom(DomainMigration::paths()); } public function packageBooted() @@ -88,12 +89,12 @@ public function packageBooted() $this->publishes([ $this->package->basePath('/../stubs') => resource_path("stubs/{$this->package->shortName()}"), ], "{$this->package->shortName()}-stubs"); - - $this->loadMigrationsFrom(DomainMigration::paths()); } public function packageRegistered() { (new DomainAutoloader)->autoload(); + + $this->registerMigrations(); } } diff --git a/src/Support/Domain.php b/src/Support/Domain.php index eb3fd32..f6172fb 100644 --- a/src/Support/Domain.php +++ b/src/Support/Domain.php @@ -121,7 +121,14 @@ public function guessNamespaceFromName(string $name): string public function object(string $type, string $name, bool $absolute = false): DomainObject { + $namespaceResolver = app('ddd')->getApplicationLayerNamespaceResolver(); + $namespace = match (true) { + is_callable($namespaceResolver) => $namespaceResolver( + domain: $this->domainWithSubdomain, + type: $type, + object: $name + ), $absolute => $this->namespace->root, str($name)->startsWith('\\') => $this->guessNamespaceFromName($name), default => $this->namespaceFor($type), @@ -132,14 +139,16 @@ public function object(string $type, string $name, bool $absolute = false): Doma ->trim('\\') ->toString(); + $fullyQualifiedName = $namespace.'\\'.$baseName; + return new DomainObject( name: $baseName, domain: $this->domain, namespace: $namespace, - fullyQualifiedName: $namespace.'\\'.$baseName, + fullyQualifiedName: $fullyQualifiedName, path: DomainResolver::isApplicationLayer($type) - ? $this->pathInApplicationLayer($namespace.'\\'.$baseName) - : $this->path($namespace.'\\'.$baseName), + ? $this->pathInApplicationLayer($fullyQualifiedName) + : $this->path($fullyQualifiedName), type: $type ); } diff --git a/src/Support/DomainMigration.php b/src/Support/DomainMigration.php index 91e4a65..cd6c59f 100644 --- a/src/Support/DomainMigration.php +++ b/src/Support/DomainMigration.php @@ -16,18 +16,18 @@ public static function domainMigrationFolder(): string public static function cachePaths(): void { - DomainCache::set('domain-migration-folders', static::discoverPaths()); + DomainCache::set('domain-migration-paths', static::discoverPaths()); } public static function clearCache(): void { - DomainCache::forget('domain-migration-folders'); + DomainCache::forget('domain-migration-paths'); } public static function paths(): array { - return DomainCache::has('domain-migration-folders') - ? DomainCache::get('domain-migration-folders') + return DomainCache::has('domain-migration-paths') + ? DomainCache::get('domain-migration-paths') : static::discoverPaths(); } diff --git a/src/Support/DomainResolver.php b/src/Support/DomainResolver.php index a7423f3..d65f93c 100644 --- a/src/Support/DomainResolver.php +++ b/src/Support/DomainResolver.php @@ -35,9 +35,12 @@ public static function domainRootNamespace(): ?string return config('ddd.domain_namespace'); } + /** + * Get the current configured root application layer namespace. + */ public static function applicationLayerRootNamespace(): ?string { - return config('ddd.application_layer.namespace', 'App\Modules'); + return config('ddd.application_layer.namespace'); } /** @@ -50,29 +53,25 @@ public static function getRelativeObjectNamespace(string $type): string return config("ddd.namespaces.{$type}", str($type)->plural()->studly()->toString()); } + /** + * Determine whether a given object type is part of the application layer. + */ public static function isApplicationLayer(string $type): bool { - $applicationObjects = config('ddd.application_layer.objects', ['controller', 'request']); - - return in_array($type, $applicationObjects); - } - - // public static function getDomainControllerNamespace(string $domain, ?string $controller = null): string - // { - // $controllerRootNamespace = app()->getNamespace() . 'Http\Controllers'; - - // $namespace = collect([ - // $controllerRootNamespace, - // $domain, - // ])->filter()->implode('\\'); + $filter = app('ddd')->getApplicationLayerFilter() ?? function (string $type) { + $applicationObjects = config('ddd.application_layer.objects', ['controller', 'request']); - // if ($controller) { - // $namespace .= "\\{$controller}"; - // } + return in_array($type, $applicationObjects); + }; - // return $namespace; - // } + return $filter($type); + } + /** + * Resolve the root namespace for a given domain object type. + * + * @param string $type The domain object type. + */ public static function resolveRootNamespace(string $type): ?string { return static::isApplicationLayer($type) @@ -80,19 +79,30 @@ public static function resolveRootNamespace(string $type): ?string : static::domainRootNamespace(); } + /** + * Get the fully qualified namespace for a domain object. + * + * @param string $domain The domain name. + * @param string $type The domain object type. + * @param string|null $object The domain object name. + */ public static function getDomainObjectNamespace(string $domain, string $type, ?string $object = null): string { - $namespace = collect([ - static::resolveRootNamespace($type), - $domain, - static::getRelativeObjectNamespace($type), - ])->filter()->implode('\\'); - - if ($object) { - $namespace .= "\\{$object}"; - } + $resolver = app('ddd')->getApplicationLayerNamespaceResolver() ?? function (string $domain, string $type, ?string $object) { + $namespace = collect([ + static::resolveRootNamespace($type), + $domain, + static::getRelativeObjectNamespace($type), + ])->filter()->implode('\\'); + + if ($object) { + $namespace .= "\\{$object}"; + } + + return $namespace; + }; - return $namespace; + return $resolver($domain, $type, $object); } /** diff --git a/tests/.skeleton/src/Domain/Invoicing/Database/Migrations/2024_10_14_215911_do_nothing.php b/tests/.skeleton/src/Domain/Invoicing/Database/Migrations/2024_10_14_215911_do_nothing.php new file mode 100644 index 0000000..88fa2f3 --- /dev/null +++ b/tests/.skeleton/src/Domain/Invoicing/Database/Migrations/2024_10_14_215911_do_nothing.php @@ -0,0 +1,24 @@ + 'app/Modules', + 'namespace' => 'App\Modules', + ]); + + $this->setupTestApplication(); +})->markTestIncomplete('wip'); + +it('can register a custom application layer namespace resolver', function () { + DDD::resolveApplicationLayerNamespaceUsing(function (string $domain, string $type, ?string $object) { + // src/App/Api/Controllers//ApiController.php + return match ($type) { + // 'command' => 'Api\\' . Str::plural($domain) . '\Commands', + // 'provider' => 'App\Modules\\' . Str::plural($domain) . '\Providers', + // 'model' => 'App\Modules\\' . Str::plural($domain) . '\Models', + default => null, + }; + }); +}); diff --git a/tests/Command/CacheTest.php b/tests/Command/CacheTest.php index 81a3587..6512f96 100644 --- a/tests/Command/CacheTest.php +++ b/tests/Command/CacheTest.php @@ -2,6 +2,7 @@ use Illuminate\Support\Facades\Artisan; use Lunarstorm\LaravelDDD\Support\DomainCache; +use Lunarstorm\LaravelDDD\Support\Path; beforeEach(function () { $this->setupTestApplication(); @@ -10,13 +11,15 @@ it('can cache discovered domain providers and commands', function () { expect(DomainCache::get('domain-providers'))->toBeNull(); - expect(DomainCache::get('domain-commands'))->toBeNull(); + expect(DomainCache::get('domain-migration-paths'))->toBeNull(); $this ->artisan('ddd:cache') - ->expectsOutputToContain('Domain providers cached successfully.') - ->expectsOutputToContain('Domain commands cached successfully.') + ->expectsOutputToContain('Caching DDD providers, commands, migration paths.') + ->expectsOutputToContain('domain providers') + ->expectsOutputToContain('domain commands') + ->expectsOutputToContain('domain migration paths') ->execute(); expect(DomainCache::get('domain-providers')) @@ -24,6 +27,9 @@ expect(DomainCache::get('domain-commands')) ->toContain('Domain\Invoicing\Commands\InvoiceDeliver'); + + expect(DomainCache::get('domain-migration-paths')) + ->toContain(base_path(Path::normalize('src/Domain/Invoicing/Database/Migrations'))); }); it('can clear the cache', function () { @@ -31,6 +37,7 @@ expect(DomainCache::get('domain-providers'))->not->toBeNull(); expect(DomainCache::get('domain-commands'))->not->toBeNull(); + expect(DomainCache::get('domain-migration-paths'))->not->toBeNull(); $this ->artisan('ddd:clear') @@ -39,6 +46,7 @@ expect(DomainCache::get('domain-providers'))->toBeNull(); expect(DomainCache::get('domain-commands'))->toBeNull(); + expect(DomainCache::get('domain-migration-paths'))->toBeNull(); }); it('will not be cleared by laravel cache clearing', function () { diff --git a/tests/Command/ListTest.php b/tests/Command/ListTest.php index 36afcee..6a1443b 100644 --- a/tests/Command/ListTest.php +++ b/tests/Command/ListTest.php @@ -13,6 +13,11 @@ '--domain' => 'Customer', ]); + $this->artisan('ddd:value', [ + 'name' => 'Subtotal', + '--domain' => 'Shared', + ]); + $this->expectedDomains = [ 'Customer', 'Invoicing', diff --git a/tests/Generator/ExtendedCommandsTest.php b/tests/Generator/ExtendedCommandsTest.php index fbba5ed..52d8959 100644 --- a/tests/Generator/ExtendedCommandsTest.php +++ b/tests/Generator/ExtendedCommandsTest.php @@ -44,6 +44,7 @@ 'job' => ['job', 'SomeJob'], 'listener' => ['listener', 'SomeListener'], 'mail' => ['mail', 'SomeMail'], + 'middleware' => ['middleware', 'SomeMiddleware'], 'notification' => ['notification', 'SomeNotification'], 'observer' => ['observer', 'SomeObserver'], 'policy' => ['policy', 'SomePolicy'], diff --git a/tests/Support/DomainResolverTest.php b/tests/Support/DomainResolverTest.php index 37b63b9..846c815 100644 --- a/tests/Support/DomainResolverTest.php +++ b/tests/Support/DomainResolverTest.php @@ -13,6 +13,11 @@ '--domain' => 'Customer', ]); + $this->artisan('ddd:value', [ + 'name' => 'Subtotal', + '--domain' => 'Shared', + ]); + $this->expectedDomains = [ 'Customer', 'Invoicing', From e621734b07a3542b9507263379059d390248f979 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 18:45:13 -0400 Subject: [PATCH 017/169] Update change notes. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39353f8..fc7cf3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,13 @@ All notable changes to `laravel-ddd` will be documented in this file. 'objects' => [ 'controller', 'request', + 'middleware', ], ], ``` - Added `ddd:controller` to generate domain-specific controllers in the application layer. - Added `ddd:request` to generate domain-spefic requests in the application layer. +- Added `ddd:middleware` to generate domain-specific middleware in the application layer. - Added `ddd:migration` to generate domain migrations. - Migration folders across domains will be registered and scanned when running `php artisan migrate`, in addition to the standard application `database/migrations` path. - Added `ddd:seeder` to generate domain seeders. From 7aa28014aa3889b05eb9a1dc9466aa17848d1084 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 18:51:22 -0400 Subject: [PATCH 018/169] Fix phpstan issue. --- src/Support/Domain.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Support/Domain.php b/src/Support/Domain.php index f6172fb..fa15066 100644 --- a/src/Support/Domain.php +++ b/src/Support/Domain.php @@ -123,12 +123,11 @@ public function object(string $type, string $name, bool $absolute = false): Doma { $namespaceResolver = app('ddd')->getApplicationLayerNamespaceResolver(); - $namespace = match (true) { - is_callable($namespaceResolver) => $namespaceResolver( - domain: $this->domainWithSubdomain, - type: $type, - object: $name - ), + $resolvedNamespace = is_callable($namespaceResolver) + ? $namespaceResolver($this->domainWithSubdomain, $type, $name) + : null; + + $namespace = $resolvedNamespace ?? match (true) { $absolute => $this->namespace->root, str($name)->startsWith('\\') => $this->guessNamespaceFromName($name), default => $this->namespaceFor($type), From af25cf9dd6627cd3dd118c955f36aa4d0c34e8e4 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 18:57:44 -0400 Subject: [PATCH 019/169] Fix test. --- tests/Command/CacheTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Command/CacheTest.php b/tests/Command/CacheTest.php index 6512f96..e0ae693 100644 --- a/tests/Command/CacheTest.php +++ b/tests/Command/CacheTest.php @@ -2,14 +2,13 @@ use Illuminate\Support\Facades\Artisan; use Lunarstorm\LaravelDDD\Support\DomainCache; -use Lunarstorm\LaravelDDD\Support\Path; beforeEach(function () { $this->setupTestApplication(); DomainCache::clear(); }); -it('can cache discovered domain providers and commands', function () { +it('can cache discovered domain providers, commands, migrations', function () { expect(DomainCache::get('domain-providers'))->toBeNull(); expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); @@ -28,8 +27,9 @@ expect(DomainCache::get('domain-commands')) ->toContain('Domain\Invoicing\Commands\InvoiceDeliver'); - expect(DomainCache::get('domain-migration-paths')) - ->toContain(base_path(Path::normalize('src/Domain/Invoicing/Database/Migrations'))); + $paths = collect(DomainCache::get('domain-migration-paths'))->join("\n"); + + expect($paths)->toContainFilepath('src/Domain/Invoicing/Database/Migrations'); }); it('can clear the cache', function () { From 0a4585f17e9cd037bc7fd72ae5cdd1039ec68df4 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 19:33:20 -0400 Subject: [PATCH 020/169] Normalize paths for windows. --- src/Support/DomainMigration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Support/DomainMigration.php b/src/Support/DomainMigration.php index cd6c59f..9f3a939 100644 --- a/src/Support/DomainMigration.php +++ b/src/Support/DomainMigration.php @@ -57,7 +57,7 @@ public static function discoverPaths(): array $finder = static::finder($paths); return Lody::filesFromFinder($finder) - ->map(fn ($file) => $file->getPath()) + ->map(fn ($file) => Path::normalize($file->getPath())) ->unique() ->values() ->toArray(); From 75c3cab510fdf3bd9d26fccd4d91c45058f44a83 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Mon, 14 Oct 2024 21:39:46 -0400 Subject: [PATCH 021/169] Support application layer paths outside app folder. --- src/Support/Domain.php | 4 ++-- src/Support/DomainResolver.php | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Support/Domain.php b/src/Support/Domain.php index fa15066..626302f 100644 --- a/src/Support/Domain.php +++ b/src/Support/Domain.php @@ -90,12 +90,12 @@ public function pathInApplicationLayer(?string $path = null): string } $path = str($path) - ->replace(app()->getNamespace(), '') + ->replace(DomainResolver::applicationLayerRootNamespace(), '') ->replace(['\\', '/'], DIRECTORY_SEPARATOR) ->append('.php') ->toString(); - return Path::join('app', $path); + return Path::join(DomainResolver::applicationLayerPath(), $path); } public function relativePath(string $path = ''): string diff --git a/src/Support/DomainResolver.php b/src/Support/DomainResolver.php index d65f93c..9e9faef 100644 --- a/src/Support/DomainResolver.php +++ b/src/Support/DomainResolver.php @@ -35,6 +35,14 @@ public static function domainRootNamespace(): ?string return config('ddd.domain_namespace'); } + /** + * Get the current configured application layer path. + */ + public static function applicationLayerPath(): ?string + { + return config('ddd.application_layer.path'); + } + /** * Get the current configured root application layer namespace. */ From edc6935005fc876b24fb6b7046ce814ba8e2971e Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 15 Oct 2024 09:00:42 -0400 Subject: [PATCH 022/169] Add peer dependencies section. --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 520acb3..e505ba8 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,20 @@ You may initialize the package using the `ddd:install` artisan command. This wil php artisan ddd:install ``` +### Peer Dependencies +The following additional packages are suggested while working with this package. + +Data Transfer Objects: [spatie/laravel-data](https://github.com/spatie/laravel-data) +```bash +composer require spatie/laravel-data +``` + +Actions: [lorisleiva/laravel-actions](https://github.com/lorisleiva/laravel-actions) +```bash +composer require lorisleiva/laravel-actions +``` +The default stubs for DTOs and Actions are based on these packages. + ### Deployment In production, run `ddd:cache` during the deployment process to [optimize autoloading](#autoloading-in-production). ```bash @@ -179,6 +193,7 @@ Autoloading behaviour can be configured with the `ddd.autoload` configuration op 'commands' => true, 'policies' => true, 'factories' => true, + 'migrations' => true, ], ``` ### Service Providers @@ -195,14 +210,18 @@ 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. +### Migrations +When `ddd.autoload.migrations` is enabled, paths within the domain layer matching the configured `ddd.namespaces.migration` namespace will be auto-registered as a database migration path. + ### 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. +To specify folders or paths that should be skipped during autoloading class 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', ], ``` +Note that ignoring folders only applies to class-based autoloading: Service Providers, Console Commands, Policies, and Factories. 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 From 5280bd77bc752830f8ab9ed1e4b19795e79a4cbc Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 15 Oct 2024 09:34:25 -0400 Subject: [PATCH 023/169] Document the Application Layer. --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index e505ba8..a773062 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,40 @@ php artisan ddd:clear ``` ## Advanced Usage +### Application Layer (since 1.2) +Some objects interact with the domain layer, but are not part of the domain layer themselves. By default, these include: `controller`, `request`, `middleware`. You may customize the path, namespace, and which `ddd:*` objects belong in the application layer. +```php +// In config/ddd.php +'application_layer' => [ + 'path' => 'app/Modules', + 'namespace' => 'App\Modules', + 'objects' => [ + 'controller', + 'request', + 'middleware', + ], +], +``` +The default configuration above will result in the following: +```bash +ddd:model Invoicing:Invoice --controller --resource --requests +``` +Output: +``` +├─ app +| └─ Modules +│ └─ Invoicing +│ ├─ Controllers +│ │ └─ InvoiceController.php +│ └─ Requests +│ ├─ StoreInvoiceRequest.php +│ └─ UpdateInvoiceRequest.php +├─ src/Domain + └── Invoicing + └── Models + └── Invoice.php +``` + ### Nested Objects For any `ddd:*` generator command, nested objects can be specified with forward slashes. ```bash From e2ff98dc97dc3e57b36046f9162567ecc60b7f4c Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 15 Oct 2024 09:42:51 -0400 Subject: [PATCH 024/169] Update wording. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a773062..10a3a02 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ php artisan ddd:install ``` ### Peer Dependencies -The following additional packages are suggested while working with this package. +The following additional packages are suggested (but not required) while working with this package. Data Transfer Objects: [spatie/laravel-data](https://github.com/spatie/laravel-data) ```bash @@ -31,7 +31,7 @@ Actions: [lorisleiva/laravel-actions](https://github.com/lorisleiva/laravel-acti ```bash composer require lorisleiva/laravel-actions ``` -The default stubs for DTOs and Actions are based on these packages. +The default DTO and Action stubs of this package use the above dependencies. Of course, you may customize the stubs to suit your needs if you aren't using the above packages. ### Deployment In production, run `ddd:cache` during the deployment process to [optimize autoloading](#autoloading-in-production). From b7be8708931d323c04d52b609b6a9e1a09b13694 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 15 Oct 2024 09:50:54 -0400 Subject: [PATCH 025/169] Minor wording. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 10a3a02..28328a2 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ 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. ### Migrations -When `ddd.autoload.migrations` is enabled, paths within the domain layer matching the configured `ddd.namespaces.migration` namespace will be auto-registered as a database migration path. +When `ddd.autoload.migrations` is enabled, paths within the domain layer matching the configured `ddd.namespaces.migration` namespace will be auto-registered as a database migration path and recognized by `php artisan migrate`. ### Ignoring Paths During Autoloading To specify folders or paths that should be skipped during autoloading class discovery, add them to the `ddd.autoload_ignore` configuration option. By default, the `Tests` and `Migrations` folders are ignored. From adb5d77c44885d5104302fec1a843273d9774a12 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 15 Oct 2024 14:18:35 -0400 Subject: [PATCH 026/169] Support optimize commands in Laravel 11.27.1 --- README.md | 9 ++++++--- src/LaravelDDDServiceProvider.php | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 28328a2..c1707ad 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ In production, run `ddd:cache` during the deployment process to [optimize autolo ```bash php artisan ddd:cache ``` +Note: Since Laravel 11.27.1, `ddd:cache` will automatically be invoked during Laravel's `optimize` command, in which case you do not need to run `ddd:cache` separately if your production environment already runs `optimize`. ### Version Compatibility Laravel | LaravelDDD | | @@ -145,9 +146,9 @@ Output: │ ├─ StoreInvoiceRequest.php │ └─ UpdateInvoiceRequest.php ├─ src/Domain - └── Invoicing - └── Models - └── Invoice.php + └─ Invoicing + └─ Models + └─ Invoice.php ``` ### Nested Objects @@ -287,6 +288,8 @@ You may disable autoloading by setting the respective autoload options to `false ## 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. +Note: Since Laravel 11.27.1, `ddd:cache` and `ddd:clear` will automatically be invoked when running Laravel's `optimize` and `optimize:clear` respectively. If this applies to you and you are already running `optimize` in production, you don't need to manually run `ddd:cache`. + ## Configuration File diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index 82cd2f5..c2f9d95 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -89,6 +89,14 @@ public function packageBooted() $this->publishes([ $this->package->basePath('/../stubs') => resource_path("stubs/{$this->package->shortName()}"), ], "{$this->package->shortName()}-stubs"); + + if ($this->app->runningInConsole() && method_exists($this, 'optimizes')) { + $this->optimizes( + optimize: 'ddd:cache', + clear: 'ddd:clear', + key: 'ddd:cache', + ); + } } public function packageRegistered() From 02c75b013aa990cda8c4c08753a58757b89ac6ad Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 15 Oct 2024 14:53:29 -0400 Subject: [PATCH 027/169] Update cache tests. --- tests/Command/CacheTest.php | 52 +++++++++++++++++++++++++++++--- tests/Fixtures/Enums/Feature.php | 1 + 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/tests/Command/CacheTest.php b/tests/Command/CacheTest.php index e0ae693..20259d7 100644 --- a/tests/Command/CacheTest.php +++ b/tests/Command/CacheTest.php @@ -2,10 +2,16 @@ use Illuminate\Support\Facades\Artisan; use Lunarstorm\LaravelDDD\Support\DomainCache; +use Lunarstorm\LaravelDDD\Tests\Fixtures\Enums\Feature; beforeEach(function () { $this->setupTestApplication(); + + config(['cache.default' => 'file']); + DomainCache::clear(); + Artisan::call('cache:clear'); + Artisan::call('optimize:clear'); }); it('can cache discovered domain providers, commands, migrations', function () { @@ -50,23 +56,59 @@ }); 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(); + expect(DomainCache::get('domain-migration-paths'))->toBeNull(); $this->artisan('ddd:cache')->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); expect(DomainCache::get('domain-commands'))->not->toBeNull(); + expect(DomainCache::get('domain-migration-paths'))->not->toBeNull(); $this->artisan('cache:clear')->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); expect(DomainCache::get('domain-commands'))->not->toBeNull(); + expect(DomainCache::get('domain-migration-paths'))->not->toBeNull(); - $this->artisan('optimize:clear')->execute(); + if (Feature::LaravelPackageOptimizeCommands->missing()) { + $this->artisan('optimize:clear')->execute(); - expect(DomainCache::get('domain-providers'))->not->toBeNull(); - expect(DomainCache::get('domain-commands'))->not->toBeNull(); + expect(DomainCache::get('domain-providers'))->not->toBeNull(); + expect(DomainCache::get('domain-commands'))->not->toBeNull(); + expect(DomainCache::get('domain-migration-paths'))->not->toBeNull(); + } }); + +describe('laravel optimize', function () { + test('optimize will include ddd:cache', function () { + config(['cache.default' => 'file']); + + expect(DomainCache::get('domain-providers'))->toBeNull(); + expect(DomainCache::get('domain-commands'))->toBeNull(); + expect(DomainCache::get('domain-migration-paths'))->toBeNull(); + + $this->artisan('optimize')->execute(); + + expect(DomainCache::get('domain-providers'))->not->toBeNull(); + expect(DomainCache::get('domain-commands'))->not->toBeNull(); + expect(DomainCache::get('domain-migration-paths'))->not->toBeNull(); + }); + + test('optimize:clear will clear ddd cache', function () { + config(['cache.default' => 'file']); + + $this->artisan('ddd:cache')->execute(); + + expect(DomainCache::get('domain-providers'))->not->toBeNull(); + expect(DomainCache::get('domain-commands'))->not->toBeNull(); + expect(DomainCache::get('domain-migration-paths'))->not->toBeNull(); + + $this->artisan('optimize:clear')->execute(); + + expect(DomainCache::get('domain-providers'))->toBeNull(); + expect(DomainCache::get('domain-commands'))->toBeNull(); + expect(DomainCache::get('domain-migration-paths'))->toBeNull(); + }); +})->skipOnLaravelVersionsBelow(Feature::LaravelPackageOptimizeCommands->value); diff --git a/tests/Fixtures/Enums/Feature.php b/tests/Fixtures/Enums/Feature.php index 21c2aa1..9b82fbb 100644 --- a/tests/Fixtures/Enums/Feature.php +++ b/tests/Fixtures/Enums/Feature.php @@ -7,6 +7,7 @@ enum Feature: string case PromptForMissingInput = '9.49.0'; case IncludeFilepathInGeneratorCommandOutput = '9.32.0'; case LaravelPromptsPackage = '10.17'; + case LaravelPackageOptimizeCommands = '11.27.1'; public function exists(): bool { From cfc4515b26d1dbbe6bf9842763df2721f57b0601 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 15 Oct 2024 15:13:07 -0400 Subject: [PATCH 028/169] Ensure optimize:clear after each test. --- tests/Command/CacheTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Command/CacheTest.php b/tests/Command/CacheTest.php index 20259d7..f7aec3f 100644 --- a/tests/Command/CacheTest.php +++ b/tests/Command/CacheTest.php @@ -10,8 +10,10 @@ config(['cache.default' => 'file']); DomainCache::clear(); - Artisan::call('cache:clear'); - Artisan::call('optimize:clear'); +}); + +afterEach(function () { + $this->artisan('optimize:clear')->execute(); }); it('can cache discovered domain providers, commands, migrations', function () { From 79ea5ad4bff59d0f630c8d31838883448450dec7 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 15 Oct 2024 17:16:49 -0400 Subject: [PATCH 029/169] Update internals and implement custom namespace resolving. --- src/Commands/Concerns/CallsDomainCommands.php | 31 ------- .../Concerns/ForwardsToDomainCommands.php | 62 +++++++++++++ .../Concerns/ResolvesDomainFromInput.php | 2 + src/Commands/DomainControllerMakeCommand.php | 6 +- src/Commands/DomainModelMakeCommand.php | 86 +------------------ src/DomainManager.php | 29 +++++-- src/Facades/DDD.php | 2 +- src/Support/Domain.php | 16 ++-- src/Support/DomainResolver.php | 24 ++++-- src/ValueObjects/DomainCommandContext.php | 51 +++++++++++ .../NamespaceResolverTest.php | 49 ++++++++--- tests/TestCase.php | 1 + 12 files changed, 209 insertions(+), 150 deletions(-) delete mode 100644 src/Commands/Concerns/CallsDomainCommands.php create mode 100644 src/Commands/Concerns/ForwardsToDomainCommands.php create mode 100644 src/ValueObjects/DomainCommandContext.php diff --git a/src/Commands/Concerns/CallsDomainCommands.php b/src/Commands/Concerns/CallsDomainCommands.php deleted file mode 100644 index c29914d..0000000 --- a/src/Commands/Concerns/CallsDomainCommands.php +++ /dev/null @@ -1,31 +0,0 @@ -getNameInput(), '/') - ? Str::beforeLast($this->getNameInput(), '/') - : null; - - $nameWithSubfolder = $subfolder ? "{$subfolder}/{$arguments['name']}" : $arguments['name']; - - return match ($command) { - 'make:request' => $this->runCommand('ddd:request', [ - 'name' => $nameWithSubfolder, - '--domain' => $this->domain->dotName, - ], $this->output), - - 'make:model' => $this->runCommand('ddd:model', [ - 'name' => $nameWithSubfolder, - '--domain' => $this->domain->dotName, - ], $this->output), - - default => $this->runCommand($command, $arguments, $this->output), - }; - } -} diff --git a/src/Commands/Concerns/ForwardsToDomainCommands.php b/src/Commands/Concerns/ForwardsToDomainCommands.php new file mode 100644 index 0000000..6586bd7 --- /dev/null +++ b/src/Commands/Concerns/ForwardsToDomainCommands.php @@ -0,0 +1,62 @@ +getNameInput(), '/') + ? Str::beforeLast($this->getNameInput(), '/') + : null; + + $nameWithSubfolder = $subfolder ? "{$subfolder}/{$arguments['name']}" : $arguments['name']; + + return match ($command) { + 'make:request' => $this->runCommand('ddd:request', [ + ...$arguments, + 'name' => $nameWithSubfolder, + '--domain' => $this->domain->dotName, + ], $this->output), + + 'make:model' => $this->runCommand('ddd:model', [ + ...$arguments, + 'name' => $nameWithSubfolder, + '--domain' => $this->domain->dotName, + ], $this->output), + + 'make:factory' => $this->runCommand('ddd:factory', [ + ...$arguments, + 'name' => $nameWithSubfolder, + '--domain' => $this->domain->dotName, + ], $this->output), + + 'make:policy' => $this->runCommand('ddd:policy', [ + ...$arguments, + 'name' => $nameWithSubfolder, + '--domain' => $this->domain->dotName, + ], $this->output), + + 'make:migration' => $this->runCommand('ddd:migration', [ + ...$arguments, + '--domain' => $this->domain->dotName, + ], $this->output), + + 'make:seeder' => $this->runCommand('ddd:seeder', [ + ...$arguments, + 'name' => $nameWithSubfolder, + '--domain' => $this->domain->dotName, + ], $this->output), + + 'make:controller' => $this->runCommand('ddd:controller', [ + ...$arguments, + 'name' => $nameWithSubfolder, + '--domain' => $this->domain->dotName, + ], $this->output), + + default => $this->runCommand($command, $arguments, $this->output), + }; + } +} diff --git a/src/Commands/Concerns/ResolvesDomainFromInput.php b/src/Commands/Concerns/ResolvesDomainFromInput.php index bd65dd8..9192aee 100644 --- a/src/Commands/Concerns/ResolvesDomainFromInput.php +++ b/src/Commands/Concerns/ResolvesDomainFromInput.php @@ -110,5 +110,7 @@ protected function beforeHandle() } $this->input->setArgument('name', $nameInput); + + app('ddd')->captureCommandContext($this, $this->domain, $this->guessObjectType()); } } diff --git a/src/Commands/DomainControllerMakeCommand.php b/src/Commands/DomainControllerMakeCommand.php index 751a303..aa2a373 100644 --- a/src/Commands/DomainControllerMakeCommand.php +++ b/src/Commands/DomainControllerMakeCommand.php @@ -3,14 +3,14 @@ namespace Lunarstorm\LaravelDDD\Commands; use Illuminate\Routing\Console\ControllerMakeCommand; -use Lunarstorm\LaravelDDD\Commands\Concerns\CallsDomainCommands; +use Lunarstorm\LaravelDDD\Commands\Concerns\ForwardsToDomainCommands; use Lunarstorm\LaravelDDD\Commands\Concerns\ResolvesDomainFromInput; use function Laravel\Prompts\confirm; class DomainControllerMakeCommand extends ControllerMakeCommand { - use CallsDomainCommands, + use ForwardsToDomainCommands, ResolvesDomainFromInput; protected $name = 'ddd:controller'; @@ -51,7 +51,7 @@ protected function buildFormRequestReplacements(array $replace, $modelClass) ]; if ($this->option('requests')) { - $namespace = $this->domain->namespaceFor('request'); + $namespace = $this->domain->namespaceFor('request', $this->getNameInput()); [$storeRequestClass, $updateRequestClass] = $this->generateFormRequests( $modelClass, diff --git a/src/Commands/DomainModelMakeCommand.php b/src/Commands/DomainModelMakeCommand.php index ebb40a0..c92ccdd 100644 --- a/src/Commands/DomainModelMakeCommand.php +++ b/src/Commands/DomainModelMakeCommand.php @@ -4,13 +4,14 @@ use Illuminate\Foundation\Console\ModelMakeCommand; use Illuminate\Support\Str; +use Lunarstorm\LaravelDDD\Commands\Concerns\ForwardsToDomainCommands; use Lunarstorm\LaravelDDD\Commands\Concerns\ResolvesDomainFromInput; -use Lunarstorm\LaravelDDD\Commands\Migration\DomainMigrateMakeCommand; use Lunarstorm\LaravelDDD\Support\DomainResolver; class DomainModelMakeCommand extends ModelMakeCommand { - use ResolvesDomainFromInput; + use ForwardsToDomainCommands, + ResolvesDomainFromInput; protected $name = 'ddd:model'; @@ -58,87 +59,6 @@ protected function buildClass($name) return $stub; } - protected function createFactory() - { - $factory = Str::studly($this->argument('name')); - - $this->call(DomainFactoryMakeCommand::class, [ - 'name' => $factory.'Factory', - '--domain' => $this->domain->dotName, - '--model' => $this->qualifyClass($this->getNameInput()), - ]); - } - - protected function createMigration() - { - $table = Str::snake(Str::pluralStudly(class_basename($this->argument('name')))); - - if ($this->option('pivot')) { - $table = Str::singular($table); - } - - $this->call(DomainMigrateMakeCommand::class, [ - 'name' => "create_{$table}_table", - '--domain' => $this->domain->dotName, - '--create' => $table, - ]); - } - - /** - * Create a seeder file for the model. - * - * @return void - */ - protected function createSeeder() - { - $seeder = Str::studly(class_basename($this->argument('name'))); - - $this->call(DomainSeederMakeCommand::class, [ - 'name' => "{$seeder}Seeder", - '--domain' => $this->domain->dotName, - ]); - } - - /** - * Create a controller for the model. - * - * @return void - */ - protected function createController() - { - $controller = Str::studly(class_basename($this->argument('name'))); - - $modelName = $this->qualifyClass($this->getNameInput()); - - $controllerName = "{$controller}Controller"; - - $this->call(DomainControllerMakeCommand::class, array_filter([ - 'name' => $controllerName, - '--domain' => $this->domain->dotName, - '--model' => $this->option('resource') || $this->option('api') ? $modelName : null, - '--api' => $this->option('api'), - '--requests' => $this->option('requests') || $this->option('all'), - '--test' => $this->option('test'), - '--pest' => $this->option('pest'), - ])); - } - - /** - * Create a policy file for the model. - * - * @return void - */ - protected function createPolicy() - { - $policy = Str::studly(class_basename($this->argument('name'))); - - $this->call(DomainPolicyMakeCommand::class, [ - 'name' => "{$policy}Policy", - '--domain' => $this->domain->dotName, - '--model' => $this->qualifyClass($this->getNameInput()), - ]); - } - protected function createBaseModelIfNeeded() { if (! $this->shouldCreateBaseModel()) { diff --git a/src/DomainManager.php b/src/DomainManager.php index 8b5e8c4..13d465a 100755 --- a/src/DomainManager.php +++ b/src/DomainManager.php @@ -2,6 +2,10 @@ namespace Lunarstorm\LaravelDDD; +use Illuminate\Console\Command; +use Lunarstorm\LaravelDDD\Support\Domain; +use Lunarstorm\LaravelDDD\ValueObjects\DomainCommandContext; + class DomainManager { /** @@ -23,13 +27,16 @@ class DomainManager * * @var callable|null */ - protected $applicationLayerNamespaceResolver; + protected $namespaceResolver; + + protected ?DomainCommandContext $commandContext; public function __construct() { $this->autoloadFilter = null; $this->applicationLayerFilter = null; - $this->applicationLayerNamespaceResolver = null; + $this->namespaceResolver = null; + $this->commandContext = null; } public function filterAutoloadPathsUsing(callable $filter): void @@ -52,13 +59,23 @@ public function getApplicationLayerFilter(): ?callable return $this->applicationLayerFilter; } - public function resolveApplicationLayerNamespaceUsing(callable $resolver): void + public function resolveNamespaceUsing(callable $resolver): void + { + $this->namespaceResolver = $resolver; + } + + public function getNamespaceResolver(): ?callable + { + return $this->namespaceResolver; + } + + public function captureCommandContext(Command $command, ?Domain $domain, ?string $type): void { - $this->applicationLayerNamespaceResolver = $resolver; + $this->commandContext = DomainCommandContext::fromCommand($command, $domain, $type); } - public function getApplicationLayerNamespaceResolver(): ?callable + public function getCommandContext(): ?DomainCommandContext { - return $this->applicationLayerNamespaceResolver; + return $this->commandContext; } } diff --git a/src/Facades/DDD.php b/src/Facades/DDD.php index d3bd5ea..a5aba45 100644 --- a/src/Facades/DDD.php +++ b/src/Facades/DDD.php @@ -8,7 +8,7 @@ * @see \Lunarstorm\LaravelDDD\DomainManager * * @method static void filterAutoloadPathsUsing(callable $filter) - * @method static void resolveApplicationLayerNamespaceUsing(callable $resolver) + * @method static void resolveNamespaceUsing(callable $resolver) */ class DDD extends Facade { diff --git a/src/Support/Domain.php b/src/Support/Domain.php index 626302f..225fe61 100644 --- a/src/Support/Domain.php +++ b/src/Support/Domain.php @@ -103,9 +103,9 @@ public function relativePath(string $path = ''): string return collect([$this->domain, $path])->filter()->implode(DIRECTORY_SEPARATOR); } - public function namespaceFor(string $type): string + public function namespaceFor(string $type, ?string $name = null): string { - return DomainResolver::getDomainObjectNamespace($this->domainWithSubdomain, $type); + return DomainResolver::getDomainObjectNamespace($this->domainWithSubdomain, $type, $name); } public function guessNamespaceFromName(string $name): string @@ -121,11 +121,15 @@ public function guessNamespaceFromName(string $name): string public function object(string $type, string $name, bool $absolute = false): DomainObject { - $namespaceResolver = app('ddd')->getApplicationLayerNamespaceResolver(); + $resolvedNamespace = null; - $resolvedNamespace = is_callable($namespaceResolver) - ? $namespaceResolver($this->domainWithSubdomain, $type, $name) - : null; + if (DomainResolver::isApplicationLayer($type)) { + $resolver = app('ddd')->getNamespaceResolver(); + + $resolvedNamespace = is_callable($resolver) + ? $resolver($this->domainWithSubdomain, $type, app('ddd')->getCommandContext()) + : null; + } $namespace = $resolvedNamespace ?? match (true) { $absolute => $this->namespace->root, diff --git a/src/Support/DomainResolver.php b/src/Support/DomainResolver.php index 9e9faef..f358e34 100644 --- a/src/Support/DomainResolver.php +++ b/src/Support/DomainResolver.php @@ -92,25 +92,37 @@ public static function resolveRootNamespace(string $type): ?string * * @param string $domain The domain name. * @param string $type The domain object type. - * @param string|null $object The domain object name. + * @param string|null $name The domain object name. */ - public static function getDomainObjectNamespace(string $domain, string $type, ?string $object = null): string + public static function getDomainObjectNamespace(string $domain, string $type, ?string $name = null): string { - $resolver = app('ddd')->getApplicationLayerNamespaceResolver() ?? function (string $domain, string $type, ?string $object) { + if (static::isApplicationLayer($type)) { + $customResolver = app('ddd')->getNamespaceResolver(); + + $resolved = is_callable($customResolver) + ? $customResolver($domain, $type, app('ddd')->getCommandContext()) + : null; + + if (! is_null($resolved)) { + return $resolved; + } + } + + $resolver = function (string $domain, string $type, ?string $name) { $namespace = collect([ static::resolveRootNamespace($type), $domain, static::getRelativeObjectNamespace($type), ])->filter()->implode('\\'); - if ($object) { - $namespace .= "\\{$object}"; + if ($name) { + $namespace .= "\\{$name}"; } return $namespace; }; - return $resolver($domain, $type, $object); + return $resolver($domain, $type, $name); } /** diff --git a/src/ValueObjects/DomainCommandContext.php b/src/ValueObjects/DomainCommandContext.php new file mode 100644 index 0000000..692ceed --- /dev/null +++ b/src/ValueObjects/DomainCommandContext.php @@ -0,0 +1,51 @@ +getName(), + domain: $domain?->domainWithSubdomain, + type: $type, + resource: $command->argument('name'), + arguments: $command->arguments(), + options: $command->options(), + ); + } + + public function hasOption(string $key): bool + { + return array_key_exists($key, $this->options); + } + + public function option(string $key): mixed + { + return data_get($this->options, $key); + } + + public function hasArgument(string $key): bool + { + return array_key_exists($key, $this->arguments); + } + + public function argument(string $key): mixed + { + return data_get($this->arguments, $key); + } +} diff --git a/tests/ApplicationLayer/NamespaceResolverTest.php b/tests/ApplicationLayer/NamespaceResolverTest.php index e3e7651..8e2502b 100644 --- a/tests/ApplicationLayer/NamespaceResolverTest.php +++ b/tests/ApplicationLayer/NamespaceResolverTest.php @@ -1,27 +1,48 @@ setupTestApplication(); +}); + +it('can register a custom namespace resolver', function () { Config::set('ddd.application_layer', [ - 'path' => 'app/Modules', - 'namespace' => 'App\Modules', + 'path' => 'src/App', + 'namespace' => 'App', ]); - $this->setupTestApplication(); -})->markTestIncomplete('wip'); - -it('can register a custom application layer namespace resolver', function () { - DDD::resolveApplicationLayerNamespaceUsing(function (string $domain, string $type, ?string $object) { - // src/App/Api/Controllers//ApiController.php - return match ($type) { - // 'command' => 'Api\\' . Str::plural($domain) . '\Commands', - // 'provider' => 'App\Modules\\' . Str::plural($domain) . '\Providers', - // 'model' => 'App\Modules\\' . Str::plural($domain) . '\Models', - default => null, - }; + DDD::resolveNamespaceUsing(function ( + string $domain, + string $type, + ?DomainCommandContext $context + ): ?string { + if ($type == 'controller' && $context->option('api')) { + return "App\\Api\\Controllers\\{$domain}"; + } + + return null; }); + + Artisan::call('ddd:controller', [ + 'name' => 'PaymentApiController', + '--domain' => 'Invoicing', + '--api' => true, + ]); + + $output = Artisan::output(); + + expect($output) + ->toContainFilepath('src/App/Api/Controllers/Invoicing/PaymentApiController.php'); + + $expectedPath = base_path('src/App/Api/Controllers/Invoicing/PaymentApiController.php'); + + expect(file_get_contents($expectedPath)) + ->toContain("namespace App\Api\Controllers\Invoicing;"); }); diff --git a/tests/TestCase.php b/tests/TestCase.php index fb69cab..b04ef5a 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -151,6 +151,7 @@ protected function cleanSlate() File::deleteDirectory(base_path('Custom')); File::deleteDirectory(base_path('src/Domain')); File::deleteDirectory(base_path('src/Domains')); + File::deleteDirectory(base_path('src/App')); File::deleteDirectory(app_path('Models')); File::deleteDirectory(base_path('bootstrap/cache/ddd')); From 81b922f906917c478762dae2d6f2703c53a2537c Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 15 Oct 2024 17:29:09 -0400 Subject: [PATCH 030/169] Apply namespace resolver to all types. --- src/Support/DomainResolver.php | 14 ++++++-------- tests/ApplicationLayer/NamespaceResolverTest.php | 6 +----- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/Support/DomainResolver.php b/src/Support/DomainResolver.php index f358e34..15257b4 100644 --- a/src/Support/DomainResolver.php +++ b/src/Support/DomainResolver.php @@ -96,16 +96,14 @@ public static function resolveRootNamespace(string $type): ?string */ public static function getDomainObjectNamespace(string $domain, string $type, ?string $name = null): string { - if (static::isApplicationLayer($type)) { - $customResolver = app('ddd')->getNamespaceResolver(); + $customResolver = app('ddd')->getNamespaceResolver(); - $resolved = is_callable($customResolver) - ? $customResolver($domain, $type, app('ddd')->getCommandContext()) - : null; + $resolved = is_callable($customResolver) + ? $customResolver($domain, $type, app('ddd')->getCommandContext()) + : null; - if (! is_null($resolved)) { - return $resolved; - } + if (! is_null($resolved)) { + return $resolved; } $resolver = function (string $domain, string $type, ?string $name) { diff --git a/tests/ApplicationLayer/NamespaceResolverTest.php b/tests/ApplicationLayer/NamespaceResolverTest.php index 8e2502b..870e902 100644 --- a/tests/ApplicationLayer/NamespaceResolverTest.php +++ b/tests/ApplicationLayer/NamespaceResolverTest.php @@ -18,11 +18,7 @@ 'namespace' => 'App', ]); - DDD::resolveNamespaceUsing(function ( - string $domain, - string $type, - ?DomainCommandContext $context - ): ?string { + DDD::resolveNamespaceUsing(function (string $domain, string $type, ?DomainCommandContext $context): ?string { if ($type == 'controller' && $context->option('api')) { return "App\\Api\\Controllers\\{$domain}"; } From 13124581efd8c7dbb67e3255da55b82b3054af86 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 16 Oct 2024 10:17:38 -0400 Subject: [PATCH 031/169] Refactor cache/optimize commands. --- CHANGELOG.md | 4 +++- README.md | 12 ++++++------ ...cheClearCommand.php => OptimizeClearCommand.php} | 11 ++++++++++- .../{CacheCommand.php => OptimizeCommand.php} | 13 +++++++++++-- src/LaravelDDDServiceProvider.php | 8 ++++---- 5 files changed, 34 insertions(+), 14 deletions(-) rename src/Commands/{CacheClearCommand.php => OptimizeClearCommand.php} (66%) rename src/Commands/{CacheCommand.php => OptimizeCommand.php} (76%) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc7cf3c..f5dc0ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,8 +21,8 @@ All notable changes to `laravel-ddd` will be documented in this file. - Added `ddd:request` to generate domain-spefic requests in the application layer. - Added `ddd:middleware` to generate domain-specific middleware in the application layer. - Added `ddd:migration` to generate domain migrations. -- Migration folders across domains will be registered and scanned when running `php artisan migrate`, in addition to the standard application `database/migrations` path. - Added `ddd:seeder` to generate domain seeders. +- Migration folders across domains will be registered and scanned when running `php artisan migrate`, in addition to the standard application `database/migrations` path. ### Changed - `ddd:model` now internally extends Laravel's native `make:model` and inherits all standard options: @@ -34,6 +34,8 @@ All notable changes to `laravel-ddd` will be documented in this file. - `-mfsc` - `--all|-a` - `--pivot|-p` +- `ddd:cache` is now `ddd:optimize` (`ddd:cache` is still available as an alias). +- For Laravel 11.27.1+, the framework's `optimize` and `optimize:clear` commands will automatically invoke `ddd:optimize` and `ddd:clear` respectively. ### Deprecated - Domain base models are no longer required by default, and `config('ddd.base_model')` is now `null` by default. diff --git a/README.md b/README.md index c1707ad..8542ceb 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,11 @@ composer require lorisleiva/laravel-actions The default DTO and Action stubs of this package use the above dependencies. Of course, you may customize the stubs to suit your needs if you aren't using the above packages. ### Deployment -In production, run `ddd:cache` during the deployment process to [optimize autoloading](#autoloading-in-production). +In production, run `ddd:optimize` during the deployment process to [optimize autoloading](#autoloading-in-production). ```bash -php artisan ddd:cache +php artisan ddd:optimize ``` -Note: Since Laravel 11.27.1, `ddd:cache` will automatically be invoked during Laravel's `optimize` command, in which case you do not need to run `ddd:cache` separately if your production environment already runs `optimize`. +Since Laravel 11.27.1, `php artisan optimize` automatically invokes `ddd:optimize`. If you already run `optimize` in production, a separate `ddd:optimize` is no longer necessary. ### Version Compatibility Laravel | LaravelDDD | | @@ -110,7 +110,7 @@ Generated objects will be placed in the appropriate domain namespace as specifie php artisan ddd:list # Cache domain manifests (used for autoloading) -php artisan ddd:cache +php artisan ddd:optimize # Clear the domain cache php artisan ddd:clear @@ -286,9 +286,9 @@ You may disable autoloading by setting the respective autoload options to `false ## 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. +In production, you should cache the autoload manifests using the `ddd:optimize` 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. -Note: Since Laravel 11.27.1, `ddd:cache` and `ddd:clear` will automatically be invoked when running Laravel's `optimize` and `optimize:clear` respectively. If this applies to you and you are already running `optimize` in production, you don't need to manually run `ddd:cache`. +> **Note**: Since Laravel 11.27.1, the framework's `optimize` and `optimize:clear` commands will automatically invoke `ddd:optimize` and `ddd:clear` respectively. diff --git a/src/Commands/CacheClearCommand.php b/src/Commands/OptimizeClearCommand.php similarity index 66% rename from src/Commands/CacheClearCommand.php rename to src/Commands/OptimizeClearCommand.php index 7e4c465..3ad99c8 100644 --- a/src/Commands/CacheClearCommand.php +++ b/src/Commands/OptimizeClearCommand.php @@ -5,12 +5,21 @@ use Illuminate\Console\Command; use Lunarstorm\LaravelDDD\Support\DomainCache; -class CacheClearCommand extends Command +class OptimizeClearCommand extends Command { protected $name = 'ddd:clear'; protected $description = 'Clear cached domain autoloaded objects.'; + protected function configure() + { + $this->setAliases([ + 'ddd:optimize:clear', + ]); + + parent::configure(); + } + public function handle() { DomainCache::clear(); diff --git a/src/Commands/CacheCommand.php b/src/Commands/OptimizeCommand.php similarity index 76% rename from src/Commands/CacheCommand.php rename to src/Commands/OptimizeCommand.php index e6bd6cf..e73462e 100644 --- a/src/Commands/CacheCommand.php +++ b/src/Commands/OptimizeCommand.php @@ -6,12 +6,21 @@ use Lunarstorm\LaravelDDD\Support\DomainAutoloader; use Lunarstorm\LaravelDDD\Support\DomainMigration; -class CacheCommand extends Command +class OptimizeCommand extends Command { - protected $name = 'ddd:cache'; + protected $name = 'ddd:optimize'; protected $description = 'Cache auto-discovered domain objects and migration paths.'; + protected function configure() + { + $this->setAliases([ + 'ddd:cache', + ]); + + parent::configure(); + } + public function handle() { $this->components->info('Caching DDD providers, commands, migration paths.'); diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index c2f9d95..106ec30 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -28,8 +28,8 @@ public function configurePackage(Package $package): void ->hasCommands([ Commands\InstallCommand::class, Commands\UpgradeCommand::class, - Commands\CacheCommand::class, - Commands\CacheClearCommand::class, + Commands\OptimizeCommand::class, + Commands\OptimizeClearCommand::class, Commands\DomainListCommand::class, Commands\DomainModelMakeCommand::class, Commands\DomainFactoryMakeCommand::class, @@ -92,9 +92,9 @@ public function packageBooted() if ($this->app->runningInConsole() && method_exists($this, 'optimizes')) { $this->optimizes( - optimize: 'ddd:cache', + optimize: 'ddd:optimize', clear: 'ddd:clear', - key: 'ddd:cache', + key: 'ddd cache', ); } } From b2f977ed643909ad7b7a733a3c249bf06228676e Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 16 Oct 2024 13:26:29 -0400 Subject: [PATCH 032/169] Minor changes. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8542ceb..ebd218c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Actions: [lorisleiva/laravel-actions](https://github.com/lorisleiva/laravel-acti ```bash composer require lorisleiva/laravel-actions ``` -The default DTO and Action stubs of this package use the above dependencies. Of course, you may customize the stubs to suit your needs if you aren't using the above packages. +The default DTO and Action stubs of this package reference classes from these packages. If this doesn't apply to your application, you may customize the stubs accordingly. ### Deployment In production, run `ddd:optimize` during the deployment process to [optimize autoloading](#autoloading-in-production). @@ -71,7 +71,7 @@ php artisan ddd:{object} {name} The following generators are currently available: | Command | Description | Usage | |---|---|---| -| ddd:model [options] | Generate a domain model | `php artisan ddd:model Invoicing:Invoice`

Options:
`--migration\|-m`
`--factory\|-f`
`--seed\|-s`
`--controller --resource --requests\|-crR`
`--policy`
`-mfsc`
`--all\|-a`
`--pivot\|-p`
| +| ddd:model | Generate a domain model | `php artisan ddd:model Invoicing:Invoice`

Options:
`--migration\|-m`
`--factory\|-f`
`--seed\|-s`
`--controller --resource --requests\|-crR`
`--policy`
`-mfsc`
`--all\|-a`
`--pivot\|-p`
| | ddd:factory | Generate a domain factory | `php artisan ddd:factory Invoicing:InvoiceFactory` | | ddd:dto | Generate a data transfer object | `php artisan ddd:dto Invoicing:LineItemPayload` | | ddd:value | Generate a value object | `php artisan ddd:value Shared:DollarAmount` | @@ -80,7 +80,7 @@ The following generators are currently available: | ddd:cast | Generate a cast | `php artisan ddd:cast Invoicing:MoneyCast` | | ddd:channel | Generate a channel | `php artisan ddd:channel Invoicing:InvoiceChannel` | | ddd:command | Generate a command | `php artisan ddd:command Invoicing:InvoiceDeliver` | -| ddd:controller [options] | Generate a controller | `php artisan ddd:controller Invoicing:InvoiceController`

Options: supports the standard options as `make:controller` | +| ddd:controller | Generate a controller | `php artisan ddd:controller Invoicing:InvoiceController`

Options: inherits options from *make:controller* | | ddd:event | Generate an event | `php artisan ddd:event Invoicing:PaymentWasReceived` | | ddd:exception | Generate an exception | `php artisan ddd:exception Invoicing:InvoiceNotFoundException` | | ddd:job | Generate a job | `php artisan ddd:job Invoicing:GenerateInvoicePdf` | From 7daf96e2b0533029f59638678decf825d86aea30 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 16 Oct 2024 13:28:53 -0400 Subject: [PATCH 033/169] Wrap command column with backticks. --- README.md | 60 +++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index ebd218c..881f582 100644 --- a/README.md +++ b/README.md @@ -71,36 +71,36 @@ php artisan ddd:{object} {name} The following generators are currently available: | Command | Description | Usage | |---|---|---| -| ddd:model | Generate a domain model | `php artisan ddd:model Invoicing:Invoice`

Options:
`--migration\|-m`
`--factory\|-f`
`--seed\|-s`
`--controller --resource --requests\|-crR`
`--policy`
`-mfsc`
`--all\|-a`
`--pivot\|-p`
| -| ddd:factory | Generate a domain factory | `php artisan ddd:factory Invoicing:InvoiceFactory` | -| ddd:dto | Generate a data transfer object | `php artisan ddd:dto Invoicing:LineItemPayload` | -| ddd:value | Generate a value object | `php artisan ddd:value Shared:DollarAmount` | -| ddd:view-model | Generate a view model | `php artisan ddd:view-model Invoicing:ShowInvoiceViewModel` | -| ddd:action | Generate an action | `php artisan ddd:action Invoicing:SendInvoiceToCustomer` | -| ddd:cast | Generate a cast | `php artisan ddd:cast Invoicing:MoneyCast` | -| ddd:channel | Generate a channel | `php artisan ddd:channel Invoicing:InvoiceChannel` | -| ddd:command | Generate a command | `php artisan ddd:command Invoicing:InvoiceDeliver` | -| ddd:controller | Generate a controller | `php artisan ddd:controller Invoicing:InvoiceController`

Options: inherits options from *make:controller* | -| ddd:event | Generate an event | `php artisan ddd:event Invoicing:PaymentWasReceived` | -| ddd:exception | Generate an exception | `php artisan ddd:exception Invoicing:InvoiceNotFoundException` | -| ddd:job | Generate a job | `php artisan ddd:job Invoicing:GenerateInvoicePdf` | -| ddd:listener | Generate a listener | `php artisan ddd:listener Invoicing:HandlePaymentReceived` | -| ddd:mail | Generate a mail | `php artisan ddd:mail Invoicing:OverduePaymentReminderEmail` | -| ddd:middleware | Generate a middleware | `php artisan ddd:middleware Invoicing:VerifiedCustomerMiddleware` | -| ddd:migration | Generate a migration | `php artisan ddd:migration Invoicing:CreateInvoicesTable` | -| ddd:notification | Generate a notification | `php artisan ddd:notification Invoicing:YourPaymentWasReceived` | -| ddd:observer | Generate an observer | `php artisan ddd:observer Invoicing:InvoiceObserver` | -| ddd:policy | Generate a policy | `php artisan ddd:policy Invoicing:InvoicePolicy` | -| ddd:provider | Generate a provider | `php artisan ddd:provider Invoicing:InvoiceServiceProvider` | -| ddd:resource | Generate a resource | `php artisan ddd:resource Invoicing:InvoiceResource` | -| ddd:rule | Generate a rule | `php artisan ddd:rule Invoicing:ValidPaymentMethod` | -| ddd:request | Generate a form request | `php artisan ddd:request Invoicing:StoreInvoiceRequest` | -| ddd:scope | Generate a scope | `php artisan ddd:scope Invoicing:ArchivedInvoicesScope` | -| ddd:seeder | Generate a seeder | `php artisan ddd:seeder Invoicing:InvoiceSeeder` | -| ddd:class | Generate a class (Laravel 11+) | `php artisan ddd:class Invoicing:Support/InvoiceBuilder` | -| ddd:enum | Generate an enum (Laravel 11+) | `php artisan ddd:enum Customer:CustomerType` | -| ddd:interface | Generate an interface (Laravel 11+) | `php artisan ddd:interface Customer:Contracts/Invoiceable` | -| ddd:trait | Generate a trait (Laravel 11+) | `php artisan ddd:trait Customer:Concerns/HasInvoices` | +| `ddd:model` | Generate a domain model | `php artisan ddd:model Invoicing:Invoice`

Options:
`--migration\|-m`
`--factory\|-f`
`--seed\|-s`
`--controller --resource --requests\|-crR`
`--policy`
`-mfsc`
`--all\|-a`
`--pivot\|-p`
| +| `ddd:factory` | Generate a domain factory | `php artisan ddd:factory Invoicing:InvoiceFactory` | +| `ddd:dto` | Generate a data transfer object | `php artisan ddd:dto Invoicing:LineItemPayload` | +| `ddd:value` | Generate a value object | `php artisan ddd:value Shared:DollarAmount` | +| `ddd:view-model` | Generate a view model | `php artisan ddd:view-model Invoicing:ShowInvoiceViewModel` | +| `ddd:action` | Generate an action | `php artisan ddd:action Invoicing:SendInvoiceToCustomer` | +| `ddd:cast` | Generate a cast | `php artisan ddd:cast Invoicing:MoneyCast` | +| `ddd:channel` | Generate a channel | `php artisan ddd:channel Invoicing:InvoiceChannel` | +| `ddd:command` | Generate a command | `php artisan ddd:command Invoicing:InvoiceDeliver` | +| `ddd:controller` | Generate a controller | `php artisan ddd:controller Invoicing:InvoiceController`

Options: inherits options from *make:controller* | +| `ddd:event` | Generate an event | `php artisan ddd:event Invoicing:PaymentWasReceived` | +| `ddd:exception` | Generate an exception | `php artisan ddd:exception Invoicing:InvoiceNotFoundException` | +| `ddd:job` | Generate a job | `php artisan ddd:job Invoicing:GenerateInvoicePdf` | +| `ddd:listener` | Generate a listener | `php artisan ddd:listener Invoicing:HandlePaymentReceived` | +| `ddd:mail` | Generate a mail | `php artisan ddd:mail Invoicing:OverduePaymentReminderEmail` | +| `ddd:middleware` | Generate a middleware | `php artisan ddd:middleware Invoicing:VerifiedCustomerMiddleware` | +| `ddd:migration` | Generate a migration | `php artisan ddd:migration Invoicing:CreateInvoicesTable` | +| `ddd:notification` | Generate a notification | `php artisan ddd:notification Invoicing:YourPaymentWasReceived` | +| `ddd:observer` | Generate an observer | `php artisan ddd:observer Invoicing:InvoiceObserver` | +| `ddd:policy` | Generate a policy | `php artisan ddd:policy Invoicing:InvoicePolicy` | +| `ddd:provider` | Generate a provider | `php artisan ddd:provider Invoicing:InvoiceServiceProvider` | +| `ddd:resource` | Generate a resource | `php artisan ddd:resource Invoicing:InvoiceResource` | +| `ddd:rule` | Generate a rule | `php artisan ddd:rule Invoicing:ValidPaymentMethod` | +| `ddd:request` | Generate a form request | `php artisan ddd:request Invoicing:StoreInvoiceRequest` | +| `ddd:scope` | Generate a scope | `php artisan ddd:scope Invoicing:ArchivedInvoicesScope` | +| `ddd:seeder` | Generate a seeder | `php artisan ddd:seeder Invoicing:InvoiceSeeder` | +| `ddd:class` | Generate a class (Laravel 11+) | `php artisan ddd:class Invoicing:Support/InvoiceBuilder` | +| `ddd:enum` | Generate an enum (Laravel 11+) | `php artisan ddd:enum Customer:CustomerType` | +| `ddd:interface` | Generate an interface (Laravel 11+) | `php artisan ddd:interface Customer:Contracts/Invoiceable` | +| `ddd:trait` | Generate a trait (Laravel 11+) | `php artisan ddd:trait Customer:Concerns/HasInvoices` | Generated objects will be placed in the appropriate domain namespace as specified by `ddd.namespaces.*` in the [config file](#config-file). From 42054d48b8a006f4584e02649a687772460c9570 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 16 Oct 2024 19:55:01 -0400 Subject: [PATCH 034/169] Experimental: custom layers --- CHANGELOG.md | 2 +- README.md | 19 +++++- config/ddd.php | 27 ++++++-- src/Commands/DomainListCommand.php | 4 +- src/Support/Domain.php | 49 +++++++++----- src/Support/DomainResolver.php | 33 +++++++-- src/Support/Layer.php | 67 +++++++++++++++++++ src/Support/Path.php | 8 +++ tests/.skeleton/composer.json | 3 +- tests/Generator/ControllerMakeTest.php | 2 +- tests/Generator/Model/MakeWithOptionsTest.php | 3 +- tests/Generator/RequestMakeTest.php | 2 +- tests/Support/DomainTest.php | 21 +++++- tests/Support/ResolveLayerTest.php | 44 ++++++++++++ .../ResolveNamespaceTest.php} | 2 +- tests/TestCase.php | 4 +- 16 files changed, 249 insertions(+), 41 deletions(-) create mode 100644 src/Support/Layer.php create mode 100644 tests/Support/ResolveLayerTest.php rename tests/{ApplicationLayer/NamespaceResolverTest.php => Support/ResolveNamespaceTest.php} (96%) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5dc0ba..c2fab9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ All notable changes to `laravel-ddd` will be documented in this file. - Experimental: Ability to configure the Application Layer, to generate domain objects that don't typically belong inside the domain layer. ```php // In config/ddd.php - 'application_layer' => [ + 'application' => [ 'path' => 'app/Modules', 'namespace' => 'App\Modules', 'objects' => [ diff --git a/README.md b/README.md index 881f582..0d8fcd7 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ php artisan ddd:clear Some objects interact with the domain layer, but are not part of the domain layer themselves. By default, these include: `controller`, `request`, `middleware`. You may customize the path, namespace, and which `ddd:*` objects belong in the application layer. ```php // In config/ddd.php -'application_layer' => [ +'application' => [ 'path' => 'app/Modules', 'namespace' => 'App\Modules', 'objects' => [ @@ -151,6 +151,21 @@ Output: └─ Invoice.php ``` +### Custom Layers (since 1.2) +Some objects interact with the domain layer, but are not part of the domain layer themselves. By default, these include: `controller`, `request`, `middleware`. You may customize the path, namespace, and which `ddd:*` objects belong in the application layer. +```php +// In config/ddd.php +'application' => [ + 'path' => 'app/Modules', + 'namespace' => 'App\Modules', + 'objects' => [ + 'controller', + 'request', + 'middleware', + ], +], +``` + ### Nested Objects For any `ddd:*` generator command, nested objects can be specified with forward slashes. ```bash @@ -326,7 +341,7 @@ return [ | Configure domain objects in the application layer. | */ - 'application_layer' => [ + 'application' => [ 'path' => 'app/Modules', 'namespace' => 'App\Modules', 'objects' => [ diff --git a/config/ddd.php b/config/ddd.php index 08016f3..5541ab4 100644 --- a/config/ddd.php +++ b/config/ddd.php @@ -30,7 +30,7 @@ | Configure domain objects in the application layer. | */ - 'application_layer' => [ + 'application' => [ 'path' => 'app/Modules', 'namespace' => 'App\Modules', 'objects' => [ @@ -40,14 +40,33 @@ ], ], + /* + |-------------------------------------------------------------------------- + | Custom Layers + |-------------------------------------------------------------------------- + | + | Mapping of additional top-level namespaces and paths that should + | be recognized as layers when generating ddd:* objects. + | + | e.g., 'Infrastructure' => 'src/Infrastructure', + | + | When using ddd:* generators, specifying a domain matching a key in + | this array will generate objects in that corresponding layer. + | + */ + 'layers' => [ + // 'Infrastructure' => 'src/Infrastructure', + // 'Integrations' => 'src/Integrations', + // 'Support' => 'src/Support', + ], + /* |-------------------------------------------------------------------------- | Domain Object Namespaces |-------------------------------------------------------------------------- | - | This value contains the default namespaces of generated domain - | objects relative to the domain namespace of which the object - | belongs to. + | This value contains the default namespaces of ddd:* generated + | objects relative to the layer of which the object belongs to. | | e.g., Domain\Invoicing\Models\* | Domain\Invoicing\Data\* diff --git a/src/Commands/DomainListCommand.php b/src/Commands/DomainListCommand.php index 2cd3a2f..3b6adb6 100644 --- a/src/Commands/DomainListCommand.php +++ b/src/Commands/DomainListCommand.php @@ -23,8 +23,8 @@ public function handle() return [ $domain->domain, - $domain->namespace->root, - Path::normalize($domain->path), + $domain->layer->namespace, + Path::normalize($domain->layer->path), ]; }) ->toArray(); diff --git a/src/Support/Domain.php b/src/Support/Domain.php index 225fe61..49483fc 100644 --- a/src/Support/Domain.php +++ b/src/Support/Domain.php @@ -21,6 +21,8 @@ class Domain public readonly DomainNamespaces $namespace; + public readonly Layer $layer; + public static array $objects = []; public function __construct(string $domain, ?string $subdomain = null) @@ -56,9 +58,11 @@ public function __construct(string $domain, ?string $subdomain = null) ? "{$this->domain}.{$this->subdomain}" : $this->domain; + $this->layer = DomainResolver::resolveLayer($this->domainWithSubdomain); + $this->namespace = DomainNamespaces::from($this->domain, $this->subdomain); - $this->path = Path::join(DomainResolver::domainPath(), $this->domainWithSubdomain); + $this->path = $this->layer->path; $this->migrationPath = Path::join($this->path, config('ddd.namespaces.migration', 'Database/Migrations')); } @@ -74,13 +78,13 @@ public function path(?string $path = null): string return $this->path; } - $path = str($path) - ->replace($this->namespace->root, '') + $resolvedPath = str($path) + ->replace($this->layer->namespace, '') ->replace(['\\', '/'], DIRECTORY_SEPARATOR) ->append('.php') ->toString(); - return Path::join($this->path, $path); + return Path::join($this->path, $resolvedPath); } public function pathInApplicationLayer(?string $path = null): string @@ -121,20 +125,18 @@ public function guessNamespaceFromName(string $name): string public function object(string $type, string $name, bool $absolute = false): DomainObject { - $resolvedNamespace = null; + $resolver = app('ddd')->getNamespaceResolver(); - if (DomainResolver::isApplicationLayer($type)) { - $resolver = app('ddd')->getNamespaceResolver(); + $customNamespace = is_callable($resolver) + ? $resolver($this->domainWithSubdomain, $type, app('ddd')->getCommandContext()) + : null; - $resolvedNamespace = is_callable($resolver) - ? $resolver($this->domainWithSubdomain, $type, app('ddd')->getCommandContext()) - : null; - } + $layer = DomainResolver::resolveLayer($this->domainWithSubdomain, $type); - $namespace = $resolvedNamespace ?? match (true) { - $absolute => $this->namespace->root, - str($name)->startsWith('\\') => $this->guessNamespaceFromName($name), - default => $this->namespaceFor($type), + $namespace = $customNamespace ?? match (true) { + $absolute => $layer->namespace, + str($name)->startsWith('\\') => $layer->guessNamespaceFromName($name), + default => $layer->namespaceFor($type), }; $baseName = str($name)->replace($namespace, '') @@ -144,14 +146,25 @@ public function object(string $type, string $name, bool $absolute = false): Doma $fullyQualifiedName = $namespace.'\\'.$baseName; + if ($customNamespace) { + return new DomainObject( + name: $baseName, + domain: $this->domain, + namespace: $namespace, + fullyQualifiedName: $fullyQualifiedName, + path: DomainResolver::isApplicationLayer($type) + ? $this->pathInApplicationLayer($fullyQualifiedName) + : $this->path($fullyQualifiedName), + type: $type + ); + } + return new DomainObject( name: $baseName, domain: $this->domain, namespace: $namespace, fullyQualifiedName: $fullyQualifiedName, - path: DomainResolver::isApplicationLayer($type) - ? $this->pathInApplicationLayer($fullyQualifiedName) - : $this->path($fullyQualifiedName), + path: $layer->path($fullyQualifiedName), type: $type ); } diff --git a/src/Support/DomainResolver.php b/src/Support/DomainResolver.php index 15257b4..6559dad 100644 --- a/src/Support/DomainResolver.php +++ b/src/Support/DomainResolver.php @@ -40,7 +40,7 @@ public static function domainRootNamespace(): ?string */ public static function applicationLayerPath(): ?string { - return config('ddd.application_layer.path'); + return config('ddd.application.path'); } /** @@ -48,7 +48,7 @@ public static function applicationLayerPath(): ?string */ public static function applicationLayerRootNamespace(): ?string { - return config('ddd.application_layer.namespace'); + return config('ddd.application.namespace'); } /** @@ -67,7 +67,7 @@ public static function getRelativeObjectNamespace(string $type): string public static function isApplicationLayer(string $type): bool { $filter = app('ddd')->getApplicationLayerFilter() ?? function (string $type) { - $applicationObjects = config('ddd.application_layer.objects', ['controller', 'request']); + $applicationObjects = config('ddd.application.objects', ['controller', 'request']); return in_array($type, $applicationObjects); }; @@ -87,6 +87,28 @@ public static function resolveRootNamespace(string $type): ?string : static::domainRootNamespace(); } + /** + * Resolve the intended layer of a specified domain name keyword. + */ + public static function resolveLayer(string $domain, ?string $type = null): ?Layer + { + $layers = config('ddd.layers', []); + + return match (true) { + array_key_exists($domain, $layers) => new Layer($domain, $layers[$domain]), + + $type && static::isApplicationLayer($type) => new Layer( + static::applicationLayerRootNamespace().'\\'.$domain, + Path::join(static::applicationLayerPath(), $domain), + ), + + default => new Layer( + static::domainRootNamespace().'\\'.$domain, + Path::join(static::domainPath(), $domain), + ) + }; + } + /** * Get the fully qualified namespace for a domain object. * @@ -107,9 +129,10 @@ public static function getDomainObjectNamespace(string $domain, string $type, ?s } $resolver = function (string $domain, string $type, ?string $name) { + $layer = static::resolveLayer($domain, $type); + $namespace = collect([ - static::resolveRootNamespace($type), - $domain, + $layer->namespace, static::getRelativeObjectNamespace($type), ])->filter()->implode('\\'); diff --git a/src/Support/Layer.php b/src/Support/Layer.php new file mode 100644 index 0000000..7019956 --- /dev/null +++ b/src/Support/Layer.php @@ -0,0 +1,67 @@ +namespace = Path::normalizeNamespace($namespace); + $this->path = is_null($path) + ? $this->path() + : Path::normalize($path); + } + + public static function fromNamespace(string $namespace): self + { + return new self($namespace, null); + } + + public function path(?string $path = null): string + { + if (is_null($path)) { + return $this->path; + } + + $relativePath = str($path) + ->replace($this->namespace, '') + ->replace(['\\', '/'], DIRECTORY_SEPARATOR) + ->append('.php') + ->toString(); + + return Path::join($this->path, $relativePath); + } + + public function namespaceFor(string $type, ?string $name = null): string + { + $namespace = collect([ + $this->namespace, + DomainResolver::getRelativeObjectNamespace($type), + ])->filter()->implode('\\'); + + if ($name) { + $namespace .= "\\{$name}"; + } + + return Path::normalizeNamespace($namespace); + } + + public function guessNamespaceFromName(string $name): string + { + $baseName = class_basename($name); + + return Path::normalizeNamespace( + str($name) + ->before($baseName) + ->trim('\\') + ->prepend($this->namespace.'\\') + ->toString() + ); + } +} diff --git a/src/Support/Path.php b/src/Support/Path.php index 7fbdb86..28da83c 100644 --- a/src/Support/Path.php +++ b/src/Support/Path.php @@ -18,6 +18,14 @@ public static function join(...$parts) return implode(DIRECTORY_SEPARATOR, $parts); } + public static function fromNamespace(string $namespace, ?string $classname = null): string + { + return str($namespace) + ->replace(['\\', '/'], DIRECTORY_SEPARATOR) + ->when($classname, fn ($s) => $s->append("{$classname}.php")) + ->toString(); + } + public static function filePathToNamespace(string $path, string $namespacePath, string $namespace): string { return str_replace( diff --git a/tests/.skeleton/composer.json b/tests/.skeleton/composer.json index 6e77450..37eb342 100644 --- a/tests/.skeleton/composer.json +++ b/tests/.skeleton/composer.json @@ -13,7 +13,8 @@ "tests/TestCase.php" ], "psr-4": { - "App\\": "app/" + "App\\": "app/", + "Support\\": "src/Support" } }, "extra": { diff --git a/tests/Generator/ControllerMakeTest.php b/tests/Generator/ControllerMakeTest.php index a4658f4..5aae081 100644 --- a/tests/Generator/ControllerMakeTest.php +++ b/tests/Generator/ControllerMakeTest.php @@ -8,7 +8,7 @@ Config::set('ddd.domain_path', 'src/Domain'); Config::set('ddd.domain_namespace', 'Domain'); - Config::set('ddd.application_layer', [ + Config::set('ddd.application', [ 'path' => 'app/Modules', 'namespace' => 'App\Modules', 'objects' => ['controller', 'request'], diff --git a/tests/Generator/Model/MakeWithOptionsTest.php b/tests/Generator/Model/MakeWithOptionsTest.php index 09cb201..581680b 100644 --- a/tests/Generator/Model/MakeWithOptionsTest.php +++ b/tests/Generator/Model/MakeWithOptionsTest.php @@ -71,7 +71,8 @@ } $command = [ - 'ddd:model', [ + 'ddd:model', + [ 'name' => $modelName, '--domain' => $domain->dotName, ...$options, diff --git a/tests/Generator/RequestMakeTest.php b/tests/Generator/RequestMakeTest.php index 2872b79..cc24949 100644 --- a/tests/Generator/RequestMakeTest.php +++ b/tests/Generator/RequestMakeTest.php @@ -8,7 +8,7 @@ Config::set('ddd.domain_path', 'src/Domain'); Config::set('ddd.domain_namespace', 'Domain'); - Config::set('ddd.application_layer', [ + Config::set('ddd.application', [ 'path' => 'app/Modules', 'namespace' => 'App\Modules', 'objects' => ['controller', 'request'], diff --git a/tests/Support/DomainTest.php b/tests/Support/DomainTest.php index 459af6f..e911635 100644 --- a/tests/Support/DomainTest.php +++ b/tests/Support/DomainTest.php @@ -93,7 +93,7 @@ describe('application layer', function () { beforeEach(function () { - Config::set('ddd.application_layer', [ + Config::set('ddd.application', [ 'path' => 'app/Modules', 'namespace' => 'App\Modules', 'objects' => ['controller', 'request'], @@ -113,6 +113,25 @@ ]); }); +describe('custom layers', function () { + beforeEach(function () { + Config::set('ddd.layers', [ + 'Support' => 'src/Support', + ]); + }); + + it('can map domains to custom layers', function ($domainName, $objectType, $objectName, $expectedFQN, $expectedPath) { + expect((new Domain($domainName))->object($objectType, $objectName)) + ->name->toBe($objectName) + ->fullyQualifiedName->toBe($expectedFQN) + ->path->toBe(Path::normalize($expectedPath)); + })->with([ + ['Support', 'class', 'ExchangeRate', 'Support\\ExchangeRate', 'src/Support/ExchangeRate.php'], + ['Support', 'trait', 'Concerns\\HasOptions', 'Support\\Concerns\\HasOptions', 'src/Support/Concerns/HasOptions.php'], + ['Support', 'exception', 'InvalidExchangeRate', 'Support\\Exceptions\\InvalidExchangeRate', 'src/Support/Exceptions/InvalidExchangeRate.php'], + ]); +}); + it('normalizes slashes in nested objects', function ($nameInput, $normalized) { expect((new Domain('Invoicing'))->object('class', $nameInput)) ->name->toBe($normalized); diff --git a/tests/Support/ResolveLayerTest.php b/tests/Support/ResolveLayerTest.php new file mode 100644 index 0000000..6b25d23 --- /dev/null +++ b/tests/Support/ResolveLayerTest.php @@ -0,0 +1,44 @@ +setupTestApplication(); +}); + +it('can resolve domains to custom layers', function ($domainName, $namespace, $path) { + Config::set('ddd.layers', [ + 'Support' => 'src/Support', + ]); + + $layer = DomainResolver::resolveLayer($domainName); + + expect($layer) + ->namespace->toBe($namespace) + ->path->toBe(Path::normalize($path)); +})->with([ + ['Support', 'Support', 'src/Support'], + ['Invoicing', 'Domain\\Invoicing', 'src/Domain/Invoicing'], + ['Reporting\\Internal', 'Domain\\Reporting\\Internal', 'src/Domain/Reporting/Internal'], +]); + +it('resolves normally when no matching custom layer is found', function ($domainName, $namespace, $path) { + Config::set('ddd.layers', [ + 'SupportNotMatching' => 'src/Support', + ]); + + $layer = DomainResolver::resolveLayer($domainName); + + expect($layer) + ->namespace->toBe($namespace) + ->path->toBe(Path::normalize($path)); +})->with([ + ['Support', 'Domain\\Support', 'src/Domain/Support'], + ['Invoicing', 'Domain\\Invoicing', 'src/Domain/Invoicing'], + ['Reporting\\Internal', 'Domain\\Reporting\\Internal', 'src/Domain/Reporting/Internal'], +]); diff --git a/tests/ApplicationLayer/NamespaceResolverTest.php b/tests/Support/ResolveNamespaceTest.php similarity index 96% rename from tests/ApplicationLayer/NamespaceResolverTest.php rename to tests/Support/ResolveNamespaceTest.php index 870e902..c1f6a48 100644 --- a/tests/ApplicationLayer/NamespaceResolverTest.php +++ b/tests/Support/ResolveNamespaceTest.php @@ -13,7 +13,7 @@ }); it('can register a custom namespace resolver', function () { - Config::set('ddd.application_layer', [ + Config::set('ddd.application', [ 'path' => 'src/App', 'namespace' => 'App', ]); diff --git a/tests/TestCase.php b/tests/TestCase.php index b04ef5a..4493988 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -149,9 +149,7 @@ protected function cleanSlate() File::deleteDirectory(resource_path('stubs/ddd')); File::deleteDirectory(base_path('Custom')); - File::deleteDirectory(base_path('src/Domain')); - File::deleteDirectory(base_path('src/Domains')); - File::deleteDirectory(base_path('src/App')); + File::cleanDirectory(base_path('src')); File::deleteDirectory(app_path('Models')); File::deleteDirectory(base_path('bootstrap/cache/ddd')); From a6f61110a1d107f75ff7a6a73987f308d3da03aa Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 10 Nov 2024 10:15:33 -0500 Subject: [PATCH 035/169] Fix duplicated methods from merge. --- src/LaravelDDDServiceProvider.php | 15 --------------- src/Support/Domain.php | 15 --------------- 2 files changed, 30 deletions(-) diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index e478e41..c35ff7b 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -96,21 +96,6 @@ protected function registerMigrations() $this->loadMigrationsFrom(DomainMigration::paths()); } - protected function registerMigrations() - { - $this->app->singleton(Commands\Migration\DomainMigrateMakeCommand::class, function ($app) { - // Once we have the migration creator registered, we will create the command - // and inject the creator. The creator is responsible for the actual file - // creation of the migrations, and may be extended by these developers. - $creator = $app['migration.creator']; - $composer = $app['composer']; - - return new Commands\Migration\DomainMigrateMakeCommand($creator, $composer); - }); - - $this->loadMigrationsFrom(DomainMigration::paths()); - } - public function packageBooted() { $this->publishes([ diff --git a/src/Support/Domain.php b/src/Support/Domain.php index f578c5a..49483fc 100644 --- a/src/Support/Domain.php +++ b/src/Support/Domain.php @@ -102,21 +102,6 @@ public function pathInApplicationLayer(?string $path = null): string return Path::join(DomainResolver::applicationLayerPath(), $path); } - public function pathInApplicationLayer(?string $path = null): string - { - if (is_null($path)) { - return $this->path; - } - - $path = str($path) - ->replace(DomainResolver::applicationLayerRootNamespace(), '') - ->replace(['\\', '/'], DIRECTORY_SEPARATOR) - ->append('.php') - ->toString(); - - return Path::join(DomainResolver::applicationLayerPath(), $path); - } - public function relativePath(string $path = ''): string { return collect([$this->domain, $path])->filter()->implode(DIRECTORY_SEPARATOR); From 62371741352dda0ba02286764788e62710133809 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 10 Nov 2024 10:25:22 -0500 Subject: [PATCH 036/169] Restore factory naming clause. --- src/Support/Domain.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Support/Domain.php b/src/Support/Domain.php index 49483fc..2a94d5c 100644 --- a/src/Support/Domain.php +++ b/src/Support/Domain.php @@ -142,6 +142,7 @@ public function object(string $type, string $name, bool $absolute = false): Doma $baseName = str($name)->replace($namespace, '') ->replace(['\\', '/'], '\\') ->trim('\\') + ->when($type === 'factory', fn ($name) => $name->finish('Factory')) ->toString(); $fullyQualifiedName = $namespace.'\\'.$baseName; From 936632510a0030047b81971ef1fe5b03dfa26ec9 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 12 Nov 2024 00:15:16 -0500 Subject: [PATCH 037/169] Custom Layers + Refactored Internals, WIP --- config/ddd.php | 4 +- .../Concerns/ForwardsToDomainCommands.php | 14 +- .../Concerns/HasGeneratorBlueprint.php | 10 + .../Concerns/QualifiesDomainModels.php | 2 +- .../Concerns/ResolvesDomainFromInput.php | 77 +--- src/Commands/Concerns/UpdatesComposer.php | 54 +++ src/Commands/ConfigCommand.php | 358 ++++++++++++++++++ src/Commands/DomainControllerMakeCommand.php | 2 +- src/Commands/DomainFactoryMakeCommand.php | 6 +- src/Commands/DomainGeneratorCommand.php | 39 +- src/Commands/DomainModelMakeCommand.php | 2 +- src/Commands/DomainModelMakeLegacyCommand.php | 108 ------ src/Commands/DomainRequestMakeCommand.php | 4 +- .../Migration/DomainMigrateMakeCommand.php | 4 +- src/ComposerManager.php | 172 +++++++++ src/DomainManager.php | 39 +- src/Enums/LayerType.php | 10 + src/Facades/DDD.php | 6 +- src/LaravelDDDServiceProvider.php | 10 +- .../Concerns/InteractsWithComposer.php | 30 ++ src/Support/Domain.php | 38 +- src/Support/DomainResolver.php | 31 +- src/Support/GeneratorBlueprint.php | 131 +++++++ src/Support/Layer.php | 17 +- src/ValueObjects/CommandContext.php | 32 ++ src/ValueObjects/DomainCommandContext.php | 51 --- src/ValueObjects/ObjectSchema.php | 13 + .../NamespaceResolverTest.php | 44 --- tests/Generator/CustomLayerTest.php | 92 +++++ .../{DtoMakeTestTest.php => DtoMakeTest.php} | 0 ...dCommandsTest.php => ExtendedMakeTest.php} | 2 +- tests/Support/ResolveLayerTest.php | 2 +- ...t.php => ResolveObjectSchemaUsingTest.php} | 16 +- 33 files changed, 1022 insertions(+), 398 deletions(-) create mode 100644 src/Commands/Concerns/HasGeneratorBlueprint.php create mode 100644 src/Commands/Concerns/UpdatesComposer.php create mode 100644 src/Commands/ConfigCommand.php delete mode 100644 src/Commands/DomainModelMakeLegacyCommand.php create mode 100755 src/ComposerManager.php create mode 100644 src/Enums/LayerType.php create mode 100644 src/Support/Concerns/InteractsWithComposer.php create mode 100644 src/Support/GeneratorBlueprint.php create mode 100644 src/ValueObjects/CommandContext.php delete mode 100644 src/ValueObjects/DomainCommandContext.php create mode 100644 src/ValueObjects/ObjectSchema.php delete mode 100644 tests/ApplicationLayer/NamespaceResolverTest.php create mode 100644 tests/Generator/CustomLayerTest.php rename tests/Generator/{DtoMakeTestTest.php => DtoMakeTest.php} (100%) rename tests/Generator/{ExtendedCommandsTest.php => ExtendedMakeTest.php} (95%) rename tests/Support/{ResolveNamespaceTest.php => ResolveObjectSchemaUsingTest.php} (56%) diff --git a/config/ddd.php b/config/ddd.php index 5541ab4..082196a 100644 --- a/config/ddd.php +++ b/config/ddd.php @@ -31,8 +31,8 @@ | */ 'application' => [ - 'path' => 'app/Modules', 'namespace' => 'App\Modules', + 'path' => 'app/Modules', 'objects' => [ 'controller', 'request', @@ -55,7 +55,7 @@ | */ 'layers' => [ - // 'Infrastructure' => 'src/Infrastructure', + 'Infrastructure' => 'src/Infrastructure', // 'Integrations' => 'src/Integrations', // 'Support' => 'src/Support', ], diff --git a/src/Commands/Concerns/ForwardsToDomainCommands.php b/src/Commands/Concerns/ForwardsToDomainCommands.php index 6586bd7..2b753f9 100644 --- a/src/Commands/Concerns/ForwardsToDomainCommands.php +++ b/src/Commands/Concerns/ForwardsToDomainCommands.php @@ -18,42 +18,42 @@ public function call($command, array $arguments = []) 'make:request' => $this->runCommand('ddd:request', [ ...$arguments, 'name' => $nameWithSubfolder, - '--domain' => $this->domain->dotName, + '--domain' => $this->blueprint->domain->dotName, ], $this->output), 'make:model' => $this->runCommand('ddd:model', [ ...$arguments, 'name' => $nameWithSubfolder, - '--domain' => $this->domain->dotName, + '--domain' => $this->blueprint->domain->dotName, ], $this->output), 'make:factory' => $this->runCommand('ddd:factory', [ ...$arguments, 'name' => $nameWithSubfolder, - '--domain' => $this->domain->dotName, + '--domain' => $this->blueprint->domain->dotName, ], $this->output), 'make:policy' => $this->runCommand('ddd:policy', [ ...$arguments, 'name' => $nameWithSubfolder, - '--domain' => $this->domain->dotName, + '--domain' => $this->blueprint->domain->dotName, ], $this->output), 'make:migration' => $this->runCommand('ddd:migration', [ ...$arguments, - '--domain' => $this->domain->dotName, + '--domain' => $this->blueprint->domain->dotName, ], $this->output), 'make:seeder' => $this->runCommand('ddd:seeder', [ ...$arguments, 'name' => $nameWithSubfolder, - '--domain' => $this->domain->dotName, + '--domain' => $this->blueprint->domain->dotName, ], $this->output), 'make:controller' => $this->runCommand('ddd:controller', [ ...$arguments, 'name' => $nameWithSubfolder, - '--domain' => $this->domain->dotName, + '--domain' => $this->blueprint->domain->dotName, ], $this->output), default => $this->runCommand($command, $arguments, $this->output), diff --git a/src/Commands/Concerns/HasGeneratorBlueprint.php b/src/Commands/Concerns/HasGeneratorBlueprint.php new file mode 100644 index 0000000..1cf2a36 --- /dev/null +++ b/src/Commands/Concerns/HasGeneratorBlueprint.php @@ -0,0 +1,10 @@ +domain) { + if ($domain = $this->blueprint->domain) { $domainModel = $domain->model($model); return $domainModel->fullyQualifiedName; diff --git a/src/Commands/Concerns/ResolvesDomainFromInput.php b/src/Commands/Concerns/ResolvesDomainFromInput.php index 9192aee..5bdb136 100644 --- a/src/Commands/Concerns/ResolvesDomainFromInput.php +++ b/src/Commands/Concerns/ResolvesDomainFromInput.php @@ -4,20 +4,18 @@ use Illuminate\Support\Str; use Lunarstorm\LaravelDDD\Support\Domain; -use Lunarstorm\LaravelDDD\Support\DomainResolver; -use Lunarstorm\LaravelDDD\Support\Path; +use Lunarstorm\LaravelDDD\Support\GeneratorBlueprint; use Symfony\Component\Console\Input\InputOption; trait ResolvesDomainFromInput { use CanPromptForDomain, HandleHooks, + HasGeneratorBlueprint, QualifiesDomainModels; protected $nameIsAbsolute = false; - protected ?Domain $domain = null; - protected function getOptions() { return [ @@ -28,47 +26,21 @@ protected function getOptions() protected function rootNamespace() { - $type = $this->guessObjectType(); - - return Str::finish(DomainResolver::resolveRootNamespace($type), '\\'); - } - - protected function guessObjectType(): string - { - return match ($this->name) { - 'ddd:base-view-model' => 'view_model', - 'ddd:base-model' => 'model', - 'ddd:value' => 'value_object', - 'ddd:dto' => 'data_transfer_object', - 'ddd:migration' => 'migration', - default => str($this->name)->after(':')->snake()->toString(), - }; + return $this->blueprint->rootNamespace(); } protected function getDefaultNamespace($rootNamespace) { - if ($this->domain) { - return $this->nameIsAbsolute - ? $this->domain->namespace->root - : $this->domain->namespaceFor($this->guessObjectType()); - } - - return parent::getDefaultNamespace($rootNamespace); + return $this->blueprint + ? $this->blueprint->getDefaultNamespace($rootNamespace) + : parent::getDefaultNamespace($rootNamespace); } protected function getPath($name) { - if ($this->domain) { - return Path::normalize($this->laravel->basePath( - $this->domain->object( - type: $this->guessObjectType(), - name: $name, - absolute: $this->nameIsAbsolute - )->path - )); - } - - return parent::getPath($name); + return $this->blueprint + ? $this->blueprint->getPath($name) + : parent::getPath($name); } protected function beforeHandle() @@ -84,33 +56,24 @@ protected function beforeHandle() $nameInput = Str::after($nameInput, ':'); } - $this->domain = match (true) { + $domainName = match (true) { // Domain was specified explicitly via option (priority) - filled($this->option('domain')) => new Domain($this->option('domain')), + filled($this->option('domain')) => $this->option('domain'), // Domain was specified as a prefix in the name - filled($domainExtractedFromName) => new Domain($domainExtractedFromName), + filled($domainExtractedFromName) => $domainExtractedFromName, - default => null, + default => $this->promptForDomainName(), }; - // If the domain is not set, prompt for it - if (! $this->domain) { - $this->domain = new Domain($this->promptForDomainName()); - } - - // Now that the domain part is handled, - // we will deal with the name portion. - - // Normalize slash and dot separators - $nameInput = Str::replace(['.', '\\', '/'], '/', $nameInput); - - if ($this->nameIsAbsolute = Str::startsWith($nameInput, ['/'])) { - // $nameInput = Str::after($nameInput, '/'); - } + $this->blueprint = new GeneratorBlueprint( + nameInput: $nameInput, + domainName: $domainName, + command: $this, + ); - $this->input->setArgument('name', $nameInput); + $this->input->setArgument('name', $this->blueprint->nameInput); - app('ddd')->captureCommandContext($this, $this->domain, $this->guessObjectType()); + $this->input->setOption('domain', $this->blueprint->domainName); } } diff --git a/src/Commands/Concerns/UpdatesComposer.php b/src/Commands/Concerns/UpdatesComposer.php new file mode 100644 index 0000000..6f094de --- /dev/null +++ b/src/Commands/Concerns/UpdatesComposer.php @@ -0,0 +1,54 @@ +rtrim('/\\') + ->finish('\\') + ->toString(); + + $this->comment("Registering `{$namespace}`:`{$path}` in composer.json..."); + + $this->fillComposerValue(['autoload', 'psr-4', $namespace], $path); + + $this->composerReload(); + + return $this; + } + + protected function composerReload() + { + $composer = $this->hasOption('composer') ? $this->option('composer') : 'global'; + + if ($composer !== 'global') { + $command = ['php', $composer, 'dump-autoload']; + } else { + $command = ['composer', 'dump-autoload']; + } + + (new Process($command, base_path(), ['COMPOSER_MEMORY_LIMIT' => '-1'])) + ->setTimeout(null) + ->run(function ($type, $output) { + $this->output->write($output); + }); + + return $this; + } +} diff --git a/src/Commands/ConfigCommand.php b/src/Commands/ConfigCommand.php new file mode 100644 index 0000000..0ddda01 --- /dev/null +++ b/src/Commands/ConfigCommand.php @@ -0,0 +1,358 @@ +composer = DDD::composer()->usingOutput($this->output); + + $action = str($this->argument('action'))->trim()->lower()->toString(); + + if (! $action && $this->option('layer')) { + $action = 'layers'; + } + + return match ($action) { + 'wizard' => $this->wizard(), + 'detect' => $this->detect(), + 'composer' => $this->syncComposer(), + 'layers' => $this->layers(), + default => $this->home(), + }; + } + + protected function home(): int + { + $action = select('Laravel-DDD Config Utility', [ + 'wizard' => 'Run the configuration wizard', + 'detect' => 'Detect domain namespace from composer.json', + 'composer' => 'Sync composer.json from ddd.php', + 'exit' => 'Exit', + ], scroll: 10); + + return match ($action) { + 'wizard' => $this->wizard(), + 'detect' => $this->detect(), + 'composer' => $this->syncComposer(), + 'exit' => $this->exit(), + default => $this->exit(), + }; + } + + protected function layers() + { + $layers = $this->option('layer'); + + if ($layers = $this->option('layer')) { + foreach ($layers as $layer) { + $parts = explode(':', $layer); + + $this->composer->registerPsr4Autoload( + namespace: data_get($parts, 0), + path: data_get($parts, 1) + ); + } + + $this->composer->saveAndReload(); + } + + $this->info('Configuration updated.'); + + return self::SUCCESS; + } + + protected function wizard(): int + { + $namespaces = collect($this->composer->getPsr4Namespaces()); + + $layers = $namespaces->map(fn ($path, $namespace) => new Layer($namespace, $path)); + $laravelAppLayer = $layers->first(fn (Layer $layer) => str($layer->namespace)->exactly('App')); + $possibleDomainLayers = $layers->filter(fn (Layer $layer) => str($layer->namespace)->startsWith('Domain')); + $possibleApplicationLayers = $layers->filter(fn (Layer $layer) => str($layer->namespace)->startsWith('App')); + + $domainLayer = $possibleDomainLayers->first(); + $applicationLayer = $possibleApplicationLayers->first(); + + $detected = collect([ + 'domain_namespace' => $domainLayer?->namespace, + 'domain_path' => $domainLayer?->path, + 'application' => [ + 'namespace' => $applicationLayer?->namespace, + 'path' => $applicationLayer?->path, + ], + ]); + + $config = $detected->merge(Config::get('ddd')); + + // dd($config); + + info('Detected DDD configuration:'); + + table( + headers: ['Key', 'Value'], + rows: $detected->dot()->map(fn ($value, $key) => [$key, $value])->all() + ); + + $choices = [ + 'domain_path' => [ + 'src/Domain' => 'src/Domain', + 'src/Domains' => 'src/Domains', + ...[ + $config->get('domain_path') => $config->get('domain_path'), + ], + ...$possibleDomainLayers->mapWithKeys( + fn (Layer $layer) => [$layer->path => $layer->path] + ), + ], + 'domain_namespace' => [ + 'Domain' => 'Domain', + 'Domains' => 'Domains', + ...[ + $config->get('domain_namespace') => $config->get('domain_namespace'), + ], + ...$possibleDomainLayers->mapWithKeys( + fn (Layer $layer) => [$layer->namespace => $layer->namespace] + ), + ], + 'application_path' => [ + 'app/Modules' => 'app/Modules', + 'src/Modules' => 'src/Modules', + 'Modules' => 'Modules', + 'src/Application' => 'src/Application', + 'Application' => 'Application', + ...[ + data_get($config, 'application.path') => data_get($config, 'application.path'), + ], + ...$possibleApplicationLayers->mapWithKeys( + fn (Layer $layer) => [$layer->path => $layer->path] + ), + ], + 'application_namespace' => [ + 'App\Modules' => 'App\Modules', + 'Application' => 'Application', + 'Modules' => 'Modules', + ...[ + data_get($config, 'application.namespace') => data_get($config, 'application.namespace'), + ], + ...$possibleApplicationLayers->mapWithKeys( + fn (Layer $layer) => [$layer->namespace => $layer->namespace] + ), + ], + 'layers' => [ + 'src/Infrastructure' => 'src/Infrastructure', + 'src/Integrations' => 'src/Integrations', + 'src/Support' => 'src/Support', + ], + ]; + + // dd($choices['application_namespace']); + + $form = form() + ->add( + function ($responses) use ($choices, $detected, $config) { + return suggest( + label: 'Domain Path', + options: $choices['domain_path'], + default: $detected->get('domain_path') ?: $config->get('domain_path'), + hint: 'The path to the domain layer relative to the base path.', + required: true, + ); + }, + name: 'domain_path' + ) + ->add( + function ($responses) use ($choices, $config) { + return suggest( + label: 'Domain Namespace', + options: $choices['domain_namespace'], + default: class_basename($responses['domain_path']) ?: $config->get('domain_namespace'), + required: true, + hint: 'The root domain namespace.', + ); + }, + name: 'domain_namespace' + ) + ->add( + function ($responses) use ($choices) { + return suggest( + label: 'Path to Application Layer', + options: $choices['application_path'], + hint: "For objects that don't belong in the domain layer (controllers, form requests, etc.)", + placeholder: 'Leave blank to skip and configure later', + scroll: 10, + ); + }, + name: 'application_path' + ) + ->add( + function ($responses) use ($choices, $laravelAppLayer) { + $applicationPath = $responses['application_path']; + $laravelAppPath = $laravelAppLayer->path; + + $namespace = match (true) { + str($applicationPath)->exactly($laravelAppPath) => $laravelAppLayer->namespace, + str($applicationPath)->startsWith("{$laravelAppPath}/") => str($applicationPath)->studly()->toString(), + default => str($applicationPath)->classBasename()->studly()->toString(), + }; + + return suggest( + label: 'Application Layer Namespace', + options: $choices['application_namespace'], + default: $namespace, + hint: 'The root application namespace.', + ); + }, + name: 'application_namespace' + ) + ->add( + function ($responses) use ($choices) { + return multiselect( + label: 'Additional Layers (Optional)', + options: $choices['layers'], + hint: 'Layers can be customized in the ddd.php config file at any time.', + ); + }, + name: 'layers' + ); + + $responses = $form->submit(); + + // dd($responses); + + return self::SUCCESS; + } + + protected function detect(): int + { + $search = ['Domain', 'Domains']; + + $detected = []; + + foreach ($search as $namespace) { + if ($path = $this->composer->getAutoloadPath($namespace)) { + $detected['domain_namespace'] = $namespace; + $detected['domain_path'] = $path; + break; + } + } + + $this->info('Detected configuration:'); + + table( + headers: ['Config', 'Value'], + rows: collect($detected) + ->map(fn ($value, $key) => [$key, $value]) + ->all() + ); + + return self::SUCCESS; + } + + protected function applyConfig(Collection $config) + { + // $this->composer->update([ + // ['domain_namespace', $config['domain_namespace']], + // ['domain_path', $config['domain_path']], + // ['application.namespace', $config['application']['namespace']], + // ['application.path', $config['application']['path']], + // ]); + + return self::SUCCESS; + } + + protected function syncComposer(): int + { + $namespaces = [ + config('ddd.domain_namespace', 'Domain') => config('ddd.domain_path', 'src/Domain'), + config('ddd.application.namespace', 'App\\Modules') => config('ddd.application.path', 'app/Modules'), + ...collect(config('ddd.layers', [])) + ->all(), + ]; + + $this->info('Syncing composer.json from ddd.php...'); + + $results = []; + + $added = 0; + + foreach ($namespaces as $namespace => $path) { + if ($this->composer->hasPsr4Autoload($namespace)) { + $results[] = [$namespace, $path, 'Already Registered']; + + continue; + } + + $rootNamespace = Str::before($namespace, '\\'); + + if ($this->composer->hasPsr4Autoload($rootNamespace)) { + $results[] = [$namespace, $path, 'Skipped']; + + continue; + } + + $this->composer->registerPsr4Autoload($rootNamespace, $path); + + $results[] = [$namespace, $path, 'Added']; + + $added++; + } + + if ($added > 0) { + $this->composer->saveAndReload(); + } + + table( + headers: ['Namespace', 'Path', 'Status'], + rows: $results + ); + + return self::SUCCESS; + } + + protected function exit(): int + { + $this->info('Goodbye!'); + + return self::SUCCESS; + } +} diff --git a/src/Commands/DomainControllerMakeCommand.php b/src/Commands/DomainControllerMakeCommand.php index 47f03dc..5228211 100644 --- a/src/Commands/DomainControllerMakeCommand.php +++ b/src/Commands/DomainControllerMakeCommand.php @@ -53,7 +53,7 @@ protected function buildFormRequestReplacements(array $replace, $modelClass) ]; if ($this->option('requests')) { - $namespace = $this->domain->namespaceFor('request', $this->getNameInput()); + $namespace = $this->blueprint->getNamespaceFor('request', $this->getNameInput()); [$storeRequestClass, $updateRequestClass] = $this->generateFormRequests( $modelClass, diff --git a/src/Commands/DomainFactoryMakeCommand.php b/src/Commands/DomainFactoryMakeCommand.php index 1c53382..646d036 100644 --- a/src/Commands/DomainFactoryMakeCommand.php +++ b/src/Commands/DomainFactoryMakeCommand.php @@ -22,12 +22,12 @@ protected function getStub() protected function getNamespace($name) { - return $this->domain->namespaceFor('factory'); + return $this->blueprint->getNamespaceFor('factory'); } protected function preparePlaceholders(): array { - $domain = $this->domain; + $domain = $this->blueprint->domain; $name = $this->getNameInput(); @@ -51,6 +51,6 @@ protected function guessModelName($name) $name = substr($name, 0, -7); } - return $this->domain->model(class_basename($name))->name; + return $this->blueprint->domain->model(class_basename($name))->name; } } diff --git a/src/Commands/DomainGeneratorCommand.php b/src/Commands/DomainGeneratorCommand.php index 9dc8dd2..9d5dcd2 100644 --- a/src/Commands/DomainGeneratorCommand.php +++ b/src/Commands/DomainGeneratorCommand.php @@ -15,48 +15,11 @@ abstract class DomainGeneratorCommand extends GeneratorCommand protected function getRelativeDomainNamespace(): string { - return DomainResolver::getRelativeObjectNamespace($this->guessObjectType()); + return DomainResolver::getRelativeObjectNamespace($this->blueprint->type); } protected function getNameInput() { return Str::studly($this->argument('name')); } - - // protected function resolveStubPath($path) - // { - // $path = ltrim($path, '/\\'); - - // $publishedPath = resource_path('stubs/ddd/'.$path); - - // return file_exists($publishedPath) - // ? $publishedPath - // : __DIR__.DIRECTORY_SEPARATOR.'../../stubs'.DIRECTORY_SEPARATOR.$path; - // } - - // protected function fillPlaceholder($stub, $placeholder, $value) - // { - // return str_replace(["{{$placeholder}}", "{{ $placeholder }}"], $value, $stub); - // } - - // protected function preparePlaceholders(): array - // { - // return []; - // } - - // protected function applyPlaceholders($stub) - // { - // $placeholders = $this->preparePlaceholders(); - - // foreach ($placeholders as $placeholder => $value) { - // $stub = $this->fillPlaceholder($stub, $placeholder, $value ?? ''); - // } - - // return $stub; - // } - - // protected function buildClass($name) - // { - // return $this->applyPlaceholders(parent::buildClass($name)); - // } } diff --git a/src/Commands/DomainModelMakeCommand.php b/src/Commands/DomainModelMakeCommand.php index 1bc72b3..de45c24 100644 --- a/src/Commands/DomainModelMakeCommand.php +++ b/src/Commands/DomainModelMakeCommand.php @@ -38,7 +38,7 @@ protected function buildFactoryReplacements() $replacements = parent::buildFactoryReplacements(); if ($this->option('factory')) { - $factoryNamespace = Str::start($this->domain->factory($this->getNameInput())->fullyQualifiedName, '\\'); + $factoryNamespace = Str::start($this->blueprint->getFactoryFor($this->getNameInput())->fullyQualifiedName, '\\'); $factoryCode = << */ diff --git a/src/Commands/DomainModelMakeLegacyCommand.php b/src/Commands/DomainModelMakeLegacyCommand.php deleted file mode 100644 index 37c8164..0000000 --- a/src/Commands/DomainModelMakeLegacyCommand.php +++ /dev/null @@ -1,108 +0,0 @@ -resolveStubPath('model.php.stub'); - } - - protected function preparePlaceholders(): array - { - $baseClass = config('ddd.base_model'); - $baseClassName = class_basename($baseClass); - - return [ - 'extends' => filled($baseClass) ? " extends {$baseClassName}" : '', - 'baseClassImport' => filled($baseClass) ? "use {$baseClass};" : '', - ]; - } - - public function handle() - { - $this->createBaseModelIfNeeded(); - - parent::handle(); - - if ($this->option('factory')) { - $this->createFactory(); - } - } - - protected function createBaseModelIfNeeded() - { - if (! $this->shouldCreateBaseModel()) { - return; - } - - $baseModel = config('ddd.base_model'); - - $this->warn("Base model {$baseModel} doesn't exist, generating..."); - - $domain = DomainResolver::guessDomainFromClass($baseModel); - - $name = Str::after($baseModel, $domain); - - $this->call(DomainBaseModelMakeCommand::class, [ - '--domain' => $domain, - 'name' => $name, - ]); - } - - protected function shouldCreateBaseModel(): bool - { - $baseModel = config('ddd.base_model'); - - // If the class exists, we don't need to create it. - if (class_exists($baseModel)) { - return false; - } - - // If the class is outside of the domain layer, we won't attempt to create it. - if (! DomainResolver::isDomainClass($baseModel)) { - return false; - } - - // At this point the class is probably a domain object, but we should - // check if the expected path exists. - if (file_exists(app()->basePath(DomainResolver::guessPathFromClass($baseModel)))) { - return false; - } - - return true; - } - - protected function createFactory() - { - $this->call(DomainFactoryMakeCommand::class, [ - 'name' => $this->getNameInput().'Factory', - '--domain' => $this->domain->dotName, - '--model' => $this->qualifyClass($this->getNameInput()), - ]); - } -} diff --git a/src/Commands/DomainRequestMakeCommand.php b/src/Commands/DomainRequestMakeCommand.php index 88cad30..30f7b88 100644 --- a/src/Commands/DomainRequestMakeCommand.php +++ b/src/Commands/DomainRequestMakeCommand.php @@ -17,8 +17,6 @@ class DomainRequestMakeCommand extends RequestMakeCommand protected function rootNamespace() { - $type = $this->guessObjectType(); - - return Str::finish(DomainResolver::resolveRootNamespace($type), '\\'); + return Str::finish(DomainResolver::resolveRootNamespace($this->blueprint->type), '\\'); } } diff --git a/src/Commands/Migration/DomainMigrateMakeCommand.php b/src/Commands/Migration/DomainMigrateMakeCommand.php index e0dcb73..4dcca4b 100644 --- a/src/Commands/Migration/DomainMigrateMakeCommand.php +++ b/src/Commands/Migration/DomainMigrateMakeCommand.php @@ -18,8 +18,8 @@ class DomainMigrateMakeCommand extends BaseMigrateMakeCommand */ protected function getMigrationPath() { - if ($this->domain) { - return $this->laravel->basePath($this->domain->migrationPath); + if ($this->blueprint) { + return $this->laravel->basePath($this->blueprint->getMigrationPath()); } return $this->laravel->databasePath().DIRECTORY_SEPARATOR.'migrations'; diff --git a/src/ComposerManager.php b/src/ComposerManager.php new file mode 100755 index 0000000..df30f14 --- /dev/null +++ b/src/ComposerManager.php @@ -0,0 +1,172 @@ +composer = app(Composer::class)->setWorkingPath(app()->basePath()); + + $this->composerFile = $composerFile ?? app()->basePath('composer.json'); + + $this->data = json_decode(file_get_contents($this->composerFile), true); + } + + public static function make(?string $composerFile = null): self + { + return new self($composerFile); + } + + public function usingOutput(OutputStyle $output) + { + $this->output = $output; + + return $this; + } + + protected function guessAutoloadPathFromNamespace(string $namespace): string + { + $rootFolders = [ + 'src', + '', + ]; + + $relativePath = Str::rtrim(Path::fromNamespace($namespace), '/\\'); + + foreach ($rootFolders as $folder) { + $path = Path::join($folder, $relativePath); + + if (is_dir($path)) { + return $this->normalizePathForComposer($path); + } + } + + return $this->normalizePathForComposer("src/{$relativePath}"); + } + + protected function normalizePathForComposer($path): string + { + $path = Path::normalize($path); + + return str_replace(['\\', '/'], '/', $path); + } + + public function hasPsr4Autoload(string $namespace): bool + { + return collect($this->getPsr4Namespaces()) + ->hasAny([ + $namespace, + Str::finish($namespace, '\\'), + ]); + } + + public function registerPsr4Autoload(string $namespace, $path) + { + $namespace = str($namespace) + ->rtrim('/\\') + ->finish('\\') + ->toString(); + + $path = $path ?? $this->guessAutoloadPathFromNamespace($namespace); + + return $this->fill( + ['autoload', 'psr-4', $namespace], + $this->normalizePathForComposer($path) + ); + } + + public function fill($path, $value) + { + data_fill($this->data, $path, $value); + + return $this; + } + + protected function update($set = [], $forget = []) + { + foreach ($forget as $key) { + $this->forget($key); + } + + foreach ($set as $pair) { + [$path, $value] = $pair; + $this->fill($path, $value); + } + + return $this; + } + + public function forget($key) + { + $keys = Arr::wrap($key); + + foreach ($keys as $key) { + Arr::forget($this->data, $key); + } + + return $this; + } + + public function get($path, $default = null) + { + return data_get($this->data, $path, $default); + } + + public function getPsr4Namespaces() + { + return $this->get(['autoload', 'psr-4'], []); + } + + public function getAutoloadPath($namespace) + { + $namespace = Str::finish($namespace, '\\'); + + return $this->get(['autoload', 'psr-4', $namespace]); + } + + public function reload() + { + $this->output?->writeLn('Reloading composer (dump-autoload)...'); + + $this->composer->dumpAutoloads(); + + return $this; + } + + public function save() + { + $this->composer->modify(fn ($composerData) => $this->data); + + return $this; + } + + public function saveAndReload() + { + return $this->save()->reload(); + } + + public function toJson() + { + return json_encode($this->data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); + } + + public function toArray() + { + return $this->data; + } +} diff --git a/src/DomainManager.php b/src/DomainManager.php index 317c453..728867a 100755 --- a/src/DomainManager.php +++ b/src/DomainManager.php @@ -2,10 +2,8 @@ namespace Lunarstorm\LaravelDDD; -use Illuminate\Console\Command; -use Lunarstorm\LaravelDDD\Support\Domain; +use Lunarstorm\LaravelDDD\Support\GeneratorBlueprint; use Lunarstorm\LaravelDDD\Support\Path; -use Lunarstorm\LaravelDDD\ValueObjects\DomainCommandContext; class DomainManager { @@ -24,13 +22,18 @@ class DomainManager protected $applicationLayerFilter; /** - * The application layer object resolver callback. + * The object schema resolver callback. * * @var callable|null */ - protected $namespaceResolver; + protected $objectSchemaResolver; - protected ?DomainCommandContext $commandContext; + /** + * Resolved custom objects. + */ + protected array $resolvedObjects = []; + + protected ?GeneratorBlueprint $commandContext; protected StubManager $stubs; @@ -38,11 +41,15 @@ public function __construct() { $this->autoloadFilter = null; $this->applicationLayerFilter = null; - $this->namespaceResolver = null; $this->commandContext = null; $this->stubs = new StubManager; } + public function composer(): ComposerManager + { + return app(ComposerManager::class); + } + public function filterAutoloadPathsUsing(callable $filter): void { $this->autoloadFilter = $filter; @@ -63,24 +70,14 @@ public function getApplicationLayerFilter(): ?callable return $this->applicationLayerFilter; } - public function resolveNamespaceUsing(callable $resolver): void - { - $this->namespaceResolver = $resolver; - } - - public function getNamespaceResolver(): ?callable - { - return $this->namespaceResolver; - } - - public function captureCommandContext(Command $command, ?Domain $domain, ?string $type): void + public function resolveObjectSchemaUsing(callable $resolver): void { - $this->commandContext = DomainCommandContext::fromCommand($command, $domain, $type); + $this->objectSchemaResolver = $resolver; } - public function getCommandContext(): ?DomainCommandContext + public function getObjectSchemaResolver(): ?callable { - return $this->commandContext; + return $this->objectSchemaResolver; } public function packagePath($path = ''): string diff --git a/src/Enums/LayerType.php b/src/Enums/LayerType.php new file mode 100644 index 0000000..65e367a --- /dev/null +++ b/src/Enums/LayerType.php @@ -0,0 +1,10 @@ +app->scoped(ComposerManager::class, function () { + return ComposerManager::make(app()->basePath('composer.json')); + }); + $this->app->bind('ddd', DomainManager::class); /* @@ -27,6 +31,7 @@ public function configurePackage(Package $package): void ->hasConfigFile() ->hasCommands([ Commands\InstallCommand::class, + Commands\ConfigCommand::class, Commands\PublishCommand::class, Commands\StubCommand::class, Commands\UpgradeCommand::class, @@ -69,11 +74,6 @@ public function configurePackage(Package $package): void $package->hasCommand(Commands\DomainInterfaceMakeCommand::class); $package->hasCommand(Commands\DomainTraitMakeCommand::class); } - - // if ($this->laravelVersion('11.30.0')) { - // $package->hasCommand(Commands\PublishCommand::class); - // $package->hasCommand(Commands\StubCommand::class); - // } } protected function laravelVersion($value) diff --git a/src/Support/Concerns/InteractsWithComposer.php b/src/Support/Concerns/InteractsWithComposer.php new file mode 100644 index 0000000..cc1bbee --- /dev/null +++ b/src/Support/Concerns/InteractsWithComposer.php @@ -0,0 +1,30 @@ +rtrim('/\\') + ->finish('\\') + ->toString(); + + $this->composerFill(['autoload', 'psr-4', $namespace], $path); + + return $this; + } +} diff --git a/src/Support/Domain.php b/src/Support/Domain.php index 2a94d5c..d8e608b 100644 --- a/src/Support/Domain.php +++ b/src/Support/Domain.php @@ -2,7 +2,6 @@ namespace Lunarstorm\LaravelDDD\Support; -use Lunarstorm\LaravelDDD\ValueObjects\DomainNamespaces; use Lunarstorm\LaravelDDD\ValueObjects\DomainObject; class Domain @@ -19,8 +18,6 @@ class Domain public readonly string $domainWithSubdomain; - public readonly DomainNamespaces $namespace; - public readonly Layer $layer; public static array $objects = []; @@ -60,8 +57,6 @@ public function __construct(string $domain, ?string $subdomain = null) $this->layer = DomainResolver::resolveLayer($this->domainWithSubdomain); - $this->namespace = DomainNamespaces::from($this->domain, $this->subdomain); - $this->path = $this->layer->path; $this->migrationPath = Path::join($this->path, config('ddd.namespaces.migration', 'Database/Migrations')); @@ -107,6 +102,16 @@ public function relativePath(string $path = ''): string return collect([$this->domain, $path])->filter()->implode(DIRECTORY_SEPARATOR); } + public function rootNamespace(): string + { + return $this->layer->namespace; + } + + public function intendedLayerFor(string $type) + { + return DomainResolver::resolveLayer($this->domainWithSubdomain, $type); + } + public function namespaceFor(string $type, ?string $name = null): string { return DomainResolver::getDomainObjectNamespace($this->domainWithSubdomain, $type, $name); @@ -125,15 +130,9 @@ public function guessNamespaceFromName(string $name): string public function object(string $type, string $name, bool $absolute = false): DomainObject { - $resolver = app('ddd')->getNamespaceResolver(); - - $customNamespace = is_callable($resolver) - ? $resolver($this->domainWithSubdomain, $type, app('ddd')->getCommandContext()) - : null; + $layer = $this->intendedLayerFor($type); - $layer = DomainResolver::resolveLayer($this->domainWithSubdomain, $type); - - $namespace = $customNamespace ?? match (true) { + $namespace = match (true) { $absolute => $layer->namespace, str($name)->startsWith('\\') => $layer->guessNamespaceFromName($name), default => $layer->namespaceFor($type), @@ -147,19 +146,6 @@ public function object(string $type, string $name, bool $absolute = false): Doma $fullyQualifiedName = $namespace.'\\'.$baseName; - if ($customNamespace) { - return new DomainObject( - name: $baseName, - domain: $this->domain, - namespace: $namespace, - fullyQualifiedName: $fullyQualifiedName, - path: DomainResolver::isApplicationLayer($type) - ? $this->pathInApplicationLayer($fullyQualifiedName) - : $this->path($fullyQualifiedName), - type: $type - ); - } - return new DomainObject( name: $baseName, domain: $this->domain, diff --git a/src/Support/DomainResolver.php b/src/Support/DomainResolver.php index 6559dad..69f4892 100644 --- a/src/Support/DomainResolver.php +++ b/src/Support/DomainResolver.php @@ -3,6 +3,7 @@ namespace Lunarstorm\LaravelDDD\Support; use Illuminate\Support\Str; +use Lunarstorm\LaravelDDD\Enums\LayerType; class DomainResolver { @@ -94,17 +95,23 @@ public static function resolveLayer(string $domain, ?string $type = null): ?Laye { $layers = config('ddd.layers', []); - return match (true) { - array_key_exists($domain, $layers) => new Layer($domain, $layers[$domain]), - - $type && static::isApplicationLayer($type) => new Layer( + // Objects in the application layer take precedence + if ($type && static::isApplicationLayer($type)) { + return new Layer( static::applicationLayerRootNamespace().'\\'.$domain, Path::join(static::applicationLayerPath(), $domain), - ), + LayerType::Application, + ); + } + + return match (true) { + array_key_exists($domain, $layers) + && is_string($layers[$domain]) => new Layer($domain, $layers[$domain], LayerType::Custom), default => new Layer( static::domainRootNamespace().'\\'.$domain, Path::join(static::domainPath(), $domain), + LayerType::Domain, ) }; } @@ -118,15 +125,15 @@ public static function resolveLayer(string $domain, ?string $type = null): ?Laye */ public static function getDomainObjectNamespace(string $domain, string $type, ?string $name = null): string { - $customResolver = app('ddd')->getNamespaceResolver(); + // $customResolver = app('ddd')->getNamespaceResolver(); - $resolved = is_callable($customResolver) - ? $customResolver($domain, $type, app('ddd')->getCommandContext()) - : null; + // $resolved = is_callable($customResolver) + // ? $customResolver($domain, $type, $name, app('ddd')->getCommandContext()) + // : null; - if (! is_null($resolved)) { - return $resolved; - } + // if (! is_null($resolved)) { + // return $resolved; + // } $resolver = function (string $domain, string $type, ?string $name) { $layer = static::resolveLayer($domain, $type); diff --git a/src/Support/GeneratorBlueprint.php b/src/Support/GeneratorBlueprint.php new file mode 100644 index 0000000..5d2aa6c --- /dev/null +++ b/src/Support/GeneratorBlueprint.php @@ -0,0 +1,131 @@ +nameInput = Str::replace(['.', '\\', '/'], '/', $nameInput); + + $this->domain = new Domain($domainName); + + $this->domainName = $this->domain->domainWithSubdomain; + + $this->command = new CommandContext($command->getName(), $command->arguments(), $command->options()); + + $this->isAbsoluteName = str($this->nameInput)->startsWith('/'); + + $this->type = $this->guessObjectType(); + + $this->layer = DomainResolver::resolveLayer($this->domainName, $this->type); + + $this->schema = $this->resolveSchema(); + } + + protected function guessObjectType(): string + { + return match ($this->command->name) { + 'ddd:base-view-model' => 'view_model', + 'ddd:base-model' => 'model', + 'ddd:value' => 'value_object', + 'ddd:dto' => 'data_transfer_object', + 'ddd:migration' => 'migration', + default => str($this->command->name)->after(':')->snake()->toString(), + }; + } + + protected function resolveSchema(): ObjectSchema + { + $customResolver = app('ddd')->getObjectSchemaResolver(); + + $blueprint = is_callable($customResolver) + ? App::call($customResolver, [ + 'domainName' => $this->domainName, + 'nameInput' => $this->nameInput, + 'type' => $this->type, + 'command' => $this->command, + ]) + : null; + + if ($blueprint instanceof ObjectSchema) { + return $blueprint; + } + + $namespace = match (true) { + $this->isAbsoluteName => $this->layer->namespace, + str($this->nameInput)->startsWith('\\') => $this->layer->guessNamespaceFromName($this->nameInput), + default => $this->layer->namespaceFor($this->type), + }; + + $baseName = str($this->nameInput)->replace($namespace, '') + ->replace(['\\', '/'], '\\') + ->trim('\\') + ->when($this->type === 'factory', fn ($name) => $name->finish('Factory')) + ->toString(); + + $fullyQualifiedName = $namespace.'\\'.$baseName; + + return new ObjectSchema( + name: $this->nameInput, + namespace: $namespace, + fullyQualifiedName: $fullyQualifiedName, + path: $this->layer->path($fullyQualifiedName), + ); + } + + public function rootNamespace() + { + return str($this->schema->namespace)->finish('\\')->toString(); + } + + public function getDefaultNamespace($rootNamespace) + { + return $this->schema->namespace; + } + + public function getPath($name) + { + return Path::normalize(app()->basePath($this->schema->path)); + } + + public function getFactoryFor(string $name) + { + return $this->domain->factory($name); + } + + public function getMigrationPath() + { + return $this->domain->migrationPath; + } + + public function getNamespaceFor($type, $name = null) + { + return $this->domain->namespaceFor($type, $name); + } +} diff --git a/src/Support/Layer.php b/src/Support/Layer.php index 7019956..c7a5a43 100644 --- a/src/Support/Layer.php +++ b/src/Support/Layer.php @@ -2,6 +2,9 @@ namespace Lunarstorm\LaravelDDD\Support; +use Illuminate\Support\Str; +use Lunarstorm\LaravelDDD\Enums\LayerType; + class Layer { public readonly ?string $namespace; @@ -9,18 +12,20 @@ class Layer public readonly ?string $path; public function __construct( - ?string $namespace, - ?string $path, + string $namespace, + ?string $path = null, + public ?LayerType $type = null, ) { - $this->namespace = Path::normalizeNamespace($namespace); + $this->namespace = Path::normalizeNamespace(Str::replaceEnd('\\', '', $namespace)); + $this->path = is_null($path) - ? $this->path() - : Path::normalize($path); + ? Path::fromNamespace($this->namespace) + : Path::normalize(Str::replaceEnd('/', '', $path)); } public static function fromNamespace(string $namespace): self { - return new self($namespace, null); + return new self($namespace); } public function path(?string $path = null): string diff --git a/src/ValueObjects/CommandContext.php b/src/ValueObjects/CommandContext.php new file mode 100644 index 0000000..5ad6032 --- /dev/null +++ b/src/ValueObjects/CommandContext.php @@ -0,0 +1,32 @@ +options); + } + + public function option(string $key): mixed + { + return data_get($this->options, $key); + } + + public function hasArgument(string $key): bool + { + return array_key_exists($key, $this->arguments); + } + + public function argument(string $key): mixed + { + return data_get($this->arguments, $key); + } +} diff --git a/src/ValueObjects/DomainCommandContext.php b/src/ValueObjects/DomainCommandContext.php deleted file mode 100644 index 692ceed..0000000 --- a/src/ValueObjects/DomainCommandContext.php +++ /dev/null @@ -1,51 +0,0 @@ -getName(), - domain: $domain?->domainWithSubdomain, - type: $type, - resource: $command->argument('name'), - arguments: $command->arguments(), - options: $command->options(), - ); - } - - public function hasOption(string $key): bool - { - return array_key_exists($key, $this->options); - } - - public function option(string $key): mixed - { - return data_get($this->options, $key); - } - - public function hasArgument(string $key): bool - { - return array_key_exists($key, $this->arguments); - } - - public function argument(string $key): mixed - { - return data_get($this->arguments, $key); - } -} diff --git a/src/ValueObjects/ObjectSchema.php b/src/ValueObjects/ObjectSchema.php new file mode 100644 index 0000000..e3b78aa --- /dev/null +++ b/src/ValueObjects/ObjectSchema.php @@ -0,0 +1,13 @@ +setupTestApplication(); -}); - -it('can register a custom namespace resolver', function () { - Config::set('ddd.application', [ - 'path' => 'src/App', - 'namespace' => 'App', - ]); - - DDD::resolveNamespaceUsing(function (string $domain, string $type, ?DomainCommandContext $context): ?string { - if ($type == 'controller' && $context->option('api')) { - return "App\\Api\\Controllers\\{$domain}"; - } - - return null; - }); - - Artisan::call('ddd:controller', [ - 'name' => 'PaymentApiController', - '--domain' => 'Invoicing', - '--api' => true, - ]); - - $output = Artisan::output(); - - expect($output) - ->toContainFilepath('src/App/Api/Controllers/Invoicing/PaymentApiController.php'); - - $expectedPath = base_path('src/App/Api/Controllers/Invoicing/PaymentApiController.php'); - - expect(file_get_contents($expectedPath)) - ->toContain("namespace App\Api\Controllers\Invoicing;"); -}); diff --git a/tests/Generator/CustomLayerTest.php b/tests/Generator/CustomLayerTest.php new file mode 100644 index 0000000..a66fc4b --- /dev/null +++ b/tests/Generator/CustomLayerTest.php @@ -0,0 +1,92 @@ +cleanSlate(); + + Config::set('ddd.layers', [ + 'Infrastructure' => 'src/Infrastructure', + ]); +}); + +it('can generate objects into custom layers', function ($type, $objectName, $expectedNamespace, $expectedPath) { + if (in_array($type, ['class', 'enum', 'interface', 'trait'])) { + skipOnLaravelVersionsBelow('11'); + } + + $relativePath = $expectedPath; + $expectedPath = base_path($relativePath); + + if (file_exists($expectedPath)) { + unlink($expectedPath); + } + + expect(file_exists($expectedPath))->toBeFalse(); + + $command = "ddd:{$type} Infrastructure:{$objectName}"; + + Artisan::call($command); + + expect(Artisan::output())->toContainFilepath($relativePath); + + expect(file_exists($expectedPath))->toBeTrue(); + + expect(file_get_contents($expectedPath))->toContain("namespace {$expectedNamespace};"); +})->with([ + 'action' => ['action', 'SomeAction', 'Infrastructure\Actions', 'src/Infrastructure/Actions/SomeAction.php'], + 'cast' => ['cast', 'SomeCast', 'Infrastructure\Casts', 'src/Infrastructure/Casts/SomeCast.php'], + 'channel' => ['channel', 'SomeChannel', 'Infrastructure\Channels', 'src/Infrastructure/Channels/SomeChannel.php'], + 'command' => ['command', 'SomeCommand', 'Infrastructure\Commands', 'src/Infrastructure/Commands/SomeCommand.php'], + 'event' => ['event', 'SomeEvent', 'Infrastructure\Events', 'src/Infrastructure/Events/SomeEvent.php'], + 'exception' => ['exception', 'SomeException', 'Infrastructure\Exceptions', 'src/Infrastructure/Exceptions/SomeException.php'], + 'job' => ['job', 'SomeJob', 'Infrastructure\Jobs', 'src/Infrastructure/Jobs/SomeJob.php'], + 'listener' => ['listener', 'SomeListener', 'Infrastructure\Listeners', 'src/Infrastructure/Listeners/SomeListener.php'], + 'mail' => ['mail', 'SomeMail', 'Infrastructure\Mail', 'src/Infrastructure/Mail/SomeMail.php'], + 'notification' => ['notification', 'SomeNotification', 'Infrastructure\Notifications', 'src/Infrastructure/Notifications/SomeNotification.php'], + 'observer' => ['observer', 'SomeObserver', 'Infrastructure\Observers', 'src/Infrastructure/Observers/SomeObserver.php'], + 'policy' => ['policy', 'SomePolicy', 'Infrastructure\Policies', 'src/Infrastructure/Policies/SomePolicy.php'], + 'provider' => ['provider', 'SomeProvider', 'Infrastructure\Providers', 'src/Infrastructure/Providers/SomeProvider.php'], + 'resource' => ['resource', 'SomeResource', 'Infrastructure\Resources', 'src/Infrastructure/Resources/SomeResource.php'], + 'rule' => ['rule', 'SomeRule', 'Infrastructure\Rules', 'src/Infrastructure/Rules/SomeRule.php'], + 'scope' => ['scope', 'SomeScope', 'Infrastructure\Scopes', 'src/Infrastructure/Scopes/SomeScope.php'], + 'seeder' => ['seeder', 'SomeSeeder', 'Infrastructure\Database\Seeders', 'src/Infrastructure/Database/Seeders/SomeSeeder.php'], + 'class' => ['class', 'SomeClass', 'Infrastructure', 'src/Infrastructure/SomeClass.php'], + 'enum' => ['enum', 'SomeEnum', 'Infrastructure\Enums', 'src/Infrastructure/Enums/SomeEnum.php'], + 'interface' => ['interface', 'SomeInterface', 'Infrastructure', 'src/Infrastructure/SomeInterface.php'], + 'trait' => ['trait', 'SomeTrait', 'Infrastructure', 'src/Infrastructure/SomeTrait.php'], +]); + +it('ignores custom layer if object belongs in the application layer', function ($type, $objectName, $expectedNamespace, $expectedPath) { + Config::set('ddd.application', [ + 'namespace' => 'Application', + 'path' => 'src/Application', + 'objects' => [ + $type, + ], + ]); + + $relativePath = $expectedPath; + $expectedPath = base_path($relativePath); + + if (file_exists($expectedPath)) { + unlink($expectedPath); + } + + expect(file_exists($expectedPath))->toBeFalse(); + + $command = "ddd:{$type} Infrastructure:{$objectName}"; + + Artisan::call($command); + + expect(Artisan::output())->toContainFilepath($relativePath); + + expect(file_exists($expectedPath))->toBeTrue(); + + expect(file_get_contents($expectedPath))->toContain("namespace {$expectedNamespace};"); +})->with([ + 'request' => ['request', 'SomeRequest', 'Application\Infrastructure\Requests', 'src/Application/Infrastructure/Requests/SomeRequest.php'], + 'controller' => ['controller', 'SomeController', 'Application\Infrastructure\Controllers', 'src/Application/Infrastructure/Controllers/SomeController.php'], + 'middleware' => ['middleware', 'SomeMiddleware', 'Application\Infrastructure\Middleware', 'src/Application/Infrastructure/Middleware/SomeMiddleware.php'], +]); diff --git a/tests/Generator/DtoMakeTestTest.php b/tests/Generator/DtoMakeTest.php similarity index 100% rename from tests/Generator/DtoMakeTestTest.php rename to tests/Generator/DtoMakeTest.php diff --git a/tests/Generator/ExtendedCommandsTest.php b/tests/Generator/ExtendedMakeTest.php similarity index 95% rename from tests/Generator/ExtendedCommandsTest.php rename to tests/Generator/ExtendedMakeTest.php index 52d8959..ce8a0be 100644 --- a/tests/Generator/ExtendedCommandsTest.php +++ b/tests/Generator/ExtendedMakeTest.php @@ -4,7 +4,7 @@ use Illuminate\Support\Facades\Config; use Lunarstorm\LaravelDDD\Support\Domain; -it('can generate extended objects', function ($type, $objectName, $domainPath, $domainRoot) { +it('can generate other objects', function ($type, $objectName, $domainPath, $domainRoot) { if (in_array($type, ['class', 'enum', 'interface', 'trait'])) { skipOnLaravelVersionsBelow('11'); } diff --git a/tests/Support/ResolveLayerTest.php b/tests/Support/ResolveLayerTest.php index 6b25d23..5060177 100644 --- a/tests/Support/ResolveLayerTest.php +++ b/tests/Support/ResolveLayerTest.php @@ -27,7 +27,7 @@ ['Reporting\\Internal', 'Domain\\Reporting\\Internal', 'src/Domain/Reporting/Internal'], ]); -it('resolves normally when no matching custom layer is found', function ($domainName, $namespace, $path) { +it('resolves normally when no custom layer is found', function ($domainName, $namespace, $path) { Config::set('ddd.layers', [ 'SupportNotMatching' => 'src/Support', ]); diff --git a/tests/Support/ResolveNamespaceTest.php b/tests/Support/ResolveObjectSchemaUsingTest.php similarity index 56% rename from tests/Support/ResolveNamespaceTest.php rename to tests/Support/ResolveObjectSchemaUsingTest.php index c1f6a48..f43dcad 100644 --- a/tests/Support/ResolveNamespaceTest.php +++ b/tests/Support/ResolveObjectSchemaUsingTest.php @@ -3,7 +3,8 @@ use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Config; use Lunarstorm\LaravelDDD\Facades\DDD; -use Lunarstorm\LaravelDDD\ValueObjects\DomainCommandContext; +use Lunarstorm\LaravelDDD\ValueObjects\CommandContext; +use Lunarstorm\LaravelDDD\ValueObjects\ObjectSchema; beforeEach(function () { Config::set('ddd.domain_path', 'src/Domain'); @@ -18,16 +19,21 @@ 'namespace' => 'App', ]); - DDD::resolveNamespaceUsing(function (string $domain, string $type, ?DomainCommandContext $context): ?string { - if ($type == 'controller' && $context->option('api')) { - return "App\\Api\\Controllers\\{$domain}"; + DDD::resolveObjectSchemaUsing(function (string $domainName, ?string $nameInput, string $type, CommandContext $command): ?ObjectSchema { + if ($type === 'controller' && $command->option('api')) { + return new ObjectSchema( + name: $name = str($nameInput)->replaceEnd('Controller', '')->finish('ApiController')->toString(), + namespace: "App\\Api\\Controllers\\{$domainName}", + fullyQualifiedName: "App\\Api\\Controllers\\{$domainName}\\{$name}", + path: "src/App/Api/Controllers/{$domainName}/{$name}.php", + ); } return null; }); Artisan::call('ddd:controller', [ - 'name' => 'PaymentApiController', + 'name' => 'PaymentController', '--domain' => 'Invoicing', '--api' => true, ]); From 26b193d650f43d06f6d3b2cdb0e72bd7e0318e03 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 12 Nov 2024 00:22:53 -0500 Subject: [PATCH 038/169] Cler cache afterEach --- tests/Command/CacheTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Command/CacheTest.php b/tests/Command/CacheTest.php index f7aec3f..4881fd6 100644 --- a/tests/Command/CacheTest.php +++ b/tests/Command/CacheTest.php @@ -14,6 +14,8 @@ afterEach(function () { $this->artisan('optimize:clear')->execute(); + + DomainCache::clear(); }); it('can cache discovered domain providers, commands, migrations', function () { From 06d473ecf2a82159331f01a1a02c2a1e1facee9f Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 12 Nov 2024 00:27:14 -0500 Subject: [PATCH 039/169] Add some assertion messaging. --- tests/Generator/ActionMakeTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Generator/ActionMakeTest.php b/tests/Generator/ActionMakeTest.php index 2bdb1f2..f99c1f4 100644 --- a/tests/Generator/ActionMakeTest.php +++ b/tests/Generator/ActionMakeTest.php @@ -31,7 +31,7 @@ expect(Artisan::output())->when( Feature::IncludeFilepathInGeneratorCommandOutput->exists(), - fn ($output) => $output->toContainFilepath($relativePath), + fn($output) => $output->toContainFilepath($relativePath), ); expect(file_exists($expectedPath))->toBeTrue(); @@ -57,7 +57,7 @@ Artisan::call("ddd:action {$domain}:{$given}"); - expect(file_exists($expectedPath))->toBeTrue(); + expect(file_exists($expectedPath))->toBeTrue("ddd:action {$domain}:{$given} -> expected {$expectedPath} to exist."); })->with('makeActionInputs'); it('extends a base action if specified in config', function ($baseAction) { @@ -81,7 +81,7 @@ expect(file_exists($expectedPath))->toBeTrue(); - expect(file_get_contents($expectedPath))->toContain("class {$name} extends {$baseAction}".PHP_EOL.'{'); + expect(file_get_contents($expectedPath))->toContain("class {$name} extends {$baseAction}" . PHP_EOL . '{'); })->with([ 'BaseAction' => 'BaseAction', 'Base\Action' => 'Base\Action', @@ -107,5 +107,5 @@ Artisan::call("ddd:action {$domain}:{$name}"); expect(file_exists($expectedPath))->toBeTrue(); - expect(file_get_contents($expectedPath))->toContain("class {$name}".PHP_EOL.'{'); + expect(file_get_contents($expectedPath))->toContain("class {$name}" . PHP_EOL . '{'); }); From 91f7fb503d0063c341ce86c93412d0e5e309e75d Mon Sep 17 00:00:00 2001 From: JasperTey Date: Tue, 12 Nov 2024 05:27:38 +0000 Subject: [PATCH 040/169] Fix styling --- tests/Generator/ActionMakeTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Generator/ActionMakeTest.php b/tests/Generator/ActionMakeTest.php index f99c1f4..52148fb 100644 --- a/tests/Generator/ActionMakeTest.php +++ b/tests/Generator/ActionMakeTest.php @@ -31,7 +31,7 @@ expect(Artisan::output())->when( Feature::IncludeFilepathInGeneratorCommandOutput->exists(), - fn($output) => $output->toContainFilepath($relativePath), + fn ($output) => $output->toContainFilepath($relativePath), ); expect(file_exists($expectedPath))->toBeTrue(); @@ -81,7 +81,7 @@ expect(file_exists($expectedPath))->toBeTrue(); - expect(file_get_contents($expectedPath))->toContain("class {$name} extends {$baseAction}" . PHP_EOL . '{'); + expect(file_get_contents($expectedPath))->toContain("class {$name} extends {$baseAction}".PHP_EOL.'{'); })->with([ 'BaseAction' => 'BaseAction', 'Base\Action' => 'Base\Action', @@ -107,5 +107,5 @@ Artisan::call("ddd:action {$domain}:{$name}"); expect(file_exists($expectedPath))->toBeTrue(); - expect(file_get_contents($expectedPath))->toContain("class {$name}" . PHP_EOL . '{'); + expect(file_get_contents($expectedPath))->toContain("class {$name}".PHP_EOL.'{'); }); From b6617d78c34f1a0e0f65bea3a1e06d27b8723043 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 12 Nov 2024 00:36:08 -0500 Subject: [PATCH 041/169] Ensure nameInput is studly. --- src/Support/GeneratorBlueprint.php | 3 +-- tests/Generator/ActionMakeTest.php | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Support/GeneratorBlueprint.php b/src/Support/GeneratorBlueprint.php index 5d2aa6c..6333347 100644 --- a/src/Support/GeneratorBlueprint.php +++ b/src/Support/GeneratorBlueprint.php @@ -4,7 +4,6 @@ use Illuminate\Console\Command; use Illuminate\Support\Facades\App; -use Illuminate\Support\Str; use Lunarstorm\LaravelDDD\ValueObjects\CommandContext; use Lunarstorm\LaravelDDD\ValueObjects\ObjectSchema; @@ -31,7 +30,7 @@ public function __construct( string $domainName, Command $command, ) { - $this->nameInput = Str::replace(['.', '\\', '/'], '/', $nameInput); + $this->nameInput = str($nameInput)->studly()->replace(['.', '\\', '/'], '/')->toString(); $this->domain = new Domain($domainName); diff --git a/tests/Generator/ActionMakeTest.php b/tests/Generator/ActionMakeTest.php index 52148fb..a94e289 100644 --- a/tests/Generator/ActionMakeTest.php +++ b/tests/Generator/ActionMakeTest.php @@ -55,10 +55,12 @@ "{$normalized}.php", ])); + dump($expectedPath); + Artisan::call("ddd:action {$domain}:{$given}"); expect(file_exists($expectedPath))->toBeTrue("ddd:action {$domain}:{$given} -> expected {$expectedPath} to exist."); -})->with('makeActionInputs'); +})->with('makeActionInputs')->only(); it('extends a base action if specified in config', function ($baseAction) { Config::set('ddd.base_action', $baseAction); From 4dba528c51a765e7073b7f7abb45c51b4b055342 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 12 Nov 2024 00:37:32 -0500 Subject: [PATCH 042/169] Remove dump --- tests/Generator/ActionMakeTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Generator/ActionMakeTest.php b/tests/Generator/ActionMakeTest.php index a94e289..ab55725 100644 --- a/tests/Generator/ActionMakeTest.php +++ b/tests/Generator/ActionMakeTest.php @@ -55,8 +55,6 @@ "{$normalized}.php", ])); - dump($expectedPath); - Artisan::call("ddd:action {$domain}:{$given}"); expect(file_exists($expectedPath))->toBeTrue("ddd:action {$domain}:{$given} -> expected {$expectedPath} to exist."); From 1dab1f53184f807efc61f04fa76949714ad36983 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 12 Nov 2024 01:12:20 -0500 Subject: [PATCH 043/169] Update readme and tests. --- README.md | 65 ++++++++++++++++--- tests/Generator/ActionMakeTest.php | 2 +- .../Support/ResolveObjectSchemaUsingTest.php | 2 +- 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0906314..82c9c55 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,36 @@ Output: └─ Invoice.php ``` +### Custom Layers (since 1.2) +Often times, additional top-level namespaces are needed to hold shared components, helpers, and things that are not domain-specific. A common example is the `Infrastructure` layer. You may configure these additional layers in the `ddd.layers` configuration. +```php +// In config/ddd.php +'layers' => [ + 'Infrastructure' => 'src/Infrastructure', + // 'Support' => 'src/Support', +], +``` +The configuration above will result in the following: +```bash +ddd:model Invoicing:Invoice +ddd:trait Infrastructure:Concerns/HasExpiryDate +``` +Output: +``` +├─ src/Domain +| └─ Invoicing +| └─ Models +| └─ Invoice.php +├─ src/Infrastructure + └─ Concerns + └─ HasExpiryDate.php +``` +After defining new layers in `ddd.php`, make sure the corresponding namespaces are also registered in your `composer.json` file. You may use the `ddd:config` helper command to handle this for you. +```bash +# Sync composer.json with ddd.php +php artisan ddd:config composer +``` + ### Nested Objects For any `ddd:*` generator command, nested objects can be specified with forward slashes. ```bash @@ -359,17 +389,12 @@ return [ | Application Layer |-------------------------------------------------------------------------- | - | Configure objects that belong in the application layer. - | - | e.g., App\Modules\Invoicing\Controllers\* - | App\Modules\Invoicing\Requests\* + | Configure domain objects in the application layer. | */ 'application' => [ - 'path' => 'app/Modules', 'namespace' => 'App\Modules', - - // Specify which ddd:* objects belong in the application layer + 'path' => 'app/Modules', 'objects' => [ 'controller', 'request', @@ -379,11 +404,31 @@ return [ /* |-------------------------------------------------------------------------- - | Generator Object Namespaces + | Custom Layers + |-------------------------------------------------------------------------- + | + | Mapping of additional top-level namespaces and paths that should + | be recognized as layers when generating ddd:* objects. + | + | e.g., 'Infrastructure' => 'src/Infrastructure', + | + | When using ddd:* generators, specifying a domain matching a key in + | this array will generate objects in that corresponding layer. + | + */ + 'layers' => [ + // 'Infrastructure' => 'src/Infrastructure', + // 'Integrations' => 'src/Integrations', + // 'Support' => 'src/Support', + ], + + /* + |-------------------------------------------------------------------------- + | Domain Object Namespaces |-------------------------------------------------------------------------- | - | This array maps the default relative namespaces of generated objects - | relative to their domain's root namespace. + | This value contains the default namespaces of ddd:* generated + | objects relative to the layer of which the object belongs to. | | e.g., Domain\Invoicing\Models\* | Domain\Invoicing\Data\* diff --git a/tests/Generator/ActionMakeTest.php b/tests/Generator/ActionMakeTest.php index ab55725..52148fb 100644 --- a/tests/Generator/ActionMakeTest.php +++ b/tests/Generator/ActionMakeTest.php @@ -58,7 +58,7 @@ Artisan::call("ddd:action {$domain}:{$given}"); expect(file_exists($expectedPath))->toBeTrue("ddd:action {$domain}:{$given} -> expected {$expectedPath} to exist."); -})->with('makeActionInputs')->only(); +})->with('makeActionInputs'); it('extends a base action if specified in config', function ($baseAction) { Config::set('ddd.base_action', $baseAction); diff --git a/tests/Support/ResolveObjectSchemaUsingTest.php b/tests/Support/ResolveObjectSchemaUsingTest.php index f43dcad..ec5ddaf 100644 --- a/tests/Support/ResolveObjectSchemaUsingTest.php +++ b/tests/Support/ResolveObjectSchemaUsingTest.php @@ -19,7 +19,7 @@ 'namespace' => 'App', ]); - DDD::resolveObjectSchemaUsing(function (string $domainName, ?string $nameInput, string $type, CommandContext $command): ?ObjectSchema { + DDD::resolveObjectSchemaUsing(function (string $domainName, string $nameInput, string $type, CommandContext $command): ?ObjectSchema { if ($type === 'controller' && $command->option('api')) { return new ObjectSchema( name: $name = str($nameInput)->replaceEnd('Controller', '')->finish('ApiController')->toString(), From 4661c3a332854fe611ad2f1e56625206871ca88a Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 01:28:16 -0500 Subject: [PATCH 044/169] Improved config management plus test coverage. --- README.md | 17 +- composer.json | 5 +- config/ddd.php | 9 +- config/ddd.php.stub | 78 +++----- src/Commands/ConfigCommand.php | 60 ++++--- src/Commands/DomainListCommand.php | 4 +- src/Commands/InstallCommand.php | 70 ++++---- src/Commands/UpgradeCommand.php | 60 +++++-- src/ComposerManager.php | 7 + src/ConfigManager.php | 148 +++++++++++++++ src/DomainManager.php | 5 + src/Facades/DDD.php | 1 + src/LaravelDDDServiceProvider.php | 4 + tests/Command/ConfigTest.php | 180 +++++++++++++++++++ tests/Command/InstallTest.php | 16 +- tests/Command/resources/composer.sample.json | 26 +++ tests/Config/ManagerTest.php | 51 ++++++ tests/Config/resources/config.sparse.php | 25 +++ tests/Datasets/resources/config.0.10.0.php | 132 +++++++------- tests/TestCase.php | 1 + 20 files changed, 682 insertions(+), 217 deletions(-) create mode 100755 src/ConfigManager.php create mode 100644 tests/Command/ConfigTest.php create mode 100644 tests/Command/resources/composer.sample.json create mode 100644 tests/Config/ManagerTest.php create mode 100644 tests/Config/resources/config.sparse.php diff --git a/README.md b/README.md index 82c9c55..753d582 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Domain Driven Design toolkit for Laravel +# Domain Driven Design Toolkit for Laravel [![Latest Version on Packagist](https://img.shields.io/packagist/v/lunarstorm/laravel-ddd.svg?style=flat-square)](https://packagist.org/packages/lunarstorm/laravel-ddd) [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/lunarstorm/laravel-ddd/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/lunarstorm/laravel-ddd/actions?query=workflow%3Arun-tests+branch%3Amain) @@ -21,16 +21,9 @@ php artisan ddd:install ### Peer Dependencies The following additional packages are suggested (but not required) while working with this package. +- Data Transfer Objects: [spatie/laravel-data](https://github.com/spatie/laravel-data) +- Actions: [lorisleiva/laravel-actions](https://github.com/lorisleiva/laravel-actions) -Data Transfer Objects: [spatie/laravel-data](https://github.com/spatie/laravel-data) -```bash -composer require spatie/laravel-data -``` - -Actions: [lorisleiva/laravel-actions](https://github.com/lorisleiva/laravel-actions) -```bash -composer require lorisleiva/laravel-actions -``` The default DTO and Action stubs of this package reference classes from these packages. If this doesn't apply to your application, you may [customize the stubs](#publishing-stubs-advanced) accordingly. ### Deployment @@ -38,7 +31,7 @@ In production, run `ddd:optimize` during the deployment process to [optimize aut ```bash php artisan ddd:optimize ``` -Since Laravel 11.27.1, `php artisan optimize` automatically invokes `ddd:optimize`. If you already run `optimize` in production, a separate `ddd:optimize` is no longer necessary. +Since Laravel 11.27.1, `php artisan optimize` automatically invokes `ddd:optimize`. If you already run `optimize` in production, a separate `ddd:optimize` is no longer necessary. In previous versions of this package, this command was named `ddd:cache`, which will continue to work as an alias. ### Version Compatibility Laravel | LaravelDDD | | @@ -393,8 +386,8 @@ return [ | */ 'application' => [ - 'namespace' => 'App\Modules', 'path' => 'app/Modules', + 'namespace' => 'App\Modules', 'objects' => [ 'controller', 'request', diff --git a/composer.json b/composer.json index fd4f4e5..7bcfdf1 100644 --- a/composer.json +++ b/composer.json @@ -20,13 +20,14 @@ "require": { "php": "^8.1|^8.2|^8.3", "illuminate/contracts": "^10.25|^11.0", + "laravel/pint": "^1.18", "laravel/prompts": "^0.1.16|^0.3.1", "lorisleiva/lody": "^0.5.0", - "spatie/laravel-package-tools": "^1.13.0" + "spatie/laravel-package-tools": "^1.13.0", + "symfony/var-exporter": "^7.1" }, "require-dev": { "larastan/larastan": "^2.0.1", - "laravel/pint": "^1.0", "nunomaduro/collision": "^7.0|^8.1", "orchestra/testbench": "^8|^9.0", "pestphp/pest": "^2.34", diff --git a/config/ddd.php b/config/ddd.php index 082196a..1a051af 100644 --- a/config/ddd.php +++ b/config/ddd.php @@ -31,8 +31,8 @@ | */ 'application' => [ - 'namespace' => 'App\Modules', 'path' => 'app/Modules', + 'namespace' => 'App\Modules', 'objects' => [ 'controller', 'request', @@ -45,14 +45,11 @@ | Custom Layers |-------------------------------------------------------------------------- | - | Mapping of additional top-level namespaces and paths that should - | be recognized as layers when generating ddd:* objects. + | Additional top-level namespaces and paths that should be recognized as + | layers when generating ddd:* objects. | | e.g., 'Infrastructure' => 'src/Infrastructure', | - | When using ddd:* generators, specifying a domain matching a key in - | this array will generate objects in that corresponding layer. - | */ 'layers' => [ 'Infrastructure' => 'src/Infrastructure', diff --git a/config/ddd.php.stub b/config/ddd.php.stub index 612b1d8..41e6874 100644 --- a/config/ddd.php.stub +++ b/config/ddd.php.stub @@ -27,23 +27,27 @@ return [ | Application Layer |-------------------------------------------------------------------------- | - | Configure objects that belong in the application layer. + | Configure domain objects in the application layer. | - | e.g., App\Modules\Invoicing\Controllers\* - | App\Modules\Invoicing\Requests\* + */ + 'application' => {{application}}, + + /* + |-------------------------------------------------------------------------- + | Custom Layers + |-------------------------------------------------------------------------- + | + | Additional top-level namespaces and paths that should + | be recognized as layers when generating ddd:* objects. + | + | e.g., 'Infrastructure' => 'src/Infrastructure', + | + | When using ddd:* generators, specifying a domain matching a key in + | this array will generate objects in that corresponding layer. | */ - 'application' => [ - 'path' => 'app/Modules', - 'namespace' => 'App\Modules', + 'layers' => {{layers}}, - // Specify which ddd:* objects belong in the application layer - 'objects' => [ - 'controller', - 'request', - 'middleware', - ], - ], /* |-------------------------------------------------------------------------- @@ -61,38 +65,7 @@ return [ | Domain\Invoicing\Actions\* | */ - 'namespaces' => [ - 'model' => {{namespaces.model}}, - 'data_transfer_object' => {{namespaces.data_transfer_object}}, - 'view_model' => {{namespaces.view_model}}, - 'value_object' => {{namespaces.value_object}}, - 'action' => {{namespaces.action}}, - 'cast' => 'Casts', - 'class' => '', - 'channel' => 'Channels', - 'command' => 'Commands', - 'controller' => 'Controllers', - 'enum' => 'Enums', - 'event' => 'Events', - 'exception' => 'Exceptions', - 'factory' => 'Database\Factories', - 'interface' => '', - 'job' => 'Jobs', - 'listener' => 'Listeners', - 'mail' => 'Mail', - 'middleware' => 'Middleware', - 'migration' => 'Database\Migrations', - 'notification' => 'Notifications', - 'observer' => 'Observers', - 'policy' => 'Policies', - 'provider' => 'Providers', - 'resource' => 'Resources', - 'request' => 'Requests', - 'rule' => 'Rules', - 'scope' => 'Scopes', - 'seeder' => 'Database\Seeders', - 'trait' => '', - ], + 'namespaces' => {{namespaces}}, /* |-------------------------------------------------------------------------- @@ -152,13 +125,7 @@ return [ | should be auto-discovered and registered. | */ - 'autoload' => [ - 'providers' => true, - 'commands' => true, - 'policies' => true, - 'factories' => true, - 'migrations' => true, - ], + 'autoload' => {{autoload}}, /* |-------------------------------------------------------------------------- @@ -175,10 +142,7 @@ return [ | the AppServiceProvider's boot method. | */ - 'autoload_ignore' => [ - 'Tests', - 'Database/Migrations', - ], + 'autoload_ignore' => {{autoload_ignore}}, /* |-------------------------------------------------------------------------- @@ -189,5 +153,5 @@ return [ | autoloading. | */ - 'cache_directory' => 'bootstrap/cache/ddd', + 'cache_directory' => {{cache_directory}}, ]; diff --git a/src/Commands/ConfigCommand.php b/src/Commands/ConfigCommand.php index 0ddda01..8cc4d52 100644 --- a/src/Commands/ConfigCommand.php +++ b/src/Commands/ConfigCommand.php @@ -3,8 +3,6 @@ namespace Lunarstorm\LaravelDDD\Commands; use Illuminate\Console\Command; -use Illuminate\Support\Collection; -use Illuminate\Support\Composer; use Illuminate\Support\Facades\Config; use Illuminate\Support\Str; use Lunarstorm\LaravelDDD\ComposerManager; @@ -13,6 +11,7 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; +use function Laravel\Prompts\confirm; use function Laravel\Prompts\form; use function Laravel\Prompts\info; use function Laravel\Prompts\multiselect; @@ -54,6 +53,7 @@ public function handle(): int return match ($action) { 'wizard' => $this->wizard(), + 'update' => $this->update(), 'detect' => $this->detect(), 'composer' => $this->syncComposer(), 'layers' => $this->layers(), @@ -65,6 +65,7 @@ protected function home(): int { $action = select('Laravel-DDD Config Utility', [ 'wizard' => 'Run the configuration wizard', + 'update' => 'Update and merge ddd.php with latest package version', 'detect' => 'Detect domain namespace from composer.json', 'composer' => 'Sync composer.json from ddd.php', 'exit' => 'Exit', @@ -72,6 +73,7 @@ protected function home(): int return match ($action) { 'wizard' => $this->wizard(), + 'update' => $this->update(), 'detect' => $this->detect(), 'composer' => $this->syncComposer(), 'exit' => $this->exit(), @@ -114,18 +116,16 @@ protected function wizard(): int $applicationLayer = $possibleApplicationLayers->first(); $detected = collect([ - 'domain_namespace' => $domainLayer?->namespace, 'domain_path' => $domainLayer?->path, + 'domain_namespace' => $domainLayer?->namespace, 'application' => [ - 'namespace' => $applicationLayer?->namespace, 'path' => $applicationLayer?->path, + 'namespace' => $applicationLayer?->namespace, ], ]); $config = $detected->merge(Config::get('ddd')); - // dd($config); - info('Detected DDD configuration:'); table( @@ -185,8 +185,6 @@ protected function wizard(): int ], ]; - // dd($choices['application_namespace']); - $form = form() ->add( function ($responses) use ($choices, $detected, $config) { @@ -218,13 +216,14 @@ function ($responses) use ($choices) { label: 'Path to Application Layer', options: $choices['application_path'], hint: "For objects that don't belong in the domain layer (controllers, form requests, etc.)", - placeholder: 'Leave blank to skip and configure later', + placeholder: 'Leave blank to skip and use defaults', scroll: 10, ); }, name: 'application_path' ) - ->add( + ->addIf( + fn ($responses) => filled($responses['application_path']), function ($responses) use ($choices, $laravelAppLayer) { $applicationPath = $responses['application_path']; $laravelAppPath = $laravelAppLayer->path; @@ -240,6 +239,7 @@ function ($responses) use ($choices, $laravelAppLayer) { options: $choices['application_namespace'], default: $namespace, hint: 'The root application namespace.', + placeholder: 'Leave blank to use defaults', ); }, name: 'application_namespace' @@ -247,7 +247,7 @@ function ($responses) use ($choices, $laravelAppLayer) { ->add( function ($responses) use ($choices) { return multiselect( - label: 'Additional Layers (Optional)', + label: 'Custom Layers (Optional)', options: $choices['layers'], hint: 'Layers can be customized in the ddd.php config file at any time.', ); @@ -257,7 +257,11 @@ function ($responses) use ($choices) { $responses = $form->submit(); - // dd($responses); + $this->info('Building configuration...'); + + DDD::config()->fill($responses)->save(); + + $this->info('Configuration updated: '.config_path('ddd.php')); return self::SUCCESS; } @@ -270,8 +274,8 @@ protected function detect(): int foreach ($search as $namespace) { if ($path = $this->composer->getAutoloadPath($namespace)) { - $detected['domain_namespace'] = $namespace; $detected['domain_path'] = $path; + $detected['domain_namespace'] = $namespace; break; } } @@ -285,17 +289,33 @@ protected function detect(): int ->all() ); + if (confirm('Update configuration with these values?', true)) { + DDD::config()->fill($detected)->save(); + + $this->info('Configuration updated: '.config_path('ddd.php')); + } + return self::SUCCESS; } - protected function applyConfig(Collection $config) + protected function update(): int { - // $this->composer->update([ - // ['domain_namespace', $config['domain_namespace']], - // ['domain_path', $config['domain_path']], - // ['application.namespace', $config['application']['namespace']], - // ['application.path', $config['application']['path']], - // ]); + $config = DDD::config(); + + $confirmed = confirm('Are you sure you want to update ddd.php and merge with latest copy from the package?'); + + if (! $confirmed) { + $this->info('Configuration update aborted.'); + + return self::SUCCESS; + } + + $this->info('Merging ddd.php...'); + + $config->syncWithLatest()->save(); + + $this->info('Configuration updated: '.config_path('ddd.php')); + $this->warn('Note: Some values may require manual adjustment.'); return self::SUCCESS; } diff --git a/src/Commands/DomainListCommand.php b/src/Commands/DomainListCommand.php index 3b6adb6..1014b8a 100644 --- a/src/Commands/DomainListCommand.php +++ b/src/Commands/DomainListCommand.php @@ -7,6 +7,8 @@ use Lunarstorm\LaravelDDD\Support\DomainResolver; use Lunarstorm\LaravelDDD\Support\Path; +use function Laravel\Prompts\table; + class DomainListCommand extends Command { protected $name = 'ddd:list'; @@ -29,7 +31,7 @@ public function handle() }) ->toArray(); - $this->table($headings, $table); + table($headings, $table); $countDomains = count($table); diff --git a/src/Commands/InstallCommand.php b/src/Commands/InstallCommand.php index e37d186..0594133 100644 --- a/src/Commands/InstallCommand.php +++ b/src/Commands/InstallCommand.php @@ -3,65 +3,67 @@ namespace Lunarstorm\LaravelDDD\Commands; use Illuminate\Console\Command; +use Illuminate\Foundation\Console\InteractsWithComposerPackages; use Lunarstorm\LaravelDDD\Support\DomainResolver; use Symfony\Component\Process\Process; +use function Laravel\Prompts\confirm; + class InstallCommand extends Command { + use InteractsWithComposerPackages; + public $signature = 'ddd:install {--composer=global : Absolute path to the Composer binary which should be used}'; protected $description = 'Install and initialize Laravel-DDD'; public function handle(): int { - $this->comment('Publishing config...'); - $this->call('vendor:publish', [ - '--tag' => 'ddd-config', - ]); + $this->call('ddd:publish', ['--config' => true]); - $this->comment('Ensuring domain path is registered in composer.json...'); - $this->registerDomainAutoload(); + $this->comment('Updating composer.json...'); + $this->callSilently('ddd:config', ['action' => 'composer']); - if ($this->confirm('Would you like to publish stubs?')) { + if (confirm('Would you like to publish stubs now?', default: false, hint: 'You may do this at any time via ddd:stub')) { $this->call('ddd:stub'); } return self::SUCCESS; } - public function registerDomainAutoload() - { - $domainPath = DomainResolver::domainPath(); + // public function registerDomainAutoload() + // { + // $domainPath = DomainResolver::domainPath(); - $domainRootNamespace = str(DomainResolver::domainRootNamespace()) - ->rtrim('/\\') - ->toString(); + // $domainRootNamespace = str(DomainResolver::domainRootNamespace()) + // ->rtrim('/\\') + // ->toString(); - $this->comment("Registering domain path `{$domainPath}` in composer.json..."); + // $this->comment("Registering domain path `{$domainPath}` in composer.json..."); - $composerFile = base_path('composer.json'); - $data = json_decode(file_get_contents($composerFile), true); - data_fill($data, ['autoload', 'psr-4', $domainRootNamespace.'\\'], $domainPath); + // $composerFile = base_path('composer.json'); + // $data = json_decode(file_get_contents($composerFile), true); + // data_fill($data, ['autoload', 'psr-4', $domainRootNamespace . '\\'], $domainPath); - file_put_contents($composerFile, json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); + // file_put_contents($composerFile, json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); - $this->composerReload(); - } + // $this->composerReload(); + // } - protected function composerReload() - { - $composer = $this->option('composer'); + // protected function composerReload() + // { + // $composer = $this->option('composer'); - if ($composer !== 'global') { - $command = ['php', $composer, 'dump-autoload']; - } else { - $command = ['composer', 'dump-autoload']; - } + // if ($composer !== 'global') { + // $command = ['php', $composer, 'dump-autoload']; + // } else { + // $command = ['composer', 'dump-autoload']; + // } - (new Process($command, base_path(), ['COMPOSER_MEMORY_LIMIT' => '-1'])) - ->setTimeout(null) - ->run(function ($type, $output) { - $this->output->write($output); - }); - } + // (new Process($command, base_path(), ['COMPOSER_MEMORY_LIMIT' => '-1'])) + // ->setTimeout(null) + // ->run(function ($type, $output) { + // $this->output->write($output); + // }); + // } } diff --git a/src/Commands/UpgradeCommand.php b/src/Commands/UpgradeCommand.php index e696605..ea0c3c4 100644 --- a/src/Commands/UpgradeCommand.php +++ b/src/Commands/UpgradeCommand.php @@ -4,6 +4,7 @@ use Illuminate\Console\Command; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Config; class UpgradeCommand extends Command { @@ -19,36 +20,73 @@ public function handle() return; } - $replacements = [ + $legacyMapping = [ 'domain_path' => 'paths.domain', 'domain_namespace' => 'domain_namespace', - 'namespaces.model' => 'namespaces.models', - 'namespaces.data_transfer_object' => 'namespaces.data_transfer_objects', - 'namespaces.view_model' => 'namespaces.view_models', - 'namespaces.value_object' => 'namespaces.value_objects', - 'namespaces.action' => 'namespaces.actions', + 'application' => null, + 'layers' => null, + 'namespaces' => [ + 'model' => 'namespaces.models', + 'data_transfer_object' => 'namespaces.data_transfer_objects', + 'view_model' => 'namespaces.view_models', + 'value_object' => 'namespaces.value_objects', + 'action' => 'namespaces.actions', + ], 'base_model' => 'base_model', 'base_dto' => 'base_dto', 'base_view_model' => 'base_view_model', 'base_action' => 'base_action', + 'autoload' => null, + 'autoload_ignore' => null, + 'cache_directory' => null, ]; + $factoryConfig = require __DIR__.'/../../config/ddd.php'; $oldConfig = require config_path('ddd.php'); $oldConfig = Arr::dot($oldConfig); - // Grab a flesh copy of the new config - $newConfigContent = file_get_contents(__DIR__.'/../../config/ddd.php.stub'); + $replacements = []; + + $map = Arr::dot($legacyMapping); - foreach ($replacements as $dotPath => $legacyKey) { + foreach ($map as $dotPath => $legacyKey) { $value = match (true) { array_key_exists($dotPath, $oldConfig) => $oldConfig[$dotPath], array_key_exists($legacyKey, $oldConfig) => $oldConfig[$legacyKey], default => config("ddd.{$dotPath}"), }; + $replacements[$dotPath] = $value ?? data_get($factoryConfig, $dotPath); + } + + $replacements = Arr::undot($replacements); + + $freshConfig = $factoryConfig; + + // Grab a fresh copy of the new config + $newConfigContent = file_get_contents(__DIR__.'/../../config/ddd.php.stub'); + + foreach ($freshConfig as $key => $value) { + $resolved = null; + + if (is_array($value)) { + $resolved = [ + ...$value, + ...data_get($replacements, $key, []), + ]; + + if (array_is_list($resolved)) { + $resolved = array_unique($resolved); + } + } else { + $resolved = data_get($replacements, $key, $value); + } + + $freshConfig[$key] = $resolved; + $newConfigContent = str_replace( - '{{'.$dotPath.'}}', - var_export($value, true), + '{{'.$key.'}}', + var_export($resolved, true), $newConfigContent ); } diff --git a/src/ComposerManager.php b/src/ComposerManager.php index df30f14..52d9005 100755 --- a/src/ComposerManager.php +++ b/src/ComposerManager.php @@ -139,6 +139,13 @@ public function getAutoloadPath($namespace) return $this->get(['autoload', 'psr-4', $namespace]); } + public function unsetPsr4Autoload($namespace) + { + $namespace = Str::finish($namespace, '\\'); + + return $this->forget(['autoload', 'psr-4', $namespace]); + } + public function reload() { $this->output?->writeLn('Reloading composer (dump-autoload)...'); diff --git a/src/ConfigManager.php b/src/ConfigManager.php new file mode 100755 index 0000000..59591cd --- /dev/null +++ b/src/ConfigManager.php @@ -0,0 +1,148 @@ +packageConfig = require DDD::packagePath('config/ddd.php'); + + $this->config = file_exists($configPath) ? require ($configPath) : $this->packageConfig; + + $this->stub = file_get_contents(DDD::packagePath('config/ddd.php.stub')); + } + + protected function mergeArray($path, $array) + { + $path = Arr::wrap($path); + + $merged = []; + + foreach ($array as $key => $value) { + $merged[$key] = is_array($value) + ? $this->mergeArray([...$path, $key], $value, [...$path, $key]) + : $this->resolve([...$path, $key], $value); + } + + if (array_is_list($merged)) { + $merged = array_unique($merged); + } + + return $merged; + } + + public function resolve($path, $value) + { + $path = Arr::wrap($path); + + return data_get($this->config, $path, $value); + } + + public function syncWithLatest() + { + $fresh = []; + + foreach ($this->packageConfig as $key => $value) { + $resolved = is_array($value) + ? $this->mergeArray($key, $value) + : $this->resolve($key, $value); + + $fresh[$key] = $resolved; + } + + $this->config = $fresh; + + return $this; + } + + public function get($key = null) + { + if (is_null($key)) { + return $this->config; + } + + return data_get($this->config, $key); + } + + public function set($key, $value) + { + data_set($this->config, $key, $value); + + return $this; + } + + public function fill($values) + { + foreach ($values as $key => $value) { + $this->set($key, $value); + } + + return $this; + } + + public function save() + { + $content = $this->stub; + + // We will temporary substitute namespace slashes + // with a placeholder to avoid double exporter + // escaping them as double backslashes. + $keysWithNamespaces = [ + 'domain_namespace', + 'application.namespace', + 'layers', + 'namespaces', + 'base_model', + 'base_dto', + 'base_view_model', + 'base_action', + ]; + + foreach ($keysWithNamespaces as $key) { + $value = $this->get($key); + + if (is_string($value)) { + $value = str_replace('\\', '[[BACKSLASH]]', $value); + } + + if (is_array($value)) { + $array = $value; + foreach ($array as $k => $v) { + $array[$k] = str_replace('\\', '[[BACKSLASH]]', $v); + } + $value = $array; + } + + $this->set($key, $value); + } + + foreach ($this->config as $key => $value) { + $content = str_replace( + '{{'.$key.'}}', + VarExporter::export($value), + $content + ); + } + + // Restore namespace slashes + $content = str_replace('[[BACKSLASH]]', '\\', $content); + + file_put_contents($this->configPath, $content); + + Process::run("./vendor/bin/pint {$this->configPath}"); + + return $this; + } +} diff --git a/src/DomainManager.php b/src/DomainManager.php index 728867a..eff013d 100755 --- a/src/DomainManager.php +++ b/src/DomainManager.php @@ -45,6 +45,11 @@ public function __construct() $this->stubs = new StubManager; } + public function config(): ConfigManager + { + return app(ConfigManager::class); + } + public function composer(): ComposerManager { return app(ComposerManager::class); diff --git a/src/Facades/DDD.php b/src/Facades/DDD.php index 136de7e..a7c4afa 100644 --- a/src/Facades/DDD.php +++ b/src/Facades/DDD.php @@ -10,6 +10,7 @@ * @method static void filterAutoloadPathsUsing(callable $filter) * @method static void resolveObjectSchemaUsing(callable $resolver) * @method static string packagePath(string $path = '') + * @method static \Lunarstorm\LaravelDDD\ConfigManager config() * @method static \Lunarstorm\LaravelDDD\StubManager stubs() * @method static \Lunarstorm\LaravelDDD\ComposerManager composer() */ diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index 9060842..aeba7b1 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -15,6 +15,10 @@ public function configurePackage(Package $package): void return new DomainManager; }); + $this->app->scoped(ConfigManager::class, function () { + return new ConfigManager(config_path('ddd.php')); + }); + $this->app->scoped(ComposerManager::class, function () { return ComposerManager::make(app()->basePath('composer.json')); }); diff --git a/tests/Command/ConfigTest.php b/tests/Command/ConfigTest.php new file mode 100644 index 0000000..1b4a8c0 --- /dev/null +++ b/tests/Command/ConfigTest.php @@ -0,0 +1,180 @@ +cleanSlate(); + $this->setupTestApplication(); + Artisan::call('config:clear'); +}); + +afterEach(function () { + $this->cleanSlate(); + Artisan::call('config:clear'); +}); + +it('can run the config wizard', function () { + Artisan::call('config:cache'); + + expect(config('ddd.domain_path'))->toBe('src/Domain'); + expect(config('ddd.domain_namespace'))->toBe('Domain'); + expect(config('ddd.layers'))->toBe([ + 'Infrastructure' => 'src/Infrastructure', + ]); + + $path = config_path('ddd.php'); + + $this->artisan('ddd:config') + ->expectsQuestion('Laravel-DDD Config Utility', 'wizard') + ->expectsQuestion('Domain Path', 'src/CustomDomain') + ->expectsQuestion('Domain Namespace', 'CustomDomain') + ->expectsQuestion('Path to Application Layer', null) + ->expectsQuestion('Custom Layers (Optional)', ['Support' => 'src/Support']) + ->expectsOutput('Building configuration...') + ->expectsOutput("Configuration updated: {$path}") + ->assertSuccessful() + ->execute(); + + expect(file_exists($path))->toBeTrue(); + + Artisan::call('config:cache'); + + expect(config('ddd.domain_path'))->toBe('src/CustomDomain'); + expect(config('ddd.domain_namespace'))->toBe('CustomDomain'); + expect(config('ddd.application'))->toBe([ + 'path' => 'app/Modules', + 'namespace' => 'App\Modules', + 'objects' => [ + 'controller', + 'request', + 'middleware', + ], + ]); + expect(config('ddd.layers'))->toBe([ + 'Support' => 'src/Support', + ]); +}); + +it('can update and merge ddd.php with latest package version', function () { + $path = config_path('ddd.php'); + + $originalContents = <<<'PHP' +artisan('ddd:config') + ->expectsQuestion('Laravel-DDD Config Utility', 'update') + ->expectsQuestion('Are you sure you want to update ddd.php and merge with latest copy from the package?', true) + ->expectsOutput('Merging ddd.php...') + ->expectsOutput("Configuration updated: {$path}") + ->expectsOutput('Note: Some values may require manual adjustment.') + ->assertSuccessful() + ->execute(); + + $packageConfigContents = file_get_contents(DDD::packagePath('config/ddd.php')); + + expect($updatedContents = file_get_contents($path)) + ->not->toEqual($originalContents); + + $updatedConfigArray = include $path; + $packageConfigArray = include DDD::packagePath('config/ddd.php'); + + expect($updatedConfigArray)->toHaveKeys(array_keys($packageConfigArray)); +}); + +it('can sync composer.json from ddd.php ', function () { + $configContent = <<<'PHP' + 'src/CustomDomain', + 'domain_namespace' => 'CustomDomain', + 'application' => [ + 'path' => 'src/CustomApplication', + 'namespace' => 'CustomApplication', + 'objects' => [ + 'controller', + 'request', + 'middleware', + ], + ], + 'layers' => [ + 'Infrastructure' => 'src/Infrastructure', + 'CustomLayer' => 'src/CustomLayer', + ], +]; +PHP; + + file_put_contents(config_path('ddd.php'), $configContent); + + Artisan::call('config:cache'); + + $composerContents = file_get_contents(base_path('composer.json')); + + $fragments = [ + '"CustomDomain\\\\": "src/CustomDomain"', + '"Infrastructure\\\\": "src/Infrastructure"', + '"CustomLayer\\\\": "src/CustomLayer"', + '"CustomApplication\\\\": "src/CustomApplication"', + ]; + + expect($composerContents)->not->toContain(...$fragments); + + $this->artisan('ddd:config') + ->expectsQuestion('Laravel-DDD Config Utility', 'composer') + ->expectsOutput('Syncing composer.json from ddd.php...') + ->expectsOutputToContain(...[ + 'Namespace', + 'Path', + 'Status', + + 'CustomDomain', + 'src/CustomDomain', + 'Added', + + 'CustomApplication', + 'src/CustomApplication', + 'Added', + + 'Infrastructure', + 'src/Infrastructure', + 'Added', + ]) + ->assertSuccessful() + ->execute(); + + $composerContents = file_get_contents(base_path('composer.json')); + + expect($composerContents)->toContain(...$fragments); +}); + +it('can detect domain namespace from composer.json', function () { + $sampleComposer = file_get_contents(__DIR__.'/resources/composer.sample.json'); + + file_put_contents( + app()->basePath('composer.json'), + $sampleComposer + ); + + $this->artisan('ddd:config') + ->expectsQuestion('Laravel-DDD Config Utility', 'detect') + ->expectsOutputToContain(...[ + 'Detected configuration:', + 'domain_path', + 'lib/CustomDomain', + 'domain_namespace', + 'Domain', + ]) + ->expectsQuestion('Update configuration with these values?', true) + ->expectsOutput('Configuration updated: '.config_path('ddd.php')) + ->assertSuccessful() + ->execute(); + + $configValues = DDD::config()->get(); + + expect(data_get($configValues, 'domain_path'))->toBe('lib/CustomDomain'); + expect(data_get($configValues, 'domain_namespace'))->toBe('Domain'); +}); diff --git a/tests/Command/InstallTest.php b/tests/Command/InstallTest.php index 2be26aa..c950925 100644 --- a/tests/Command/InstallTest.php +++ b/tests/Command/InstallTest.php @@ -15,11 +15,11 @@ expect(file_exists($path))->toBeFalse(); - $command = $this->artisan('ddd:install'); - $command->expectsOutput('Publishing config...'); - $command->expectsOutput('Ensuring domain path is registered in composer.json...'); - $command->expectsConfirmation('Would you like to publish stubs?', 'no'); - $command->execute(); + $command = $this->artisan('ddd:install') + ->expectsOutput('Publishing config...') + ->expectsOutput('Updating composer.json...') + ->expectsQuestion('Would you like to publish stubs now?', false) + ->execute(); expect(file_exists($path))->toBeTrue(); expect(file_get_contents($path))->toEqual(file_get_contents(__DIR__.'/../../config/ddd.php')); @@ -42,9 +42,9 @@ $before = data_get($data, ['autoload', 'psr-4', $domainRoot.'\\']); expect($before)->toBeNull(); - $command = $this->artisan('ddd:install'); - $command->expectsConfirmation('Would you like to publish stubs?', 'no'); - $command->execute(); + $command = $this->artisan('ddd:install') + ->expectsQuestion('Would you like to publish stubs now?', false) + ->execute(); $data = json_decode(file_get_contents(base_path('composer.json')), true); $after = data_get($data, ['autoload', 'psr-4', $domainRoot.'\\']); diff --git a/tests/Command/resources/composer.sample.json b/tests/Command/resources/composer.sample.json new file mode 100644 index 0000000..61f8be5 --- /dev/null +++ b/tests/Command/resources/composer.sample.json @@ -0,0 +1,26 @@ +{ + "name": "laravel/laravel", + "description": "The Laravel Framework.", + "keywords": [ + "framework", + "laravel" + ], + "license": "MIT", + "type": "project", + "autoload": { + "classmap": [ + "database", + "tests/TestCase.php" + ], + "psr-4": { + "App\\": "app/", + "Domain\\": "lib/CustomDomain" + } + }, + "extra": { + "laravel": { + "dont-discover": [] + } + }, + "minimum-stability": "dev" +} diff --git a/tests/Config/ManagerTest.php b/tests/Config/ManagerTest.php new file mode 100644 index 0000000..9368f75 --- /dev/null +++ b/tests/Config/ManagerTest.php @@ -0,0 +1,51 @@ +cleanSlate(); + + $this->latestConfig = require DDD::packagePath('config/ddd.php'); +}); + +afterEach(function () { + $this->cleanSlate(); +}); + +it('can update and merge current config file with latest copy from package', function () { + $path = __DIR__.'/resources/config.sparse.php'; + + File::copy($path, config_path('ddd.php')); + + expect(file_exists($path))->toBeTrue(); + + $originalContents = file_get_contents($path); + + expect(file_get_contents(config_path('ddd.php')))->toEqual($originalContents); + + $original = include $path; + + $config = DDD::config(); + + $config->syncWithLatest()->save(); + + $updatedContents = file_get_contents(config_path('ddd.php')); + + expect($updatedContents)->not->toEqual($originalContents); + + $updatedConfig = include config_path('ddd.php'); + + // Expect original values to be retained + foreach ($original as $key => $value) { + if (is_array($value)) { + // We won't worry about arrays for now + continue; + } + + expect($updatedConfig[$key])->toEqual($value); + } + + // Expect the updated config to have all top-level keys from the latest config + expect($updatedConfig)->toHaveKeys(array_keys($this->latestConfig)); +}); diff --git a/tests/Config/resources/config.sparse.php b/tests/Config/resources/config.sparse.php new file mode 100644 index 0000000..8c61ab9 --- /dev/null +++ b/tests/Config/resources/config.sparse.php @@ -0,0 +1,25 @@ + 'src/CustomDomainFolder', + 'domain_namespace' => 'CustomDomainNamespace', + 'application' => [ + 'objects' => [ + 'keepthis', + ], + ], + 'namespaces' => [ + 'model' => 'CustomModels', + 'data_transfer_object' => 'CustomData', + 'view_model' => 'CustomViewModels', + 'value_object' => 'CustomValueObjects', + 'action' => 'CustomActions', + ], + 'base_model' => 'Domain\Shared\Models\CustomBaseModel', + 'base_dto' => 'Spatie\LaravelData\Data', + 'base_view_model' => 'Domain\Shared\ViewModels\CustomViewModel', + 'base_action' => null, + 'autoload' => [ + 'migrations' => false, + ], +]; diff --git a/tests/Datasets/resources/config.0.10.0.php b/tests/Datasets/resources/config.0.10.0.php index 59e9940..ea52a19 100644 --- a/tests/Datasets/resources/config.0.10.0.php +++ b/tests/Datasets/resources/config.0.10.0.php @@ -2,41 +2,41 @@ return [ /* - |-------------------------------------------------------------------------- - | Domain Path - |-------------------------------------------------------------------------- - | - | The path to the domain folder relative to the application root. - | - */ + |-------------------------------------------------------------------------- + | Domain Path + |-------------------------------------------------------------------------- + | + | The path to the domain folder relative to the application root. + | + */ 'domain_path' => 'src/CustomDomainFolder', /* - |-------------------------------------------------------------------------- - | Domain Namespace - |-------------------------------------------------------------------------- - | - | The root domain namespace. - | - */ + |-------------------------------------------------------------------------- + | Domain Namespace + |-------------------------------------------------------------------------- + | + | The root domain namespace. + | + */ 'domain_namespace' => 'CustomDomainNamespace', /* - |-------------------------------------------------------------------------- - | Domain Object Namespaces - |-------------------------------------------------------------------------- - | - | This value contains the default namespaces of generated domain - | objects relative to the domain namespace of which the object - | belongs to. - | - | e.g., Domain/Invoicing/Models/* - | Domain/Invoicing/Data/* - | Domain/Invoicing/ViewModels/* - | Domain/Invoicing/ValueObjects/* - | Domain/Invoicing/Actions/* - | - */ + |-------------------------------------------------------------------------- + | Domain Object Namespaces + |-------------------------------------------------------------------------- + | + | This value contains the default namespaces of generated domain + | objects relative to the domain namespace of which the object + | belongs to. + | + | e.g., Domain/Invoicing/Models/* + | Domain/Invoicing/Data/* + | Domain/Invoicing/ViewModels/* + | Domain/Invoicing/ValueObjects/* + | Domain/Invoicing/Actions/* + | + */ 'namespaces' => [ 'models' => 'CustomModels', 'data_transfer_objects' => 'CustomData', @@ -46,51 +46,51 @@ ], /* - |-------------------------------------------------------------------------- - | Base Model - |-------------------------------------------------------------------------- - | - | The base class which generated domain models should extend. By default, - | generated domain models will extend `Domain\Shared\Models\BaseModel`, - | which will be created if it doesn't already exist. - | - */ + |-------------------------------------------------------------------------- + | Base Model + |-------------------------------------------------------------------------- + | + | The base class which generated domain models should extend. By default, + | generated domain models will extend `Domain\Shared\Models\BaseModel`, + | which will be created if it doesn't already exist. + | + */ 'base_model' => 'Domain\Shared\Models\CustomBaseModel', /* - |-------------------------------------------------------------------------- - | Base DTO - |-------------------------------------------------------------------------- - | - | The base class which generated data transfer objects should extend. By - | default, generated DTOs will extend `Spatie\LaravelData\Data` from - | Spatie's Laravel-data package, a highly recommended data object - | package to work with. - | - */ + |-------------------------------------------------------------------------- + | Base DTO + |-------------------------------------------------------------------------- + | + | The base class which generated data transfer objects should extend. By + | default, generated DTOs will extend `Spatie\LaravelData\Data` from + | Spatie's Laravel-data package, a highly recommended data object + | package to work with. + | + */ 'base_dto' => 'Spatie\LaravelData\Data', /* - |-------------------------------------------------------------------------- - | Base ViewModel - |-------------------------------------------------------------------------- - | - | The base class which generated view models should extend. By default, - | generated domain models will extend `Domain\Shared\ViewModels\BaseViewModel`, - | which will be created if it doesn't already exist. - | - */ + |-------------------------------------------------------------------------- + | Base ViewModel + |-------------------------------------------------------------------------- + | + | The base class which generated view models should extend. By default, + | generated domain models will extend `Domain\Shared\ViewModels\BaseViewModel`, + | which will be created if it doesn't already exist. + | + */ 'base_view_model' => 'Domain\Shared\ViewModels\CustomViewModel', /* - |-------------------------------------------------------------------------- - | Base Action - |-------------------------------------------------------------------------- - | - | The base class which generated action objects should extend. By default, - | generated actions are based on the `lorisleiva/laravel-actions` package - | and do not extend anything. - | - */ + |-------------------------------------------------------------------------- + | Base Action + |-------------------------------------------------------------------------- + | + | The base class which generated action objects should extend. By default, + | generated actions are based on the `lorisleiva/laravel-actions` package + | and do not extend anything. + | + */ 'base_action' => null, ]; diff --git a/tests/TestCase.php b/tests/TestCase.php index 6dc58c5..39b3e58 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -177,6 +177,7 @@ protected function setupTestApplication() File::copyDirectory(__DIR__.'/.skeleton/database', base_path('database')); File::copyDirectory(__DIR__.'/.skeleton/src/Domain', base_path('src/Domain')); File::copy(__DIR__.'/.skeleton/bootstrap/providers.php', base_path('bootstrap/providers.php')); + File::copy(__DIR__.'/.skeleton/composer.json', base_path('composer.json')); File::ensureDirectoryExists(app_path('Models')); $this->setDomainPathInComposer('Domain', 'src/Domain'); From 3651070194d0ce90e0db1ffb662799c380f3cf05 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 01:30:53 -0500 Subject: [PATCH 045/169] Fix arguments. --- src/ConfigManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ConfigManager.php b/src/ConfigManager.php index 59591cd..fd8e037 100755 --- a/src/ConfigManager.php +++ b/src/ConfigManager.php @@ -32,7 +32,7 @@ protected function mergeArray($path, $array) foreach ($array as $key => $value) { $merged[$key] = is_array($value) - ? $this->mergeArray([...$path, $key], $value, [...$path, $key]) + ? $this->mergeArray([...$path, $key], $value) : $this->resolve([...$path, $key], $value); } From bffaf3422b63d26a74a9e22c314ba21baaa38f1b Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 01:38:51 -0500 Subject: [PATCH 046/169] Update var-exporter constraint. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7bcfdf1..aaf2705 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "laravel/prompts": "^0.1.16|^0.3.1", "lorisleiva/lody": "^0.5.0", "spatie/laravel-package-tools": "^1.13.0", - "symfony/var-exporter": "^7.1" + "symfony/var-exporter": "^6|^7.1" }, "require-dev": { "larastan/larastan": "^2.0.1", From a681a4134a0605c36efc04060f800988cd412bc6 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 01:43:58 -0500 Subject: [PATCH 047/169] Remove unused code. --- src/Commands/InstallCommand.php | 41 ------------------------------- src/LaravelDDDServiceProvider.php | 6 ++--- 2 files changed, 3 insertions(+), 44 deletions(-) diff --git a/src/Commands/InstallCommand.php b/src/Commands/InstallCommand.php index 0594133..ef06bdb 100644 --- a/src/Commands/InstallCommand.php +++ b/src/Commands/InstallCommand.php @@ -3,16 +3,11 @@ namespace Lunarstorm\LaravelDDD\Commands; use Illuminate\Console\Command; -use Illuminate\Foundation\Console\InteractsWithComposerPackages; -use Lunarstorm\LaravelDDD\Support\DomainResolver; -use Symfony\Component\Process\Process; use function Laravel\Prompts\confirm; class InstallCommand extends Command { - use InteractsWithComposerPackages; - public $signature = 'ddd:install {--composer=global : Absolute path to the Composer binary which should be used}'; protected $description = 'Install and initialize Laravel-DDD'; @@ -30,40 +25,4 @@ public function handle(): int return self::SUCCESS; } - - // public function registerDomainAutoload() - // { - // $domainPath = DomainResolver::domainPath(); - - // $domainRootNamespace = str(DomainResolver::domainRootNamespace()) - // ->rtrim('/\\') - // ->toString(); - - // $this->comment("Registering domain path `{$domainPath}` in composer.json..."); - - // $composerFile = base_path('composer.json'); - // $data = json_decode(file_get_contents($composerFile), true); - // data_fill($data, ['autoload', 'psr-4', $domainRootNamespace . '\\'], $domainPath); - - // file_put_contents($composerFile, json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); - - // $this->composerReload(); - // } - - // protected function composerReload() - // { - // $composer = $this->option('composer'); - - // if ($composer !== 'global') { - // $command = ['php', $composer, 'dump-autoload']; - // } else { - // $command = ['composer', 'dump-autoload']; - // } - - // (new Process($command, base_path(), ['COMPOSER_MEMORY_LIMIT' => '-1'])) - // ->setTimeout(null) - // ->run(function ($type, $output) { - // $this->output->write($output); - // }); - // } } diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index aeba7b1..22025d0 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -102,9 +102,9 @@ protected function registerMigrations() public function packageBooted() { - $this->publishes([ - $this->package->basePath('/../stubs') => $this->app->basePath("stubs/{$this->package->shortName()}"), - ], "{$this->package->shortName()}-stubs"); + // $this->publishes([ + // $this->package->basePath('/../stubs') => $this->app->basePath("stubs/{$this->package->shortName()}"), + // ], "{$this->package->shortName()}-stubs"); if ($this->app->runningInConsole() && method_exists($this, 'optimizes')) { $this->optimizes( From be2b6d8dce2220bdc305c9aa0a0006f1c76f3b3e Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 01:47:00 -0500 Subject: [PATCH 048/169] Update expectation. --- tests/Command/ListTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Command/ListTest.php b/tests/Command/ListTest.php index 6a1443b..2174b4a 100644 --- a/tests/Command/ListTest.php +++ b/tests/Command/ListTest.php @@ -1,5 +1,6 @@ artisan('ddd:list') - ->expectsTable([ + ->expectsOutputToContain(...[ 'Domain', 'Namespace', 'Path', - ], $expectedTableContent); + ...Arr::flatten($expectedTableContent), + ]); }); From 2a3fad4a302182866d0c5ca5e617845805b715bc Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 01:48:45 -0500 Subject: [PATCH 049/169] Deprecate vendor:publish stubs --- tests/Setup/PublishTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Setup/PublishTest.php b/tests/Setup/PublishTest.php index 69858bc..eb6ac0f 100644 --- a/tests/Setup/PublishTest.php +++ b/tests/Setup/PublishTest.php @@ -34,4 +34,4 @@ expect(File::exists($dir))->toBeTrue(); expect(File::isEmptyDirectory($dir))->toBeFalse(); -}); +})->markTestSkipped('Deprecated'); From d8505157d41c53ca4161f0739459975338351cec Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 01:54:11 -0500 Subject: [PATCH 050/169] Drop Laravel10 tests for now. --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5698f95..5a4869a 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -14,7 +14,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest] php: [8.3, 8.2, 8.1] - laravel: [11.*, 10.25.*] + laravel: [11.*] stability: [prefer-lowest, prefer-stable] include: - laravel: 11.* From 17c4d6ed1a129735a7395dc48c8439aec4cdf0b1 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 01:56:10 -0500 Subject: [PATCH 051/169] Keep only laravel 11 in the matrix --- .github/workflows/run-tests.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5a4869a..8a8d82c 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,19 +13,13 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest, windows-latest] - php: [8.3, 8.2, 8.1] + php: [8.3, 8.2] laravel: [11.*] stability: [prefer-lowest, prefer-stable] include: - laravel: 11.* testbench: 9.* carbon: ^3.0 - - laravel: 10.25.* - testbench: 8.* - carbon: 2.* - exclude: - - laravel: 11.* - php: 8.1 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} From 2d544c2bc26144d9a962c84d38afe8fe0609f7be Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 02:00:36 -0500 Subject: [PATCH 052/169] Clear config cache after test. --- tests/Config/ManagerTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Config/ManagerTest.php b/tests/Config/ManagerTest.php index 9368f75..066c0ea 100644 --- a/tests/Config/ManagerTest.php +++ b/tests/Config/ManagerTest.php @@ -1,5 +1,6 @@ cleanSlate(); + Artisan::call('config:clear'); }); it('can update and merge current config file with latest copy from package', function () { From 9c5a45135d540285b2a641231db29195a768f431 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 02:02:17 -0500 Subject: [PATCH 053/169] optimize:clear before each test --- tests/Command/CacheTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Command/CacheTest.php b/tests/Command/CacheTest.php index 4881fd6..4725a6a 100644 --- a/tests/Command/CacheTest.php +++ b/tests/Command/CacheTest.php @@ -9,6 +9,8 @@ config(['cache.default' => 'file']); + $this->artisan('optimize:clear')->execute(); + DomainCache::clear(); }); From 3f34b5615a1e557712280a050015d94879fb1a1f Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 02:08:30 -0500 Subject: [PATCH 054/169] Remove composer reset in test application --- tests/TestCase.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 39b3e58..6dc58c5 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -177,7 +177,6 @@ protected function setupTestApplication() File::copyDirectory(__DIR__.'/.skeleton/database', base_path('database')); File::copyDirectory(__DIR__.'/.skeleton/src/Domain', base_path('src/Domain')); File::copy(__DIR__.'/.skeleton/bootstrap/providers.php', base_path('bootstrap/providers.php')); - File::copy(__DIR__.'/.skeleton/composer.json', base_path('composer.json')); File::ensureDirectoryExists(app_path('Models')); $this->setDomainPathInComposer('Domain', 'src/Domain'); From 7c335d795887a4c61d417862cc8faabaf34ff854 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 10:15:38 -0500 Subject: [PATCH 055/169] Fix a few issues with test environment clean slate --- composer.json | 5 ++ src/LaravelDDDServiceProvider.php | 7 +++ tests/.skeleton/composer.json | 5 ++ tests/Autoload/ProviderTest.php | 16 ++++-- tests/Command/ConfigTest.php | 4 +- tests/Command/InstallTest.php | 4 ++ tests/Command/ListTest.php | 2 + .../{CacheTest.php => OptimizeTest.php} | 10 ++-- .../Support/ResolveObjectSchemaUsingTest.php | 6 +- tests/TestCase.php | 56 +++++++------------ 10 files changed, 65 insertions(+), 50 deletions(-) rename tests/Command/{CacheTest.php => OptimizeTest.php} (93%) diff --git a/composer.json b/composer.json index aaf2705..d19e021 100644 --- a/composer.json +++ b/composer.json @@ -37,6 +37,10 @@ "phpstan/phpstan-phpunit": "^1.0", "spatie/laravel-data": "^4.10" }, + "suggest": { + "spatie/laravel-data": "Recommended for Data Transfer Objects.", + "lorisleiva/laravel-actions": "Recommended for Actions." + }, "autoload": { "psr-4": { "Lunarstorm\\LaravelDDD\\": "src", @@ -57,6 +61,7 @@ "analyse": "vendor/bin/phpstan analyse", "test": "@composer dump-autoload && vendor/bin/pest", "test-coverage": "@composer dump-autoload && vendor/bin/pest --coverage", + "purge-skeleton": "vendor/bin/testbench package:purge-skeleton", "format": "vendor/bin/pint", "lint": "vendor/bin/pint" }, diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index 22025d0..b94b48f 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -2,6 +2,7 @@ namespace Lunarstorm\LaravelDDD; +use Illuminate\Database\Migrations\MigrationCreator; use Lunarstorm\LaravelDDD\Support\DomainAutoloader; use Lunarstorm\LaravelDDD\Support\DomainMigration; use Spatie\LaravelPackageTools\Package; @@ -97,6 +98,12 @@ protected function registerMigrations() return new Commands\Migration\DomainMigrateMakeCommand($creator, $composer); }); + // $this->app->when(MigrationCreator::class) + // ->needs('$customStubPath') + // ->give(function ($app) { + // return $app->basePath('stubs'); + // }); + $this->loadMigrationsFrom(DomainMigration::paths()); } diff --git a/tests/.skeleton/composer.json b/tests/.skeleton/composer.json index 37eb342..7b6b9bb 100644 --- a/tests/.skeleton/composer.json +++ b/tests/.skeleton/composer.json @@ -17,6 +17,11 @@ "Support\\": "src/Support" } }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, "extra": { "laravel": { "dont-discover": [] diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 6316e54..2454ddc 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -17,9 +17,11 @@ 'ddd.autoload.providers' => false, ]); - $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); - }); + // $this->afterApplicationCreated(function () { + // (new DomainAutoloader)->autoload(); + // }); + + (new DomainAutoloader)->autoload(); }); it('does not register the provider', function () { @@ -33,9 +35,11 @@ 'ddd.autoload.providers' => true, ]); - $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); - }); + // $this->afterApplicationCreated(function () { + // (new DomainAutoloader)->autoload(); + // }); + + (new DomainAutoloader)->autoload(); }); it('registers the provider', function () { diff --git a/tests/Command/ConfigTest.php b/tests/Command/ConfigTest.php index 1b4a8c0..032bbf2 100644 --- a/tests/Command/ConfigTest.php +++ b/tests/Command/ConfigTest.php @@ -7,11 +7,13 @@ $this->cleanSlate(); $this->setupTestApplication(); Artisan::call('config:clear'); -}); + $this->composerReload(); +})->skip(); afterEach(function () { $this->cleanSlate(); Artisan::call('config:clear'); + $this->composerReload(); }); it('can run the config wizard', function () { diff --git a/tests/Command/InstallTest.php b/tests/Command/InstallTest.php index c950925..7939bff 100644 --- a/tests/Command/InstallTest.php +++ b/tests/Command/InstallTest.php @@ -6,6 +6,10 @@ $this->setupTestApplication(); }); +afterEach(function () { + $this->setupTestApplication(); +}); + it('publishes config', function () { $path = config_path('ddd.php'); diff --git a/tests/Command/ListTest.php b/tests/Command/ListTest.php index 2174b4a..48e067c 100644 --- a/tests/Command/ListTest.php +++ b/tests/Command/ListTest.php @@ -4,6 +4,8 @@ use Lunarstorm\LaravelDDD\Support\Path; beforeEach(function () { + $this->setupTestApplication(); + $this->artisan('ddd:model', [ 'name' => 'Invoice', '--domain' => 'Invoicing', diff --git a/tests/Command/CacheTest.php b/tests/Command/OptimizeTest.php similarity index 93% rename from tests/Command/CacheTest.php rename to tests/Command/OptimizeTest.php index 4725a6a..4626d4e 100644 --- a/tests/Command/CacheTest.php +++ b/tests/Command/OptimizeTest.php @@ -20,13 +20,13 @@ DomainCache::clear(); }); -it('can cache discovered domain providers, commands, migrations', function () { +it('can optimize discovered domain providers, commands, migrations', function () { expect(DomainCache::get('domain-providers'))->toBeNull(); expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); $this - ->artisan('ddd:cache') + ->artisan('ddd:optimize') ->expectsOutputToContain('Caching DDD providers, commands, migration paths.') ->expectsOutputToContain('domain providers') ->expectsOutputToContain('domain commands') @@ -66,7 +66,7 @@ expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); - $this->artisan('ddd:cache')->execute(); + $this->artisan('ddd:optimize')->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); expect(DomainCache::get('domain-commands'))->not->toBeNull(); @@ -88,7 +88,7 @@ }); describe('laravel optimize', function () { - test('optimize will include ddd:cache', function () { + test('optimize will include ddd:optimize', function () { config(['cache.default' => 'file']); expect(DomainCache::get('domain-providers'))->toBeNull(); @@ -105,7 +105,7 @@ test('optimize:clear will clear ddd cache', function () { config(['cache.default' => 'file']); - $this->artisan('ddd:cache')->execute(); + $this->artisan('ddd:optimize')->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); expect(DomainCache::get('domain-commands'))->not->toBeNull(); diff --git a/tests/Support/ResolveObjectSchemaUsingTest.php b/tests/Support/ResolveObjectSchemaUsingTest.php index ec5ddaf..d465166 100644 --- a/tests/Support/ResolveObjectSchemaUsingTest.php +++ b/tests/Support/ResolveObjectSchemaUsingTest.php @@ -7,13 +7,13 @@ use Lunarstorm\LaravelDDD\ValueObjects\ObjectSchema; beforeEach(function () { + $this->setupTestApplication(); + Config::set('ddd.domain_path', 'src/Domain'); Config::set('ddd.domain_namespace', 'Domain'); - - $this->setupTestApplication(); }); -it('can register a custom namespace resolver', function () { +it('can register a custom object schema resolver', function () { Config::set('ddd.application', [ 'path' => 'src/App', 'namespace' => 'App', diff --git a/tests/TestCase.php b/tests/TestCase.php index 6dc58c5..40e83d3 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -19,19 +19,6 @@ protected function setUp(): void $this->afterApplicationCreated(function () { $this->cleanSlate(); - // $this->updateComposer( - // set: [ - // [['autoload', 'psr-4', 'App\\'], 'vendor/orchestra/testbench-core/laravel/app'], - // [['autoload', 'psr-4', 'Database\\Factories\\'], 'vendor/orchestra/testbench-core/laravel/database/factories'], - // [['autoload', 'psr-4', 'Database\\Seeders\\'], 'vendor/orchestra/testbench-core/laravel/database/seeders'], - // [['autoload', 'psr-4', 'Domain\\'], 'vendor/orchestra/testbench-core/laravel/src/Domain'], - // ], - // forget: [ - // ['autoload', 'psr-4', 'Domains\\'], - // ['autoload', 'psr-4', 'Domain\\'], - // ] - // ); - Factory::guessFactoryNamesUsing( fn (string $modelName) => 'Lunarstorm\\LaravelDDD\\Database\\Factories\\'.class_basename($modelName).'Factory' ); @@ -56,16 +43,6 @@ protected function defineEnvironment($app) $config->set($key, $value); } }); - - // $this->updateComposer( - // set: [ - // [['autoload', 'psr-4', 'App\\'], 'vendor/orchestra/testbench-core/laravel/app'], - // ], - // forget: [ - // ['autoload', 'psr-4', 'Domains\\'], - // ['autoload', 'psr-4', 'Domain\\'], - // ] - // ); } protected function getComposerFileContents() @@ -142,25 +119,25 @@ protected function composerReload() protected function cleanSlate() { - File::copy(__DIR__.'/.skeleton/composer.json', base_path('composer.json')); - File::delete(base_path('config/ddd.php')); - File::cleanDirectory(app_path()); + // File::cleanDirectory(app_path()); + File::cleanDirectory(app_path('Models')); File::cleanDirectory(base_path('database/factories')); + File::cleanDirectory(base_path('src')); File::deleteDirectory(resource_path('stubs/ddd')); File::deleteDirectory(base_path('stubs')); File::deleteDirectory(base_path('Custom')); - File::cleanDirectory(base_path('src')); - File::deleteDirectory(base_path('src/Domain')); - File::deleteDirectory(base_path('src/Domains')); - File::deleteDirectory(base_path('src/App')); + // File::deleteDirectory(base_path('src/Domain')); + // File::deleteDirectory(base_path('src/Domains')); + // File::deleteDirectory(base_path('src/App')); + File::deleteDirectory(app_path('Policies')); File::deleteDirectory(app_path('Modules')); - File::deleteDirectory(app_path('Models')); - File::deleteDirectory(base_path('bootstrap/cache/ddd')); + File::copy(__DIR__.'/.skeleton/composer.json', base_path('composer.json')); + return $this; } @@ -173,11 +150,20 @@ protected function cleanStubs() protected function setupTestApplication() { - File::copyDirectory(__DIR__.'/.skeleton/app', app_path()); + $this->cleanSlate(); + + File::ensureDirectoryExists(app_path()); + File::ensureDirectoryExists(app_path('Models')); + + $skeletonAppFolders = glob(__DIR__.'/.skeleton/app/*', GLOB_ONLYDIR); + + foreach ($skeletonAppFolders as $folder) { + File::copyDirectory($folder, app_path(basename($folder))); + } + File::copyDirectory(__DIR__.'/.skeleton/database', base_path('database')); - File::copyDirectory(__DIR__.'/.skeleton/src/Domain', base_path('src/Domain')); + File::copyDirectory(__DIR__.'/.skeleton/src', base_path('src')); File::copy(__DIR__.'/.skeleton/bootstrap/providers.php', base_path('bootstrap/providers.php')); - File::ensureDirectoryExists(app_path('Models')); $this->setDomainPathInComposer('Domain', 'src/Domain'); From 29ed254adfd9753c764d89e1de0bd8dd58e94a82 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 10:19:35 -0500 Subject: [PATCH 056/169] Try adding Laravel 10 tests again. --- .github/workflows/run-tests.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 8a8d82c..5698f95 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,13 +13,19 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest, windows-latest] - php: [8.3, 8.2] - laravel: [11.*] + php: [8.3, 8.2, 8.1] + laravel: [11.*, 10.25.*] stability: [prefer-lowest, prefer-stable] include: - laravel: 11.* testbench: 9.* carbon: ^3.0 + - laravel: 10.25.* + testbench: 8.* + carbon: 2.* + exclude: + - laravel: 11.* + php: 8.1 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} From 7b2046413843956a5007f31355672fb154df4f5c Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 10:54:31 -0500 Subject: [PATCH 057/169] Use pest --ci flag --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5698f95..e904482 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -54,4 +54,4 @@ jobs: run: composer show -D - name: Execute tests - run: vendor/bin/pest + run: vendor/bin/pest --ci From e4e44ced2c6d41c9a45720c4993a7883d2452e75 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 10:55:58 -0500 Subject: [PATCH 058/169] Use --compact test output. --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e904482..3134fe4 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -54,4 +54,4 @@ jobs: run: composer show -D - name: Execute tests - run: vendor/bin/pest --ci + run: vendor/bin/pest --ci --compact From bb20d63179ce373d6dfa8263cc236d5abf168b71 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 11:19:14 -0500 Subject: [PATCH 059/169] Simplify config --- README.md | 62 ++++++------------- config/ddd.php | 38 ++++-------- config/ddd.php.stub | 40 ++++-------- src/Commands/ConfigCommand.php | 12 ++-- src/ConfigManager.php | 12 +++- src/Support/DomainResolver.php | 8 +-- tests/Command/ConfigTest.php | 14 ++--- tests/Config/resources/config.sparse.php | 6 +- tests/Generator/ControllerMakeTest.php | 13 ++-- tests/Generator/CustomLayerTest.php | 8 +-- tests/Generator/RequestMakeTest.php | 13 ++-- tests/Support/DomainTest.php | 8 +-- .../Support/ResolveObjectSchemaUsingTest.php | 6 +- 13 files changed, 92 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index 753d582..4b97df3 100644 --- a/README.md +++ b/README.md @@ -114,14 +114,12 @@ php artisan ddd:clear Some objects interact with the domain layer, but are not part of the domain layer themselves. By default, these include: `controller`, `request`, `middleware`. You may customize the path, namespace, and which `ddd:*` objects belong in the application layer. ```php // In config/ddd.php -'application' => [ - 'path' => 'app/Modules', - 'namespace' => 'App\Modules', - 'objects' => [ - 'controller', - 'request', - 'middleware', - ], +'application_path' => 'app/Modules', +'application_namespace' => 'App\Modules', +'application_objects' => [ + 'controller', + 'request', + 'middleware', ], ``` The configuration above will result in the following: @@ -150,7 +148,6 @@ Often times, additional top-level namespaces are needed to hold shared component // In config/ddd.php 'layers' => [ 'Infrastructure' => 'src/Infrastructure', - // 'Support' => 'src/Support', ], ``` The configuration above will result in the following: @@ -359,22 +356,13 @@ return [ /* |-------------------------------------------------------------------------- - | Domain Path + | Domain Layer |-------------------------------------------------------------------------- | - | The path to the domain folder relative to the application root. + | The path and namespace of the domain layer. | */ 'domain_path' => 'src/Domain', - - /* - |-------------------------------------------------------------------------- - | Domain Namespace - |-------------------------------------------------------------------------- - | - | The root domain namespace. - | - */ 'domain_namespace' => 'Domain', /* @@ -382,17 +370,16 @@ return [ | Application Layer |-------------------------------------------------------------------------- | - | Configure domain objects in the application layer. + | The path and namespace of the application layer, and the objects + | that should be recognized as part of the application layer. | */ - 'application' => [ - 'path' => 'app/Modules', - 'namespace' => 'App\Modules', - 'objects' => [ - 'controller', - 'request', - 'middleware', - ], + 'application_path' => 'app/Modules', + 'application_namespace' => 'App\Modules', + 'application_objects' => [ + 'controller', + 'request', + 'middleware', ], /* @@ -400,35 +387,26 @@ return [ | Custom Layers |-------------------------------------------------------------------------- | - | Mapping of additional top-level namespaces and paths that should - | be recognized as layers when generating ddd:* objects. + | Additional top-level namespaces and paths that should be recognized as + | layers when generating ddd:* objects. | | e.g., 'Infrastructure' => 'src/Infrastructure', | - | When using ddd:* generators, specifying a domain matching a key in - | this array will generate objects in that corresponding layer. - | */ 'layers' => [ - // 'Infrastructure' => 'src/Infrastructure', + 'Infrastructure' => 'src/Infrastructure', // 'Integrations' => 'src/Integrations', // 'Support' => 'src/Support', ], /* |-------------------------------------------------------------------------- - | Domain Object Namespaces + | Object Namespaces |-------------------------------------------------------------------------- | | This value contains the default namespaces of ddd:* generated | objects relative to the layer of which the object belongs to. | - | e.g., Domain\Invoicing\Models\* - | Domain\Invoicing\Data\* - | Domain\Invoicing\ViewModels\* - | Domain\Invoicing\ValueObjects\* - | Domain\Invoicing\Actions\* - | */ 'namespaces' => [ 'model' => 'Models', diff --git a/config/ddd.php b/config/ddd.php index 1a051af..20321d8 100644 --- a/config/ddd.php +++ b/config/ddd.php @@ -4,22 +4,13 @@ /* |-------------------------------------------------------------------------- - | Domain Path + | Domain Layer |-------------------------------------------------------------------------- | - | The path to the domain folder relative to the application root. + | The path and namespace of the domain layer. | */ 'domain_path' => 'src/Domain', - - /* - |-------------------------------------------------------------------------- - | Domain Namespace - |-------------------------------------------------------------------------- - | - | The root domain namespace. - | - */ 'domain_namespace' => 'Domain', /* @@ -27,17 +18,16 @@ | Application Layer |-------------------------------------------------------------------------- | - | Configure domain objects in the application layer. + | The path and namespace of the application layer, and the objects + | that should be recognized as part of the application layer. | */ - 'application' => [ - 'path' => 'app/Modules', - 'namespace' => 'App\Modules', - 'objects' => [ - 'controller', - 'request', - 'middleware', - ], + 'application_path' => 'app/Modules', + 'application_namespace' => 'App\Modules', + 'application_objects' => [ + 'controller', + 'request', + 'middleware', ], /* @@ -59,18 +49,12 @@ /* |-------------------------------------------------------------------------- - | Domain Object Namespaces + | Object Namespaces |-------------------------------------------------------------------------- | | This value contains the default namespaces of ddd:* generated | objects relative to the layer of which the object belongs to. | - | e.g., Domain\Invoicing\Models\* - | Domain\Invoicing\Data\* - | Domain\Invoicing\ViewModels\* - | Domain\Invoicing\ValueObjects\* - | Domain\Invoicing\Actions\* - | */ 'namespaces' => [ 'model' => 'Models', diff --git a/config/ddd.php.stub b/config/ddd.php.stub index 41e6874..911a351 100644 --- a/config/ddd.php.stub +++ b/config/ddd.php.stub @@ -4,22 +4,13 @@ return [ /* |-------------------------------------------------------------------------- - | Domain Path + | Domain Layer |-------------------------------------------------------------------------- | - | The path to the domain folder relative to the application root. + | The path and namespace of the domain layer. | */ 'domain_path' => {{domain_path}}, - - /* - |-------------------------------------------------------------------------- - | Domain Namespace - |-------------------------------------------------------------------------- - | - | The root domain namespace. - | - */ 'domain_namespace' => {{domain_namespace}}, /* @@ -27,42 +18,35 @@ return [ | Application Layer |-------------------------------------------------------------------------- | - | Configure domain objects in the application layer. + | The path and namespace of the application layer, and the objects + | that should be recognized as part of the application layer. | */ - 'application' => {{application}}, + 'application_path' => {{application_path}}, + 'application_namespace' => {{application_namespace}}, + 'application_objects' => {{application_objects}}, /* |-------------------------------------------------------------------------- | Custom Layers |-------------------------------------------------------------------------- | - | Additional top-level namespaces and paths that should - | be recognized as layers when generating ddd:* objects. + | Additional top-level namespaces and paths that should be recognized as + | layers when generating ddd:* objects. | | e.g., 'Infrastructure' => 'src/Infrastructure', | - | When using ddd:* generators, specifying a domain matching a key in - | this array will generate objects in that corresponding layer. - | */ 'layers' => {{layers}}, /* |-------------------------------------------------------------------------- - | Domain Object Namespaces + | Object Namespaces |-------------------------------------------------------------------------- | - | This value contains the default namespaces of generated domain - | objects relative to the domain namespace of which the object - | belongs to. - | - | e.g., Domain\Invoicing\Models\* - | Domain\Invoicing\Data\* - | Domain\Invoicing\ViewModels\* - | Domain\Invoicing\ValueObjects\* - | Domain\Invoicing\Actions\* + | This value contains the default namespaces of ddd:* generated + | objects relative to the layer of which the object belongs to. | */ 'namespaces' => {{namespaces}}, diff --git a/src/Commands/ConfigCommand.php b/src/Commands/ConfigCommand.php index 8cc4d52..fdd779e 100644 --- a/src/Commands/ConfigCommand.php +++ b/src/Commands/ConfigCommand.php @@ -118,10 +118,8 @@ protected function wizard(): int $detected = collect([ 'domain_path' => $domainLayer?->path, 'domain_namespace' => $domainLayer?->namespace, - 'application' => [ - 'path' => $applicationLayer?->path, - 'namespace' => $applicationLayer?->namespace, - ], + 'application_path' => $applicationLayer?->path, + 'application_namespace' => $applicationLayer?->namespace, ]); $config = $detected->merge(Config::get('ddd')); @@ -161,7 +159,7 @@ protected function wizard(): int 'src/Application' => 'src/Application', 'Application' => 'Application', ...[ - data_get($config, 'application.path') => data_get($config, 'application.path'), + data_get($config, 'application_path') => data_get($config, 'application_path'), ], ...$possibleApplicationLayers->mapWithKeys( fn (Layer $layer) => [$layer->path => $layer->path] @@ -172,7 +170,7 @@ protected function wizard(): int 'Application' => 'Application', 'Modules' => 'Modules', ...[ - data_get($config, 'application.namespace') => data_get($config, 'application.namespace'), + data_get($config, 'application_namespace') => data_get($config, 'application_namespace'), ], ...$possibleApplicationLayers->mapWithKeys( fn (Layer $layer) => [$layer->namespace => $layer->namespace] @@ -324,7 +322,7 @@ protected function syncComposer(): int { $namespaces = [ config('ddd.domain_namespace', 'Domain') => config('ddd.domain_path', 'src/Domain'), - config('ddd.application.namespace', 'App\\Modules') => config('ddd.application.path', 'app/Modules'), + config('ddd.application_namespace', 'App\\Modules') => config('ddd.application_path', 'app/Modules'), ...collect(config('ddd.layers', [])) ->all(), ]; diff --git a/src/ConfigManager.php b/src/ConfigManager.php index fd8e037..78668cd 100755 --- a/src/ConfigManager.php +++ b/src/ConfigManager.php @@ -101,7 +101,7 @@ public function save() // escaping them as double backslashes. $keysWithNamespaces = [ 'domain_namespace', - 'application.namespace', + 'application_namespace', 'layers', 'namespaces', 'base_model', @@ -139,9 +139,15 @@ public function save() // Restore namespace slashes $content = str_replace('[[BACKSLASH]]', '\\', $content); - file_put_contents($this->configPath, $content); + // Write it to a temporary file first + $tempPath = sys_get_temp_dir().'/ddd.php'; + file_put_contents($tempPath, $content); - Process::run("./vendor/bin/pint {$this->configPath}"); + // Format it using pint + Process::run("./vendor/bin/pint {$tempPath}"); + + // Copy the temporary file to the config path + copy($tempPath, $this->configPath); return $this; } diff --git a/src/Support/DomainResolver.php b/src/Support/DomainResolver.php index 69f4892..8223ca4 100644 --- a/src/Support/DomainResolver.php +++ b/src/Support/DomainResolver.php @@ -41,7 +41,7 @@ public static function domainRootNamespace(): ?string */ public static function applicationLayerPath(): ?string { - return config('ddd.application.path'); + return config('ddd.application_path'); } /** @@ -49,7 +49,7 @@ public static function applicationLayerPath(): ?string */ public static function applicationLayerRootNamespace(): ?string { - return config('ddd.application.namespace'); + return config('ddd.application_namespace'); } /** @@ -68,7 +68,7 @@ public static function getRelativeObjectNamespace(string $type): string public static function isApplicationLayer(string $type): bool { $filter = app('ddd')->getApplicationLayerFilter() ?? function (string $type) { - $applicationObjects = config('ddd.application.objects', ['controller', 'request']); + $applicationObjects = config('ddd.application_objects', ['controller', 'request']); return in_array($type, $applicationObjects); }; @@ -106,7 +106,7 @@ public static function resolveLayer(string $domain, ?string $type = null): ?Laye return match (true) { array_key_exists($domain, $layers) - && is_string($layers[$domain]) => new Layer($domain, $layers[$domain], LayerType::Custom), + && is_string($layers[$domain]) => new Layer($domain, $layers[$domain], LayerType::Custom), default => new Layer( static::domainRootNamespace().'\\'.$domain, diff --git a/tests/Command/ConfigTest.php b/tests/Command/ConfigTest.php index 032bbf2..b2d7da0 100644 --- a/tests/Command/ConfigTest.php +++ b/tests/Command/ConfigTest.php @@ -94,14 +94,12 @@ return [ 'domain_path' => 'src/CustomDomain', 'domain_namespace' => 'CustomDomain', - 'application' => [ - 'path' => 'src/CustomApplication', - 'namespace' => 'CustomApplication', - 'objects' => [ - 'controller', - 'request', - 'middleware', - ], + 'application_path' => 'src/CustomApplication', + 'application_namespace' => 'CustomApplication', + 'application_objects' => [ + 'controller', + 'request', + 'middleware', ], 'layers' => [ 'Infrastructure' => 'src/Infrastructure', diff --git a/tests/Config/resources/config.sparse.php b/tests/Config/resources/config.sparse.php index 8c61ab9..78bca52 100644 --- a/tests/Config/resources/config.sparse.php +++ b/tests/Config/resources/config.sparse.php @@ -3,10 +3,8 @@ return [ 'domain_path' => 'src/CustomDomainFolder', 'domain_namespace' => 'CustomDomainNamespace', - 'application' => [ - 'objects' => [ - 'keepthis', - ], + 'application_objects' => [ + 'keepthis', ], 'namespaces' => [ 'model' => 'CustomModels', diff --git a/tests/Generator/ControllerMakeTest.php b/tests/Generator/ControllerMakeTest.php index e814913..8f41808 100644 --- a/tests/Generator/ControllerMakeTest.php +++ b/tests/Generator/ControllerMakeTest.php @@ -9,13 +9,12 @@ $this->cleanSlate(); $this->setupTestApplication(); - Config::set('ddd.domain_path', 'src/Domain'); - Config::set('ddd.domain_namespace', 'Domain'); - - Config::set('ddd.application', [ - 'path' => 'app/Modules', - 'namespace' => 'App\Modules', - 'objects' => ['controller', 'request'], + Config::set([ + 'ddd.domain_path' => 'src/Domain', + 'ddd.domain_namespace' => 'Domain', + 'ddd.application_path' => 'app/Modules', + 'ddd.application_namespace' => 'App\Modules', + 'ddd.application_objects' => ['controller', 'request'], ]); }); diff --git a/tests/Generator/CustomLayerTest.php b/tests/Generator/CustomLayerTest.php index a66fc4b..4eb217c 100644 --- a/tests/Generator/CustomLayerTest.php +++ b/tests/Generator/CustomLayerTest.php @@ -59,10 +59,10 @@ ]); it('ignores custom layer if object belongs in the application layer', function ($type, $objectName, $expectedNamespace, $expectedPath) { - Config::set('ddd.application', [ - 'namespace' => 'Application', - 'path' => 'src/Application', - 'objects' => [ + Config::set([ + 'ddd.application_namespace' => 'Application', + 'ddd.application_path' => 'src/Application', + 'ddd.application_objects' => [ $type, ], ]); diff --git a/tests/Generator/RequestMakeTest.php b/tests/Generator/RequestMakeTest.php index cc24949..5a584b1 100644 --- a/tests/Generator/RequestMakeTest.php +++ b/tests/Generator/RequestMakeTest.php @@ -5,13 +5,12 @@ use Lunarstorm\LaravelDDD\Tests\Fixtures\Enums\Feature; beforeEach(function () { - Config::set('ddd.domain_path', 'src/Domain'); - Config::set('ddd.domain_namespace', 'Domain'); - - Config::set('ddd.application', [ - 'path' => 'app/Modules', - 'namespace' => 'App\Modules', - 'objects' => ['controller', 'request'], + Config::set([ + 'ddd.domain_path' => 'src/Domain', + 'ddd.domain_namespace' => 'Domain', + 'ddd.application_path' => 'app/Modules', + 'ddd.application_namespace' => 'App\Modules', + 'ddd.application_objects' => ['controller', 'request'], ]); $this->setupTestApplication(); diff --git a/tests/Support/DomainTest.php b/tests/Support/DomainTest.php index e911635..a88fbcf 100644 --- a/tests/Support/DomainTest.php +++ b/tests/Support/DomainTest.php @@ -93,10 +93,10 @@ describe('application layer', function () { beforeEach(function () { - Config::set('ddd.application', [ - 'path' => 'app/Modules', - 'namespace' => 'App\Modules', - 'objects' => ['controller', 'request'], + Config::set([ + 'ddd.application_path' => 'app/Modules', + 'ddd.application_namespace' => 'App\Modules', + 'ddd.application_objects' => ['controller', 'request'], ]); }); diff --git a/tests/Support/ResolveObjectSchemaUsingTest.php b/tests/Support/ResolveObjectSchemaUsingTest.php index d465166..41bafe7 100644 --- a/tests/Support/ResolveObjectSchemaUsingTest.php +++ b/tests/Support/ResolveObjectSchemaUsingTest.php @@ -14,9 +14,9 @@ }); it('can register a custom object schema resolver', function () { - Config::set('ddd.application', [ - 'path' => 'src/App', - 'namespace' => 'App', + Config::set([ + 'ddd.application_path' => 'src/App', + 'ddd.application_namespace' => 'App', ]); DDD::resolveObjectSchemaUsing(function (string $domainName, string $nameInput, string $type, CommandContext $command): ?ObjectSchema { From 5bb5de6d760091bf3d41d0f06727ee23b90c040c Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 15:07:21 -0500 Subject: [PATCH 060/169] Update readme and fix custom resolver logic. --- README.md | 121 ++++++++++++++---- UPGRADING.md | 19 ++- .../Concerns/ResolvesDomainFromInput.php | 5 + src/Commands/DomainControllerMakeCommand.php | 5 - src/Support/GeneratorBlueprint.php | 5 + .../Support/ResolveObjectSchemaUsingTest.php | 5 +- 6 files changed, 129 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 4b97df3..e5d62b8 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/lunarstorm/laravel-ddd/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/lunarstorm/laravel-ddd/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) [![Total Downloads](https://img.shields.io/packagist/dt/lunarstorm/laravel-ddd.svg?style=flat-square)](https://packagist.org/packages/lunarstorm/laravel-ddd) -Laravel-DDD is a toolkit to support domain driven design (DDD) in Laravel applications. One of the pain points when adopting DDD is the inability to use Laravel's native `make` commands to generate domain objects since they are typically stored outside the `App\*` namespace. This package aims to fill the gaps by providing equivalent commands such as `ddd:model`, `ddd:dto`, `ddd:view-model` and many more. +Laravel-DDD is a toolkit to support domain driven design (DDD) in Laravel applications. One of the pain points when adopting DDD is the inability to use Laravel's native `make` commands to generate objects outside the `App\*` namespace. This package aims to fill the gaps by providing equivalent commands such as `ddd:model`, `ddd:dto`, `ddd:view-model` and many more. ## Installation You can install the package via composer: @@ -24,7 +24,7 @@ The following additional packages are suggested (but not required) while working - Data Transfer Objects: [spatie/laravel-data](https://github.com/spatie/laravel-data) - Actions: [lorisleiva/laravel-actions](https://github.com/lorisleiva/laravel-actions) -The default DTO and Action stubs of this package reference classes from these packages. If this doesn't apply to your application, you may [customize the stubs](#publishing-stubs-advanced) accordingly. +The default DTO and Action stubs of this package reference classes from these packages. If this doesn't apply to your application, you may [publish and customize the stubs](#customizing-stubs) accordingly. ### Deployment In production, run `ddd:optimize` during the deployment process to [optimize autoloading](#autoloading-in-production). @@ -40,7 +40,7 @@ Since Laravel 11.27.1, `php artisan optimize` automatically invokes `ddd:optimiz 10.25.x | 1.x | 11.x | 1.x | -See **[UPGRADING](UPGRADING.md)** for more details about upgrading from 0.x. +See **[UPGRADING](UPGRADING.md)** for more details about upgrading from an older versions. @@ -97,6 +97,36 @@ The following generators are currently available: Generated objects will be placed in the appropriate domain namespace as specified by `ddd.namespaces.*` in the [config file](#config-file). +### Config Utility (Since 1.2) +A configuration utility was introduced in 1.2 to help manage the package's configuration over time. +```bash +php artisan ddd:config +``` +Output: +``` + ┌ Laravel-DDD Config Utility ──────────────────────────────────┐ + │ › ● Run the configuration wizard │ + │ ○ Update and merge ddd.php with latest package version │ + │ ○ Detect domain namespace from composer.json │ + │ ○ Sync composer.json from ddd.php │ + │ ○ Exit │ + └──────────────────────────────────────────────────────────────┘ +``` +These config tasks are also invokeable directly using arguments: +```bash +# Run the configuration wizard +php artisan ddd:config wizard + +# Update and merge ddd.php with latest package version +php artisan ddd:config update + +# Detect domain namespace from composer.json +php artisan ddd:config detect + +# Sync composer.json from ddd.php +php artisan ddd:config composer +``` + ### Other Commands ```bash # Show a summary of current domains in the domain folder @@ -230,34 +260,83 @@ php artisan ddd:view-model Reporting.Customer:MonthlyInvoicesReportViewModel # (supported by all commands where a domain option is accepted) ``` -## Customization -### Config File -This package ships with opinionated (but sensible) configuration defaults. You may customize by publishing the [config file](#config-file) and generator stubs as needed: +### Custom Object Resolution +If you require advanced customization of generated object naming conventions, you may register a custom resolver using `DDD::resolveObjectSchemaUsing()` in your AppServiceProvider's boot method: +```php +use Lunarstorm\LaravelDDD\Facades\DDD; +use Lunarstorm\LaravelDDD\ValueObjects\CommandContext; +use Lunarstorm\LaravelDDD\ValueObjects\ObjectSchema; + +DDD::resolveObjectSchemaUsing(function (string $domainName, string $nameInput, string $type, CommandContext $command): ?ObjectSchema { + if ($type === 'controller' && $command->option('api')) { + return new ObjectSchema( + name: $name = str($nameInput)->replaceEnd('Controller', '')->finish('ApiController')->toString(), + namespace: "App\\Api\\Controllers\\{$domainName}", + fullyQualifiedName: "App\\Api\\Controllers\\{$domainName}\\{$name}", + path: "src/App/Api/Controllers/{$domainName}/{$name}.php", + ); + } + // Return null to fall back to the default + return null; +}); +``` +The example above will result in the following: ```bash -php artisan ddd:publish --config -php artisan ddd:publish --stubs +php artisan ddd:controller Invoicing:PaymentController --api +# Controller [src/App/Api/Controllers/Invoicing/PaymentApiController.php] created successfully. ``` -### Publishing Stubs (Advanced) -For more granular management of stubs, you may use the `ddd:stub` command: + + +## Customizing Stubs +This package ships with a few ddd-specific stubs, while the rest are pulled from the framework. For a quick reference of available stubs and their source, you may use the `ddd:stub --list` command: ```bash -# Publish one or more stubs interactively via prompts -php artisan ddd:stub +php artisan ddd:stub --list +``` + +### Stub Priority +When generating objects using `ddd:*`, stubs are prioritized as follows: +- Try `stubs/ddd/*.stub` (customized for `ddd:*` only) +- Try `stubs/*.stub` (shared by both `make:*` and `ddd:*`) +- Fallback to the package or framework default +### Publishing Stubs +To publish stubs interactively, you may use the `ddd:stub` command: +```bash +php artisan ddd:stub +``` +``` + ┌ What do you want to do? ─────────────────────────────────────┐ + │ › ● Choose stubs to publish │ + │ ○ Publish all stubs │ + └──────────────────────────────────────────────────────────────┘ + + ┌ Which stub should be published? ─────────────────────────────┐ + │ policy │ + ├──────────────────────────────────────────────────────────────┤ + │ › ◼ policy.plain.stub │ + │ ◻ policy.stub │ + └────────────────────────────────────────────────── 1 selected ┘ + Use the space bar to select options. +``` +You may also use shortcuts to skip the interactive steps: +```bash # Publish all stubs php artisan ddd:stub --all -# Publish and overwrite only the files that have already been published -php artisan ddd:stub --all --existing - -# Overwrite any existing files -php artisan ddd:stub --all --force - -# Publish one or more stubs specified as arguments +# Publish one or more stubs specified as arguments (see ddd:stub --list) php artisan ddd:stub model php artisan ddd:stub model dto action php artisan ddd:stub controller controller.plain controller.api + +# Options: + +# Publish and overwrite only the files that have already been published +php artisan ddd:stub ... --existing + +# Overwrite any existing files +php artisan ddd:stub ... --force ``` To publish multiple related stubs at once, use `*` or `.` as a wildcard ending. ```bash @@ -270,10 +349,6 @@ Publishing /stubs/ddd/listener.queued.stub Publishing /stubs/ddd/listener.typed.stub Publishing /stubs/ddd/listener.stub ``` -For a quick reference of available stubs, use the `--list` option: -```bash -php artisan ddd:stub --list -``` ## Domain Autoloading and Discovery Autoloading behaviour can be configured with the `ddd.autoload` configuration option. By default, domain providers, commands, policies, and factories are auto-discovered and registered. diff --git a/UPGRADING.md b/UPGRADING.md index b9177b4..e9a6e17 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,21 @@ # Upgrading - - ## From 0.x to 1.x + +## From 1.1.x to 1.2.x +### Breaking +- Stubs are now published `to base_path('stubs/ddd')` instead of `resource_path('stubs/ddd')`. In other words, they are now co-located alongside the framework's published stubs, within a ddd subfolder. +- Published stubs now use `.stub` extension instead of `.php.stub` (following Laravel's convention). +- If you haven't previously published stubs in your installation, this change will not affect you. + +### Update Config +- Support for Application Layer and Custom Layers was added, introducing changes to the config file. +- Run `php artisan ddd:config update` to rebuild your application's published `ddd.php` config to align with the package's latest copy. +- The update utility will attempt to respect your existing customizations, but you should still review and verify manually. + +### Publishing Stubs +- Old way (removed): `php artisan vendor:publish --tag="ddd-stubs"` +- New way, using stub utility command: `php artisan ddd:stub` (see [Customizing Stubs](README.md#customizing-stubs) in README for more details). + +## From 0.x to 1.x - Minimum required Laravel version is 10.25. - The ddd generator [command syntax](README.md#usage) in 1.x. Generator commands no longer receive a domain argument. For example, instead of `ddd:action Invoicing CreateInvoice`, one of the following would be used: - Using the --domain option: ddd:action CreateInvoice --domain=Invoicing (this takes precedence). diff --git a/src/Commands/Concerns/ResolvesDomainFromInput.php b/src/Commands/Concerns/ResolvesDomainFromInput.php index 5bdb136..103dd77 100644 --- a/src/Commands/Concerns/ResolvesDomainFromInput.php +++ b/src/Commands/Concerns/ResolvesDomainFromInput.php @@ -43,6 +43,11 @@ protected function getPath($name) : parent::getPath($name); } + protected function qualifyClass($name) + { + return $this->blueprint->qualifyClass($name); + } + protected function beforeHandle() { $nameInput = $this->getNameInput(); diff --git a/src/Commands/DomainControllerMakeCommand.php b/src/Commands/DomainControllerMakeCommand.php index 5228211..1cba0c9 100644 --- a/src/Commands/DomainControllerMakeCommand.php +++ b/src/Commands/DomainControllerMakeCommand.php @@ -92,11 +92,6 @@ protected function buildClass($name) $replace = []; - // Todo: these were attempted tweaks to counteract failing CI tests - // on Laravel 10, and should be revisited at some point. - // $replace["use {$this->rootNamespace()}Http\Controllers\Controller;\n"] = ''; - // $replace[' extends Controller'] = ''; - $appRootNamespace = $this->laravel->getNamespace(); $pathToAppBaseController = parent::getPath("Http\Controllers\Controller"); diff --git a/src/Support/GeneratorBlueprint.php b/src/Support/GeneratorBlueprint.php index 6333347..8c1bfd1 100644 --- a/src/Support/GeneratorBlueprint.php +++ b/src/Support/GeneratorBlueprint.php @@ -113,6 +113,11 @@ public function getPath($name) return Path::normalize(app()->basePath($this->schema->path)); } + public function qualifyClass($name) + { + return $this->schema->fullyQualifiedName; + } + public function getFactoryFor(string $name) { return $this->domain->factory($name); diff --git a/tests/Support/ResolveObjectSchemaUsingTest.php b/tests/Support/ResolveObjectSchemaUsingTest.php index 41bafe7..4d7abfe 100644 --- a/tests/Support/ResolveObjectSchemaUsingTest.php +++ b/tests/Support/ResolveObjectSchemaUsingTest.php @@ -46,5 +46,8 @@ $expectedPath = base_path('src/App/Api/Controllers/Invoicing/PaymentApiController.php'); expect(file_get_contents($expectedPath)) - ->toContain("namespace App\Api\Controllers\Invoicing;"); + ->toContain(...[ + "namespace App\Api\Controllers\Invoicing;", + 'class PaymentApiController', + ]); }); From e3a385e04b581d5054dfc8d355ccd8966491b1cb Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 15:18:29 -0500 Subject: [PATCH 061/169] Reorder section. --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e5d62b8..2ded841 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ Output: ``` ### Custom Layers (since 1.2) -Often times, additional top-level namespaces are needed to hold shared components, helpers, and things that are not domain-specific. A common example is the `Infrastructure` layer. You may configure these additional layers in the `ddd.layers` configuration. +Often times, additional top-level namespaces are needed to hold shared components, helpers, and things that are not domain-specific. A common example is the `Infrastructure` layer. You may configure these additional layers in the `ddd.layers` array. ```php // In config/ddd.php 'layers' => [ @@ -227,6 +227,18 @@ php artisan ddd:interface Invoicing:Models/Concerns/HasLineItems # -> Domain\Invoicing\Models\Concerns\HasLineItems ``` +### Subdomains (nested domains) +Subdomains can be specified with dot notation wherever a domain option is accepted. +```bash +# Domain/Reporting/Internal/ViewModels/MonthlyInvoicesReportViewModel +php artisan ddd:view-model Reporting.Internal:MonthlyInvoicesReportViewModel + +# Domain/Reporting/Customer/ViewModels/MonthlyInvoicesReportViewModel +php artisan ddd:view-model Reporting.Customer:MonthlyInvoicesReportViewModel + +# (supported by all commands where a domain option is accepted) +``` + ### Overriding Configured Namespaces at Runtime If for some reason you need to generate a domain object under a namespace different to what is configured in `ddd.namespaces.*`, you may do so using an absolute name starting with `/`. This will generate the object from the root of the domain. @@ -248,18 +260,6 @@ php artisan ddd:exception Invoicing:/Models/Exceptions/InvoiceNotFoundException # -> Domain\Invoicing\Models\Exceptions\InvoiceNotFoundException ``` -### Subdomains (nested domains) -Subdomains can be specified with dot notation wherever a domain option is accepted. -```bash -# Domain/Reporting/Internal/ViewModels/MonthlyInvoicesReportViewModel -php artisan ddd:view-model Reporting.Internal:MonthlyInvoicesReportViewModel - -# Domain/Reporting/Customer/ViewModels/MonthlyInvoicesReportViewModel -php artisan ddd:view-model Reporting.Customer:MonthlyInvoicesReportViewModel - -# (supported by all commands where a domain option is accepted) -``` - ### Custom Object Resolution If you require advanced customization of generated object naming conventions, you may register a custom resolver using `DDD::resolveObjectSchemaUsing()` in your AppServiceProvider's boot method: ```php From c85fe26f287966b67db7185eaf274c045eecb86f Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 15:46:05 -0500 Subject: [PATCH 062/169] Update wording. --- README.md | 2 +- UPGRADING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2ded841..e47fbe5 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,7 @@ php artisan ddd:stub ... --existing # Overwrite any existing files php artisan ddd:stub ... --force ``` -To publish multiple related stubs at once, use `*` or `.` as a wildcard ending. +To publish multiple stubs with common prefixes at once, use `*` or `.` as a wildcard ending to indicate "stubs that starts with": ```bash php artisan ddd:stub listener. ``` diff --git a/UPGRADING.md b/UPGRADING.md index e9a6e17..3dcf5ab 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -13,7 +13,7 @@ ### Publishing Stubs - Old way (removed): `php artisan vendor:publish --tag="ddd-stubs"` -- New way, using stub utility command: `php artisan ddd:stub` (see [Customizing Stubs](README.md#customizing-stubs) in README for more details). +- New way: `php artisan ddd:stub` (see [Customizing Stubs](README.md#customizing-stubs) in README for more details). ## From 0.x to 1.x - Minimum required Laravel version is 10.25. From 0da548fbca058d91e9a413f5939ddba19e707e39 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 18:39:29 -0500 Subject: [PATCH 063/169] Updating readme/changelog/upgrading notes --- CHANGELOG.md | 43 +++++++++++++++++++++++++++---------------- README.md | 2 +- UPGRADING.md | 4 ++-- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a7dfed..80cc295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,32 +4,42 @@ All notable changes to `laravel-ddd` will be documented in this file. ## [Unreleased] ### Breaking -- Stubs are now published to `/stubs/ddd/*` instead of `resources/stubs/ddd/*`. If you have ddd stubs published from a prior version, they should be relocated. +- Stubs are now published to `base_path('stubs/ddd')` instead of `resource_path('stubs/ddd')`. In other words, they are now co-located alongside the framework's published stubs, within a ddd subfolder. +- Published stubs now use `.stub` extension instead of `.php.stub` (following Laravel's convention). +- If you are using published stubs from pre 1.2, you will need to refactor your stubs accordingly. ### Added -- Ability to configure the Application Layer, to generate domain objects that don't typically belong inside the domain layer. +- Support for the Application Layer, to generate domain-specific objects that don't belong directly in the domain layer: ```php // In config/ddd.php - 'application' => [ - 'path' => 'app/Modules', - 'namespace' => 'App\Modules', - 'objects' => [ - 'controller', - 'request', - 'middleware', - ], + 'application_path' => 'app/Modules', + 'application_namespace' => 'App\Modules', + 'application_objects' => [ + 'controller', + 'request', + 'middleware', ], ``` -- Added `ddd:controller` to generate domain-specific controllers in the application layer. -- Added `ddd:request` to generate domain-spefic requests in the application layer. -- Added `ddd:middleware` to generate domain-specific middleware in the application layer. +- Support for Custom Layers, additional top-level namespaces of your choosing, such as `Infrastructure`, `Integrations`, etc.: + ```php + // In config/ddd.php + 'layers' => [ + 'Infrastructure' => 'src/Infrastructure', + ], + ``` +- Added config utility command `ddd:config` to help manage the package's configuration over time. +- Added stub utility command `ddd:stub` to publish one or more stubs selectively. +- Added `ddd:controller` to generate domain-specific controllers. +- Added `ddd:request` to generate domain-spefic requests. +- Added `ddd:middleware` to generate domain-specific middleware. - Added `ddd:migration` to generate domain migrations. - Added `ddd:seeder` to generate domain seeders. -- Added `ddd:stub` to list, search, and publish one or more stubs as needed. +- Added `ddd:stub` to manage stubs. - Migration folders across domains will be registered and scanned when running `php artisan migrate`, in addition to the standard application `database/migrations` path. +- Ability to customize generator object naming conventions with your own logic using `DDD::resolveObjectSchemaUsing()`. ### Changed -- `ddd:model` now internally extends Laravel's native `make:model` and inherits all standard options: +- `ddd:model` now extends Laravel's native `make:model` and inherits all standard options: - `--migration|-m` - `--factory|-f` - `--seed|-s` @@ -39,10 +49,11 @@ All notable changes to `laravel-ddd` will be documented in this file. - `--all|-a` - `--pivot|-p` - `ddd:cache` is now `ddd:optimize` (`ddd:cache` is still available as an alias). -- For Laravel 11.27.1+, the framework's `optimize` and `optimize:clear` commands will automatically invoke `ddd:optimize` and `ddd:clear` respectively. +- Since Laravel 11.27.1, the framework's `optimize` and `optimize:clear` commands will automatically invoke `ddd:optimize` (`ddd:cache`) and `ddd:clear` respectively. ### Deprecated - Domain base models are no longer required by default, and `config('ddd.base_model')` is now `null` by default. +- Stubs are no longer published via `php artisan vendor:publish --tag="ddd-stubs"`. Instead, use `php artisan ddd:stub` to manage them. ## [1.1.3] - 2024-11-05 ### Chore diff --git a/README.md b/README.md index e47fbe5..11905a1 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Since Laravel 11.27.1, `php artisan optimize` automatically invokes `ddd:optimiz 10.25.x | 1.x | 11.x | 1.x | -See **[UPGRADING](UPGRADING.md)** for more details about upgrading from an older versions. +See **[UPGRADING](UPGRADING.md)** for more details about upgrading across different versions. diff --git a/UPGRADING.md b/UPGRADING.md index 3dcf5ab..f7f6b04 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,9 +2,9 @@ ## From 1.1.x to 1.2.x ### Breaking -- Stubs are now published `to base_path('stubs/ddd')` instead of `resource_path('stubs/ddd')`. In other words, they are now co-located alongside the framework's published stubs, within a ddd subfolder. +- Stubs are now published to `base_path('stubs/ddd')` instead of `resource_path('stubs/ddd')`. In other words, they are now co-located alongside the framework's published stubs, within a ddd subfolder. - Published stubs now use `.stub` extension instead of `.php.stub` (following Laravel's convention). -- If you haven't previously published stubs in your installation, this change will not affect you. +- If you are using published stubs from pre 1.2, you will need to refactor your stubs accordingly. ### Update Config - Support for Application Layer and Custom Layers was added, introducing changes to the config file. From efac879d2641f9f113ad45d7869ddf288c13309e Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 22:07:18 -0500 Subject: [PATCH 064/169] Ensure autoloading supports application and custom layers. --- composer.json | 4 +- src/Support/DomainAutoloader.php | 20 ++++++- src/Support/DomainResolver.php | 10 ---- tests/.skeleton/composer.json | 3 +- .../Application/Commands/ApplicationSync.php | 24 +++++++++ .../2024_10_14_215912_application_setup.php | 24 +++++++++ .../src/Application/Models/Login.php | 23 ++++++++ .../src/Application/Policies/LoginPolicy.php | 47 ++++++++++++++++ .../Providers/ApplicationServiceProvider.php | 27 ++++++++++ .../src/Infrastructure/Commands/LogPrune.php | 25 +++++++++ .../src/Infrastructure/Models/AppSession.php | 23 ++++++++ .../Policies/AppSessionPolicy.php | 47 ++++++++++++++++ .../InfrastructureServiceProvider.php | 27 ++++++++++ .../src/Infrastructure/Support/Clipboard.php | 18 +++++++ tests/Autoload/CommandTest.php | 46 +++++++++++----- tests/Autoload/IgnoreTest.php | 49 +++++++++++++---- tests/Autoload/PolicyTest.php | 23 ++++++-- tests/Autoload/ProviderTest.php | 41 ++++++++++---- tests/Generator/CustomLayerTest.php | 54 +++++++++---------- tests/TestCase.php | 3 -- 20 files changed, 457 insertions(+), 81 deletions(-) create mode 100644 tests/.skeleton/src/Application/Commands/ApplicationSync.php create mode 100644 tests/.skeleton/src/Application/Database/Migrations/2024_10_14_215912_application_setup.php create mode 100644 tests/.skeleton/src/Application/Models/Login.php create mode 100644 tests/.skeleton/src/Application/Policies/LoginPolicy.php create mode 100644 tests/.skeleton/src/Application/Providers/ApplicationServiceProvider.php create mode 100644 tests/.skeleton/src/Infrastructure/Commands/LogPrune.php create mode 100644 tests/.skeleton/src/Infrastructure/Models/AppSession.php create mode 100644 tests/.skeleton/src/Infrastructure/Policies/AppSessionPolicy.php create mode 100644 tests/.skeleton/src/Infrastructure/Providers/InfrastructureServiceProvider.php create mode 100644 tests/.skeleton/src/Infrastructure/Support/Clipboard.php diff --git a/composer.json b/composer.json index d19e021..7991560 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,9 @@ "App\\": "vendor/orchestra/testbench-core/laravel/app", "Database\\Factories\\": "vendor/orchestra/testbench-core/laravel/database/factories", "Database\\Seeders\\": "vendor/orchestra/testbench-core/laravel/database/seeders", - "Domain\\": "vendor/orchestra/testbench-core/laravel/src/Domain" + "Domain\\": "vendor/orchestra/testbench-core/laravel/src/Domain", + "Application\\": "vendor/orchestra/testbench-core/laravel/src/Application", + "Infrastructure\\": "vendor/orchestra/testbench-core/laravel/src/Infrastructure" } }, "scripts": { diff --git a/src/Support/DomainAutoloader.php b/src/Support/DomainAutoloader.php index 9e812d9..f879740 100644 --- a/src/Support/DomainAutoloader.php +++ b/src/Support/DomainAutoloader.php @@ -54,6 +54,22 @@ protected static function normalizePaths($path): array ->toArray(); } + protected static function getAllLayerPaths(): array + { + return collect([ + DomainResolver::domainPath(), + DomainResolver::applicationLayerPath(), + ...array_values(config('ddd.layers', [])), + ])->map(fn ($path) => app()->basePath($path))->toArray(); + } + + protected static function getCustomLayerPaths(): array + { + return collect([ + ...array_values(config('ddd.layers', [])), + ])->map(fn ($path) => app()->basePath($path))->toArray(); + } + protected function handleProviders(): void { $providers = DomainCache::has('domain-providers') @@ -155,7 +171,7 @@ protected static function discoverProviders(): array $paths = static::normalizePaths( $configValue === true - ? app()->basePath(DomainResolver::domainPath()) + ? static::getAllLayerPaths() : $configValue ); @@ -179,7 +195,7 @@ protected static function discoverCommands(): array $paths = static::normalizePaths( $configValue === true ? - app()->basePath(DomainResolver::domainPath()) + static::getAllLayerPaths() : $configValue ); diff --git a/src/Support/DomainResolver.php b/src/Support/DomainResolver.php index 8223ca4..ca0213a 100644 --- a/src/Support/DomainResolver.php +++ b/src/Support/DomainResolver.php @@ -125,16 +125,6 @@ public static function resolveLayer(string $domain, ?string $type = null): ?Laye */ public static function getDomainObjectNamespace(string $domain, string $type, ?string $name = null): string { - // $customResolver = app('ddd')->getNamespaceResolver(); - - // $resolved = is_callable($customResolver) - // ? $customResolver($domain, $type, $name, app('ddd')->getCommandContext()) - // : null; - - // if (! is_null($resolved)) { - // return $resolved; - // } - $resolver = function (string $domain, string $type, ?string $name) { $layer = static::resolveLayer($domain, $type); diff --git a/tests/.skeleton/composer.json b/tests/.skeleton/composer.json index 7b6b9bb..794c23c 100644 --- a/tests/.skeleton/composer.json +++ b/tests/.skeleton/composer.json @@ -14,7 +14,8 @@ ], "psr-4": { "App\\": "app/", - "Support\\": "src/Support" + "Application\\": "src/Application", + "Infrastructure\\": "src/Infrastructure" } }, "autoload-dev": { diff --git a/tests/.skeleton/src/Application/Commands/ApplicationSync.php b/tests/.skeleton/src/Application/Commands/ApplicationSync.php new file mode 100644 index 0000000..d295169 --- /dev/null +++ b/tests/.skeleton/src/Application/Commands/ApplicationSync.php @@ -0,0 +1,24 @@ +info('Application state synced!'); + + if ($secret = AppSession::getSecret()) { + $this->line($secret); + + return; + } + } +} diff --git a/tests/.skeleton/src/Application/Database/Migrations/2024_10_14_215912_application_setup.php b/tests/.skeleton/src/Application/Database/Migrations/2024_10_14_215912_application_setup.php new file mode 100644 index 0000000..88fa2f3 --- /dev/null +++ b/tests/.skeleton/src/Application/Database/Migrations/2024_10_14_215912_application_setup.php @@ -0,0 +1,24 @@ +app->singleton('application-layer', function (Application $app) { + return 'application-layer-singleton'; + }); + } + + /** + * Bootstrap any application services. + * + * @return void + */ + public function boot() + { + AppSession::setSecret('application-secret'); + } +} diff --git a/tests/.skeleton/src/Infrastructure/Commands/LogPrune.php b/tests/.skeleton/src/Infrastructure/Commands/LogPrune.php new file mode 100644 index 0000000..777cfa6 --- /dev/null +++ b/tests/.skeleton/src/Infrastructure/Commands/LogPrune.php @@ -0,0 +1,25 @@ +info('System logs pruned!'); + + if ($secret = Clipboard::get('secret')) { + $this->line($secret); + + return; + } + } +} diff --git a/tests/.skeleton/src/Infrastructure/Models/AppSession.php b/tests/.skeleton/src/Infrastructure/Models/AppSession.php new file mode 100644 index 0000000..30f1ee1 --- /dev/null +++ b/tests/.skeleton/src/Infrastructure/Models/AppSession.php @@ -0,0 +1,23 @@ +app->singleton('infrastructure-layer', function (Application $app) { + return 'infrastructure-layer-singleton'; + }); + } + + /** + * Bootstrap any application services. + * + * @return void + */ + public function boot() + { + Clipboard::set('secret', 'infrastructure-secret'); + } +} diff --git a/tests/.skeleton/src/Infrastructure/Support/Clipboard.php b/tests/.skeleton/src/Infrastructure/Support/Clipboard.php new file mode 100644 index 0000000..b8eef1e --- /dev/null +++ b/tests/.skeleton/src/Infrastructure/Support/Clipboard.php @@ -0,0 +1,18 @@ + 'src/Domain', + 'ddd.domain_namespace' => 'Domain', + 'ddd.application_namespace' => 'Application', + 'ddd.application_path' => 'src/Application', + 'ddd.application_objects' => [ + 'controller', + 'request', + 'middleware', + ], + 'ddd.layers' => [ + 'Infrastructure' => 'src/Infrastructure', + ], + ]); }); describe('without autoload', function () { @@ -22,10 +34,14 @@ }); }); - it('does not register the command', function () { - expect(class_exists('Domain\Invoicing\Commands\InvoiceDeliver'))->toBeTrue(); - expect(fn () => Artisan::call('invoice:deliver'))->toThrow(CommandNotFoundException::class); - }); + it('does not register the command', function ($className, $command) { + expect(class_exists($className))->toBeTrue(); + expect(fn () => Artisan::call($command))->toThrow(CommandNotFoundException::class); + })->with([ + ['Domain\Invoicing\Commands\InvoiceDeliver', 'invoice:deliver'], + ['Infrastructure\Commands\LogPrune', 'log:prune'], + ['Application\Commands\ApplicationSync', 'application:sync'], + ]); }); describe('with autoload', function () { @@ -39,17 +55,19 @@ }); }); - it('registers existing commands', function () { - $command = 'invoice:deliver'; - + it('registers existing commands', function ($className, $command, $output) { expect(collect(Artisan::all())) ->has($command) ->toBeTrue(); - expect(class_exists('Domain\Invoicing\Commands\InvoiceDeliver'))->toBeTrue(); + expect(class_exists($className))->toBeTrue(); Artisan::call($command); - expect(Artisan::output())->toContain('Invoice delivered!'); - }); + expect(Artisan::output())->toContain($output); + })->with([ + ['Domain\Invoicing\Commands\InvoiceDeliver', 'invoice:deliver', 'Invoice delivered!'], + ['Infrastructure\Commands\LogPrune', 'log:prune', 'System logs pruned!'], + ['Application\Commands\ApplicationSync', 'application:sync', 'Application state synced!'], + ]); it('registers newly created commands', function () { $command = 'app:invoice-void'; @@ -87,6 +105,8 @@ // command should not be recognized due to cached empty-state expect(fn () => Artisan::call('invoice:deliver'))->toThrow(CommandNotFoundException::class); + expect(fn () => Artisan::call('log:prune'))->toThrow(CommandNotFoundException::class); + expect(fn () => Artisan::call('application:sync'))->toThrow(CommandNotFoundException::class); }); it('can bust the cache', function () { @@ -98,5 +118,7 @@ }); $this->artisan('invoice:deliver')->assertSuccessful(); + $this->artisan('log:prune')->assertSuccessful(); + $this->artisan('application:sync')->assertSuccessful(); }); }); diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 0e94256..b2686fa 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -8,17 +8,34 @@ use Symfony\Component\Finder\SplFileInfo; beforeEach(function () { - Config::set('ddd.domain_path', 'src/Domain'); - Config::set('ddd.domain_namespace', 'Domain'); + Config::set([ + 'ddd.domain_path' => 'src/Domain', + 'ddd.domain_namespace' => 'Domain', + 'ddd.application_namespace' => 'Application', + 'ddd.application_path' => 'src/Application', + 'ddd.application_objects' => [ + 'controller', + 'request', + 'middleware', + ], + 'ddd.layers' => [ + 'Infrastructure' => 'src/Infrastructure', + ], + ]); + $this->setupTestApplication(); }); it('can ignore folders when autoloading', function () { - Artisan::call('ddd:cache'); + Artisan::call('ddd:optimize'); $expected = [ 'Domain\Invoicing\Providers\InvoiceServiceProvider', 'Domain\Invoicing\Commands\InvoiceDeliver', + 'Application\Providers\ApplicationServiceProvider', + 'Application\Commands\ApplicationSync', + 'Infrastructure\Providers\InfrastructureServiceProvider', + 'Infrastructure\Commands\LogPrune', ]; $cached = [ @@ -26,14 +43,16 @@ ...DomainCache::get('domain-commands'), ]; - expect($cached)->toEqual($expected); + expect($cached)->toEqualCanonicalizing($expected); Config::set('ddd.autoload_ignore', ['Commands']); - Artisan::call('ddd:cache'); + Artisan::call('ddd:optimize'); $expected = [ 'Domain\Invoicing\Providers\InvoiceServiceProvider', + 'Application\Providers\ApplicationServiceProvider', + 'Infrastructure\Providers\InfrastructureServiceProvider', ]; $cached = [ @@ -41,14 +60,16 @@ ...DomainCache::get('domain-commands'), ]; - expect($cached)->toEqual($expected); + expect($cached)->toEqualCanonicalizing($expected); Config::set('ddd.autoload_ignore', ['Providers']); - Artisan::call('ddd:cache'); + Artisan::call('ddd:optimize'); $expected = [ 'Domain\Invoicing\Commands\InvoiceDeliver', + 'Application\Commands\ApplicationSync', + 'Infrastructure\Commands\LogPrune', ]; $cached = [ @@ -60,11 +81,15 @@ }); it('can register a custom autoload filter', function () { - Artisan::call('ddd:cache'); + Artisan::call('ddd:optimize'); $expected = [ 'Domain\Invoicing\Providers\InvoiceServiceProvider', 'Domain\Invoicing\Commands\InvoiceDeliver', + 'Application\Providers\ApplicationServiceProvider', + 'Application\Commands\ApplicationSync', + 'Infrastructure\Providers\InfrastructureServiceProvider', + 'Infrastructure\Commands\LogPrune', ]; $cached = [ @@ -72,7 +97,7 @@ ...DomainCache::get('domain-commands'), ]; - expect($cached)->toEqual($expected); + expect($cached)->toEqualCanonicalizing($expected); $secret = null; @@ -80,6 +105,10 @@ $ignoredFiles = [ 'InvoiceServiceProvider.php', 'InvoiceDeliver.php', + 'ApplicationServiceProvider.php', + 'ApplicationSync.php', + 'InfrastructureServiceProvider.php', + 'LogPrune.php', ]; $secret = 'i-was-invoked'; @@ -89,7 +118,7 @@ } }); - Artisan::call('ddd:cache'); + Artisan::call('ddd:optimize'); $cached = [ ...DomainCache::get('domain-providers'), diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index 160117c..60f887a 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -7,22 +7,37 @@ beforeEach(function () { $this->setupTestApplication(); - Config::set('ddd.domain_namespace', 'Domain'); - Config::set('ddd.autoload.factories', true); + Config::set([ + 'ddd.domain_path' => 'src/Domain', + 'ddd.domain_namespace' => 'Domain', + 'ddd.application_namespace' => 'Application', + 'ddd.application_path' => 'src/Application', + 'ddd.application_objects' => [ + 'controller', + 'request', + 'middleware', + ], + 'ddd.layers' => [ + 'Infrastructure' => 'src/Infrastructure', + ], + 'ddd.autoload.factories' => true, + ]); $this->afterApplicationCreated(function () { (new DomainAutoloader)->autoload(); }); }); -it('can autoload domain policy', function ($class, $expectedPolicy) { +it('can autoload policy', function ($class, $expectedPolicy) { expect(class_exists($class))->toBeTrue(); expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); })->with([ ['Domain\Invoicing\Models\Invoice', 'Domain\Invoicing\Policies\InvoicePolicy'], + ['Infrastructure\Models\AppSession', 'Infrastructure\Policies\AppSessionPolicy'], + ['Application\Models\Login', 'Application\Policies\LoginPolicy'], ]); -it('gracefully falls back for non-domain policies', function ($class, $expectedPolicy) { +it('gracefully falls back for non-ddd policies', function ($class, $expectedPolicy) { expect(class_exists($class))->toBeTrue(); expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); })->with([ diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 2454ddc..8c474b6 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -5,8 +5,17 @@ use Lunarstorm\LaravelDDD\Support\DomainCache; beforeEach(function () { - Config::set('ddd.domain_path', 'src/Domain'); - Config::set('ddd.domain_namespace', 'Domain'); + Config::set([ + 'ddd.domain_path' => 'src/Domain', + 'ddd.domain_namespace' => 'Domain', + 'ddd.application_namespace' => 'Application', + 'ddd.application_path' => 'src/Application', + 'ddd.application_objects' => [ + 'controller', + 'request', + 'middleware', + ], + ]); $this->setupTestApplication(); }); @@ -17,10 +26,6 @@ 'ddd.autoload.providers' => false, ]); - // $this->afterApplicationCreated(function () { - // (new DomainAutoloader)->autoload(); - // }); - (new DomainAutoloader)->autoload(); }); @@ -35,17 +40,23 @@ 'ddd.autoload.providers' => true, ]); - // $this->afterApplicationCreated(function () { - // (new DomainAutoloader)->autoload(); - // }); - (new DomainAutoloader)->autoload(); }); - it('registers the provider', function () { + it('registers the provider in domain layer', function () { expect(app('invoicing'))->toEqual('invoicing-singleton'); $this->artisan('invoice:deliver')->expectsOutputToContain('invoice-secret'); }); + + it('registers the provider in application layer', function () { + expect(app('application-layer'))->toEqual('application-layer-singleton'); + $this->artisan('application:sync')->expectsOutputToContain('application-secret'); + }); + + it('registers the provider in custom layer', function () { + expect(app('infrastructure-layer'))->toEqual('infrastructure-layer-singleton'); + $this->artisan('log:prune')->expectsOutputToContain('infrastructure-secret'); + }); }); describe('caching', function () { @@ -65,6 +76,8 @@ }); expect(fn () => app('invoicing'))->toThrow(Exception::class); + expect(fn () => app('application-layer'))->toThrow(Exception::class); + expect(fn () => app('infrastructure-layer'))->toThrow(Exception::class); }); it('can bust the cache', function () { @@ -77,5 +90,11 @@ expect(app('invoicing'))->toEqual('invoicing-singleton'); $this->artisan('invoice:deliver')->expectsOutputToContain('invoice-secret'); + + expect(app('application-layer'))->toEqual('application-layer-singleton'); + $this->artisan('application:sync')->expectsOutputToContain('application-secret'); + + expect(app('infrastructure-layer'))->toEqual('infrastructure-layer-singleton'); + $this->artisan('log:prune')->expectsOutputToContain('infrastructure-secret'); }); }); diff --git a/tests/Generator/CustomLayerTest.php b/tests/Generator/CustomLayerTest.php index 4eb217c..a760c62 100644 --- a/tests/Generator/CustomLayerTest.php +++ b/tests/Generator/CustomLayerTest.php @@ -7,7 +7,7 @@ $this->cleanSlate(); Config::set('ddd.layers', [ - 'Infrastructure' => 'src/Infrastructure', + 'CustomLayer' => 'src/CustomLayer', ]); }); @@ -25,7 +25,7 @@ expect(file_exists($expectedPath))->toBeFalse(); - $command = "ddd:{$type} Infrastructure:{$objectName}"; + $command = "ddd:{$type} CustomLayer:{$objectName}"; Artisan::call($command); @@ -35,27 +35,27 @@ expect(file_get_contents($expectedPath))->toContain("namespace {$expectedNamespace};"); })->with([ - 'action' => ['action', 'SomeAction', 'Infrastructure\Actions', 'src/Infrastructure/Actions/SomeAction.php'], - 'cast' => ['cast', 'SomeCast', 'Infrastructure\Casts', 'src/Infrastructure/Casts/SomeCast.php'], - 'channel' => ['channel', 'SomeChannel', 'Infrastructure\Channels', 'src/Infrastructure/Channels/SomeChannel.php'], - 'command' => ['command', 'SomeCommand', 'Infrastructure\Commands', 'src/Infrastructure/Commands/SomeCommand.php'], - 'event' => ['event', 'SomeEvent', 'Infrastructure\Events', 'src/Infrastructure/Events/SomeEvent.php'], - 'exception' => ['exception', 'SomeException', 'Infrastructure\Exceptions', 'src/Infrastructure/Exceptions/SomeException.php'], - 'job' => ['job', 'SomeJob', 'Infrastructure\Jobs', 'src/Infrastructure/Jobs/SomeJob.php'], - 'listener' => ['listener', 'SomeListener', 'Infrastructure\Listeners', 'src/Infrastructure/Listeners/SomeListener.php'], - 'mail' => ['mail', 'SomeMail', 'Infrastructure\Mail', 'src/Infrastructure/Mail/SomeMail.php'], - 'notification' => ['notification', 'SomeNotification', 'Infrastructure\Notifications', 'src/Infrastructure/Notifications/SomeNotification.php'], - 'observer' => ['observer', 'SomeObserver', 'Infrastructure\Observers', 'src/Infrastructure/Observers/SomeObserver.php'], - 'policy' => ['policy', 'SomePolicy', 'Infrastructure\Policies', 'src/Infrastructure/Policies/SomePolicy.php'], - 'provider' => ['provider', 'SomeProvider', 'Infrastructure\Providers', 'src/Infrastructure/Providers/SomeProvider.php'], - 'resource' => ['resource', 'SomeResource', 'Infrastructure\Resources', 'src/Infrastructure/Resources/SomeResource.php'], - 'rule' => ['rule', 'SomeRule', 'Infrastructure\Rules', 'src/Infrastructure/Rules/SomeRule.php'], - 'scope' => ['scope', 'SomeScope', 'Infrastructure\Scopes', 'src/Infrastructure/Scopes/SomeScope.php'], - 'seeder' => ['seeder', 'SomeSeeder', 'Infrastructure\Database\Seeders', 'src/Infrastructure/Database/Seeders/SomeSeeder.php'], - 'class' => ['class', 'SomeClass', 'Infrastructure', 'src/Infrastructure/SomeClass.php'], - 'enum' => ['enum', 'SomeEnum', 'Infrastructure\Enums', 'src/Infrastructure/Enums/SomeEnum.php'], - 'interface' => ['interface', 'SomeInterface', 'Infrastructure', 'src/Infrastructure/SomeInterface.php'], - 'trait' => ['trait', 'SomeTrait', 'Infrastructure', 'src/Infrastructure/SomeTrait.php'], + 'action' => ['action', 'SomeAction', 'CustomLayer\Actions', 'src/CustomLayer/Actions/SomeAction.php'], + 'cast' => ['cast', 'SomeCast', 'CustomLayer\Casts', 'src/CustomLayer/Casts/SomeCast.php'], + 'channel' => ['channel', 'SomeChannel', 'CustomLayer\Channels', 'src/CustomLayer/Channels/SomeChannel.php'], + 'command' => ['command', 'SomeCommand', 'CustomLayer\Commands', 'src/CustomLayer/Commands/SomeCommand.php'], + 'event' => ['event', 'SomeEvent', 'CustomLayer\Events', 'src/CustomLayer/Events/SomeEvent.php'], + 'exception' => ['exception', 'SomeException', 'CustomLayer\Exceptions', 'src/CustomLayer/Exceptions/SomeException.php'], + 'job' => ['job', 'SomeJob', 'CustomLayer\Jobs', 'src/CustomLayer/Jobs/SomeJob.php'], + 'listener' => ['listener', 'SomeListener', 'CustomLayer\Listeners', 'src/CustomLayer/Listeners/SomeListener.php'], + 'mail' => ['mail', 'SomeMail', 'CustomLayer\Mail', 'src/CustomLayer/Mail/SomeMail.php'], + 'notification' => ['notification', 'SomeNotification', 'CustomLayer\Notifications', 'src/CustomLayer/Notifications/SomeNotification.php'], + 'observer' => ['observer', 'SomeObserver', 'CustomLayer\Observers', 'src/CustomLayer/Observers/SomeObserver.php'], + 'policy' => ['policy', 'SomePolicy', 'CustomLayer\Policies', 'src/CustomLayer/Policies/SomePolicy.php'], + 'provider' => ['provider', 'SomeProvider', 'CustomLayer\Providers', 'src/CustomLayer/Providers/SomeProvider.php'], + 'resource' => ['resource', 'SomeResource', 'CustomLayer\Resources', 'src/CustomLayer/Resources/SomeResource.php'], + 'rule' => ['rule', 'SomeRule', 'CustomLayer\Rules', 'src/CustomLayer/Rules/SomeRule.php'], + 'scope' => ['scope', 'SomeScope', 'CustomLayer\Scopes', 'src/CustomLayer/Scopes/SomeScope.php'], + 'seeder' => ['seeder', 'SomeSeeder', 'CustomLayer\Database\Seeders', 'src/CustomLayer/Database/Seeders/SomeSeeder.php'], + 'class' => ['class', 'SomeClass', 'CustomLayer', 'src/CustomLayer/SomeClass.php'], + 'enum' => ['enum', 'SomeEnum', 'CustomLayer\Enums', 'src/CustomLayer/Enums/SomeEnum.php'], + 'interface' => ['interface', 'SomeInterface', 'CustomLayer', 'src/CustomLayer/SomeInterface.php'], + 'trait' => ['trait', 'SomeTrait', 'CustomLayer', 'src/CustomLayer/SomeTrait.php'], ]); it('ignores custom layer if object belongs in the application layer', function ($type, $objectName, $expectedNamespace, $expectedPath) { @@ -76,7 +76,7 @@ expect(file_exists($expectedPath))->toBeFalse(); - $command = "ddd:{$type} Infrastructure:{$objectName}"; + $command = "ddd:{$type} CustomLayer:{$objectName}"; Artisan::call($command); @@ -86,7 +86,7 @@ expect(file_get_contents($expectedPath))->toContain("namespace {$expectedNamespace};"); })->with([ - 'request' => ['request', 'SomeRequest', 'Application\Infrastructure\Requests', 'src/Application/Infrastructure/Requests/SomeRequest.php'], - 'controller' => ['controller', 'SomeController', 'Application\Infrastructure\Controllers', 'src/Application/Infrastructure/Controllers/SomeController.php'], - 'middleware' => ['middleware', 'SomeMiddleware', 'Application\Infrastructure\Middleware', 'src/Application/Infrastructure/Middleware/SomeMiddleware.php'], + 'request' => ['request', 'SomeRequest', 'Application\CustomLayer\Requests', 'src/Application/CustomLayer/Requests/SomeRequest.php'], + 'controller' => ['controller', 'SomeController', 'Application\CustomLayer\Controllers', 'src/Application/CustomLayer/Controllers/SomeController.php'], + 'middleware' => ['middleware', 'SomeMiddleware', 'Application\CustomLayer\Middleware', 'src/Application/CustomLayer/Middleware/SomeMiddleware.php'], ]); diff --git a/tests/TestCase.php b/tests/TestCase.php index 40e83d3..a36cb99 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -129,9 +129,6 @@ protected function cleanSlate() File::deleteDirectory(resource_path('stubs/ddd')); File::deleteDirectory(base_path('stubs')); File::deleteDirectory(base_path('Custom')); - // File::deleteDirectory(base_path('src/Domain')); - // File::deleteDirectory(base_path('src/Domains')); - // File::deleteDirectory(base_path('src/App')); File::deleteDirectory(app_path('Policies')); File::deleteDirectory(app_path('Modules')); File::deleteDirectory(base_path('bootstrap/cache/ddd')); From ffd79f750566cbc45cd289df7eb138908da3b586 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 23:48:54 -0500 Subject: [PATCH 065/169] Update tests --- tests/.skeleton/composer.json | 4 +-- .../src/Infrastructure/Commands/LogPrune.php | 1 - tests/Autoload/CommandTest.php | 26 ++++++++++++++----- tests/Autoload/ProviderTest.php | 6 ++++- tests/Command/InstallTest.php | 4 +-- tests/TestCase.php | 8 +++--- 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/tests/.skeleton/composer.json b/tests/.skeleton/composer.json index 794c23c..26fbe9e 100644 --- a/tests/.skeleton/composer.json +++ b/tests/.skeleton/composer.json @@ -13,9 +13,7 @@ "tests/TestCase.php" ], "psr-4": { - "App\\": "app/", - "Application\\": "src/Application", - "Infrastructure\\": "src/Infrastructure" + "App\\": "app/" } }, "autoload-dev": { diff --git a/tests/.skeleton/src/Infrastructure/Commands/LogPrune.php b/tests/.skeleton/src/Infrastructure/Commands/LogPrune.php index 777cfa6..57f7ef7 100644 --- a/tests/.skeleton/src/Infrastructure/Commands/LogPrune.php +++ b/tests/.skeleton/src/Infrastructure/Commands/LogPrune.php @@ -3,7 +3,6 @@ namespace Infrastructure\Commands; use Illuminate\Console\Command; -use Infrastructure\Models\AppSession; use Infrastructure\Support\Clipboard; class LogPrune extends Command diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 4f3e5a7..4fb976b 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -20,18 +20,27 @@ 'ddd.layers' => [ 'Infrastructure' => 'src/Infrastructure', ], + 'ddd.autoload_ignore' => [ + 'Tests', + 'Database/Migrations', + ], + 'cache.default' => 'file', ]); }); +afterEach(function () { + DomainCache::clear(); +}); + describe('without autoload', function () { beforeEach(function () { Config::set('ddd.autoload.commands', false); - $this->setupTestApplication(); - $this->afterApplicationCreated(function () { (new DomainAutoloader)->autoload(); }); + + $this->setupTestApplication(); }); it('does not register the command', function ($className, $command) { @@ -49,18 +58,19 @@ Config::set('ddd.autoload.commands', true); $this->setupTestApplication(); + }); + + it('registers existing commands', function ($className, $command, $output) { + expect(class_exists($className))->toBeTrue(); $this->afterApplicationCreated(function () { (new DomainAutoloader)->autoload(); }); - }); - it('registers existing commands', function ($className, $command, $output) { expect(collect(Artisan::all())) ->has($command) ->toBeTrue(); - expect(class_exists($className))->toBeTrue(); Artisan::call($command); expect(Artisan::output())->toContain($output); })->with([ @@ -72,6 +82,10 @@ it('registers newly created commands', function () { $command = 'app:invoice-void'; + $this->afterApplicationCreated(function () { + (new DomainAutoloader)->autoload(); + }); + expect(collect(Artisan::all())) ->has($command) ->toBeFalse(); @@ -103,7 +117,7 @@ (new DomainAutoloader)->autoload(); }); - // command should not be recognized due to cached empty-state + // commands should not be recognized due to cached empty-state expect(fn () => Artisan::call('invoice:deliver'))->toThrow(CommandNotFoundException::class); expect(fn () => Artisan::call('log:prune'))->toThrow(CommandNotFoundException::class); expect(fn () => Artisan::call('application:sync'))->toThrow(CommandNotFoundException::class); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 8c474b6..904989d 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -8,8 +8,8 @@ Config::set([ 'ddd.domain_path' => 'src/Domain', 'ddd.domain_namespace' => 'Domain', - 'ddd.application_namespace' => 'Application', 'ddd.application_path' => 'src/Application', + 'ddd.application_namespace' => 'Application', 'ddd.application_objects' => [ 'controller', 'request', @@ -20,6 +20,10 @@ $this->setupTestApplication(); }); +afterEach(function () { + $this->setupTestApplication(); +}); + describe('without autoload', function () { beforeEach(function () { config([ diff --git a/tests/Command/InstallTest.php b/tests/Command/InstallTest.php index 7939bff..76d4deb 100644 --- a/tests/Command/InstallTest.php +++ b/tests/Command/InstallTest.php @@ -7,7 +7,7 @@ }); afterEach(function () { - $this->setupTestApplication(); + $this->setupTestApplication()->composerReload(); }); it('publishes config', function () { @@ -57,7 +57,7 @@ unlink(config_path('ddd.php')); // Reset composer back to the factory state - $this->setDomainPathInComposer('Domain', 'src/Domain', reload: true); + // $this->setAutoloadPathInComposer('Domain', 'src/Domain', reload: true); })->with([ ['src/Domain', 'Domain'], ['src/Domains', 'Domains'], diff --git a/tests/TestCase.php b/tests/TestCase.php index a36cb99..85e74a6 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -162,16 +162,18 @@ protected function setupTestApplication() File::copyDirectory(__DIR__.'/.skeleton/src', base_path('src')); File::copy(__DIR__.'/.skeleton/bootstrap/providers.php', base_path('bootstrap/providers.php')); - $this->setDomainPathInComposer('Domain', 'src/Domain'); + $this->setAutoloadPathInComposer('Domain', 'src/Domain'); + $this->setAutoloadPathInComposer('Application', 'src/Application'); + $this->setAutoloadPathInComposer('Infrastructure', 'src/Infrastructure'); return $this; } - protected function setDomainPathInComposer($domainNamespace, $domainPath, bool $reload = true) + protected function setAutoloadPathInComposer($namespace, $path, bool $reload = true) { $this->updateComposer( set: [ - [['autoload', 'psr-4', $domainNamespace.'\\'], $domainPath], + [['autoload', 'psr-4', $namespace.'\\'], $path], ], ); From 80f43468b6a12f67339cff4a34662dc061034d5c Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Wed, 13 Nov 2024 23:56:23 -0500 Subject: [PATCH 066/169] Set more config values. --- tests/Command/OptimizeTest.php | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index 4626d4e..7c3aeec 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -1,13 +1,33 @@ setupTestApplication(); - config(['cache.default' => 'file']); + Config::set([ + 'ddd.domain_path' => 'src/Domain', + 'ddd.domain_namespace' => 'Domain', + 'ddd.application_namespace' => 'Application', + 'ddd.application_path' => 'src/Application', + 'ddd.application_objects' => [ + 'controller', + 'request', + 'middleware', + ], + 'ddd.layers' => [ + 'Infrastructure' => 'src/Infrastructure', + ], + 'ddd.autoload_ignore' => [ + 'Tests', + 'Database/Migrations', + ], + 'cache.default' => 'file', + ]); $this->artisan('optimize:clear')->execute(); @@ -25,6 +45,10 @@ expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); + // $this->afterApplicationCreated(function () { + // (new DomainAutoloader)->autoload(); + // }); + $this ->artisan('ddd:optimize') ->expectsOutputToContain('Caching DDD providers, commands, migration paths.') From 74eb34bc4180b95360b869db5f14540c736fcd65 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 00:06:37 -0500 Subject: [PATCH 067/169] Normalize config initialization across autoload tests. --- tests/Autoload/IgnoreTest.php | 5 +++++ tests/Autoload/PolicyTest.php | 6 +++++- tests/Autoload/ProviderTest.php | 10 +++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index b2686fa..f7c82fd 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -21,6 +21,11 @@ 'ddd.layers' => [ 'Infrastructure' => 'src/Infrastructure', ], + 'ddd.autoload_ignore' => [ + 'Tests', + 'Database/Migrations', + ], + 'cache.default' => 'file', ]); $this->setupTestApplication(); diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index 60f887a..a9e560e 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -20,7 +20,11 @@ 'ddd.layers' => [ 'Infrastructure' => 'src/Infrastructure', ], - 'ddd.autoload.factories' => true, + 'ddd.autoload_ignore' => [ + 'Tests', + 'Database/Migrations', + ], + 'cache.default' => 'file', ]); $this->afterApplicationCreated(function () { diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 904989d..387e06a 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -8,13 +8,21 @@ Config::set([ 'ddd.domain_path' => 'src/Domain', 'ddd.domain_namespace' => 'Domain', - 'ddd.application_path' => 'src/Application', 'ddd.application_namespace' => 'Application', + 'ddd.application_path' => 'src/Application', 'ddd.application_objects' => [ 'controller', 'request', 'middleware', ], + 'ddd.layers' => [ + 'Infrastructure' => 'src/Infrastructure', + ], + 'ddd.autoload_ignore' => [ + 'Tests', + 'Database/Migrations', + ], + 'cache.default' => 'file', ]); $this->setupTestApplication(); From 977653825b257fbdc697206265943d3c235352ae Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 00:10:55 -0500 Subject: [PATCH 068/169] Attempt fix --- tests/Autoload/ProviderTest.php | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 387e06a..1f8a4b9 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -42,7 +42,7 @@ }); it('does not register the provider', function () { - expect(fn () => app('invoicing'))->toThrow(Exception::class); + expect(fn() => app('invoicing'))->toThrow(Exception::class); }); }); @@ -51,21 +51,31 @@ config([ 'ddd.autoload.providers' => true, ]); - - (new DomainAutoloader)->autoload(); }); it('registers the provider in domain layer', function () { + $this->afterApplicationCreated(function () { + (new DomainAutoloader)->autoload(); + }); + expect(app('invoicing'))->toEqual('invoicing-singleton'); $this->artisan('invoice:deliver')->expectsOutputToContain('invoice-secret'); }); it('registers the provider in application layer', function () { + $this->afterApplicationCreated(function () { + (new DomainAutoloader)->autoload(); + }); + expect(app('application-layer'))->toEqual('application-layer-singleton'); $this->artisan('application:sync')->expectsOutputToContain('application-secret'); }); it('registers the provider in custom layer', function () { + $this->afterApplicationCreated(function () { + (new DomainAutoloader)->autoload(); + }); + expect(app('infrastructure-layer'))->toEqual('infrastructure-layer-singleton'); $this->artisan('log:prune')->expectsOutputToContain('infrastructure-secret'); }); @@ -87,9 +97,9 @@ (new DomainAutoloader)->autoload(); }); - expect(fn () => app('invoicing'))->toThrow(Exception::class); - expect(fn () => app('application-layer'))->toThrow(Exception::class); - expect(fn () => app('infrastructure-layer'))->toThrow(Exception::class); + expect(fn() => app('invoicing'))->toThrow(Exception::class); + expect(fn() => app('application-layer'))->toThrow(Exception::class); + expect(fn() => app('infrastructure-layer'))->toThrow(Exception::class); }); it('can bust the cache', function () { From 604d387f156b6705ef1ad14f9a9cbb8fd7971f0b Mon Sep 17 00:00:00 2001 From: JasperTey Date: Thu, 14 Nov 2024 05:11:25 +0000 Subject: [PATCH 069/169] Fix styling --- tests/Autoload/ProviderTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 1f8a4b9..73c8ccb 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -42,7 +42,7 @@ }); it('does not register the provider', function () { - expect(fn() => app('invoicing'))->toThrow(Exception::class); + expect(fn () => app('invoicing'))->toThrow(Exception::class); }); }); @@ -97,9 +97,9 @@ (new DomainAutoloader)->autoload(); }); - expect(fn() => app('invoicing'))->toThrow(Exception::class); - expect(fn() => app('application-layer'))->toThrow(Exception::class); - expect(fn() => app('infrastructure-layer'))->toThrow(Exception::class); + expect(fn () => app('invoicing'))->toThrow(Exception::class); + expect(fn () => app('application-layer'))->toThrow(Exception::class); + expect(fn () => app('infrastructure-layer'))->toThrow(Exception::class); }); it('can bust the cache', function () { From b0337f53aee81c87aefe859bdd793ccf89c52329 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 17:01:14 -0500 Subject: [PATCH 070/169] Working through issues sample application test environment. --- src/Commands/OptimizeCommand.php | 8 +- src/DomainManager.php | 23 ++- src/Facades/DDD.php | 1 + src/LaravelDDDServiceProvider.php | 26 ++- .../{DomainAutoloader.php => Autoloader.php} | 121 ++++++++---- src/Support/DomainMigration.php | 4 +- tests/.skeleton/config/ddd.php | 185 ++++++++++++++++++ tests/Autoload/CommandTest.php | 96 +++------ tests/Autoload/FactoryTest.php | 5 +- tests/Autoload/IgnoreTest.php | 23 +-- tests/Autoload/PolicyTest.php | 29 +-- tests/Autoload/ProviderTest.php | 118 ++++++----- tests/AutoloadingTest.php | 40 ++++ tests/BootsTestApplication.php | 5 + tests/Command/OptimizeTest.php | 33 +--- tests/Factory/DomainFactoryTest.php | 3 +- .../Model/MakeWithControllerTest.php | 14 +- tests/Support/AutoloaderTest.php | 46 ++++- tests/TestCase.php | 55 ++++++ 19 files changed, 562 insertions(+), 273 deletions(-) rename src/Support/{DomainAutoloader.php => Autoloader.php} (62%) create mode 100644 tests/.skeleton/config/ddd.php create mode 100644 tests/AutoloadingTest.php create mode 100644 tests/BootsTestApplication.php diff --git a/src/Commands/OptimizeCommand.php b/src/Commands/OptimizeCommand.php index e73462e..b36f7cd 100644 --- a/src/Commands/OptimizeCommand.php +++ b/src/Commands/OptimizeCommand.php @@ -3,7 +3,7 @@ namespace Lunarstorm\LaravelDDD\Commands; use Illuminate\Console\Command; -use Lunarstorm\LaravelDDD\Support\DomainAutoloader; +use Lunarstorm\LaravelDDD\Facades\DDD; use Lunarstorm\LaravelDDD\Support\DomainMigration; class OptimizeCommand extends Command @@ -24,11 +24,9 @@ protected function configure() public function handle() { $this->components->info('Caching DDD providers, commands, migration paths.'); - - $this->components->task('domain providers', fn () => DomainAutoloader::cacheProviders()); - $this->components->task('domain commands', fn () => DomainAutoloader::cacheCommands()); + $this->components->task('domain providers', fn () => DDD::autoloader()->cacheProviders()); + $this->components->task('domain commands', fn () => DDD::autoloader()->cacheCommands()); $this->components->task('domain migration paths', fn () => DomainMigration::cachePaths()); - $this->newLine(); } } diff --git a/src/DomainManager.php b/src/DomainManager.php index eff013d..b0f084f 100755 --- a/src/DomainManager.php +++ b/src/DomainManager.php @@ -2,6 +2,7 @@ namespace Lunarstorm\LaravelDDD; +use Lunarstorm\LaravelDDD\Support\Autoloader; use Lunarstorm\LaravelDDD\Support\GeneratorBlueprint; use Lunarstorm\LaravelDDD\Support\Path; @@ -35,19 +36,16 @@ class DomainManager protected ?GeneratorBlueprint $commandContext; - protected StubManager $stubs; - public function __construct() { $this->autoloadFilter = null; $this->applicationLayerFilter = null; $this->commandContext = null; - $this->stubs = new StubManager; } - public function config(): ConfigManager + public function autoloader(): Autoloader { - return app(ConfigManager::class); + return app(Autoloader::class); } public function composer(): ComposerManager @@ -55,6 +53,16 @@ public function composer(): ComposerManager return app(ComposerManager::class); } + public function config(): ConfigManager + { + return app(ConfigManager::class); + } + + public function stubs(): StubManager + { + return app(StubManager::class); + } + public function filterAutoloadPathsUsing(callable $filter): void { $this->autoloadFilter = $filter; @@ -94,9 +102,4 @@ public function laravelVersion($value) { return version_compare(app()->version(), $value, '>='); } - - public function stubs(): StubManager - { - return $this->stubs; - } } diff --git a/src/Facades/DDD.php b/src/Facades/DDD.php index a7c4afa..9496a08 100644 --- a/src/Facades/DDD.php +++ b/src/Facades/DDD.php @@ -10,6 +10,7 @@ * @method static void filterAutoloadPathsUsing(callable $filter) * @method static void resolveObjectSchemaUsing(callable $resolver) * @method static string packagePath(string $path = '') + * @method static \Lunarstorm\LaravelDDD\Support\Autoloader autoloader() * @method static \Lunarstorm\LaravelDDD\ConfigManager config() * @method static \Lunarstorm\LaravelDDD\StubManager stubs() * @method static \Lunarstorm\LaravelDDD\ComposerManager composer() diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index b94b48f..323c4ba 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -3,7 +3,7 @@ namespace Lunarstorm\LaravelDDD; use Illuminate\Database\Migrations\MigrationCreator; -use Lunarstorm\LaravelDDD\Support\DomainAutoloader; +use Lunarstorm\LaravelDDD\Support\Autoloader; use Lunarstorm\LaravelDDD\Support\DomainMigration; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -16,15 +16,27 @@ public function configurePackage(Package $package): void return new DomainManager; }); - $this->app->scoped(ConfigManager::class, function () { - return new ConfigManager(config_path('ddd.php')); + $this->app->scoped(Autoloader::class, function () { + return new Autoloader; }); $this->app->scoped(ComposerManager::class, function () { return ComposerManager::make(app()->basePath('composer.json')); }); + $this->app->scoped(ConfigManager::class, function () { + return new ConfigManager(config_path('ddd.php')); + }); + + $this->app->scoped(StubManager::class, function () { + return new StubManager; + }); + $this->app->bind('ddd', DomainManager::class); + $this->app->bind('ddd.autoloader', Autoloader::class); + $this->app->bind('ddd.config', ConfigManager::class); + $this->app->bind('ddd.composer', ComposerManager::class); + $this->app->bind('ddd.stubs', StubManager::class); /* * This class is a Package Service Provider @@ -120,12 +132,16 @@ public function packageBooted() key: 'laravel-ddd', ); } + + // dump([ + // 'package booted' => config('ddd') + // ]); + + app('ddd.autoloader')->boot(); } public function packageRegistered() { - (new DomainAutoloader)->autoload(); - $this->registerMigrations(); } } diff --git a/src/Support/DomainAutoloader.php b/src/Support/Autoloader.php similarity index 62% rename from src/Support/DomainAutoloader.php rename to src/Support/Autoloader.php index f879740..c874e93 100644 --- a/src/Support/DomainAutoloader.php +++ b/src/Support/Autoloader.php @@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Gate; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; +use Illuminate\Support\Traits\Conditionable; use Lorisleiva\Lody\Lody; use Lunarstorm\LaravelDDD\Factories\DomainFactory; use Lunarstorm\LaravelDDD\ValueObjects\DomainObject; @@ -19,42 +20,51 @@ use Symfony\Component\Finder\SplFileInfo; use Throwable; -class DomainAutoloader +class Autoloader { + use Conditionable; + + protected string $appNamespace; + + protected array $discoveredCommands = []; + + protected array $discoveredProviders = []; + public function __construct() { - // + $this->appNamespace = $this->resolveAppNamespace(); } - public function autoload(): void + public function boot(): void { if (! config()->has('ddd.autoload')) { return; } - $this->handleProviders(); - - if (app()->runningInConsole()) { - $this->handleCommands(); - } + // if ($discoveredCommands = $this->getDiscoveredCommands()) { + // dump('Commands were already discovered', $discoveredCommands); + // } - if (config('ddd.autoload.policies') === true) { - $this->handlePolicies(); - } + // if ($discoveredProviders = $this->getDiscoveredProviders()) { + // dump('Providers were already discovered', $discoveredProviders); + // } - if (config('ddd.autoload.factories') === true) { - $this->handleFactories(); - } + $this + ->handleProviders() + ->when(app()->runningInConsole(), fn ($autoloader) => $autoloader->handleProviders()) + ->when(config('ddd.autoload.commands') === true, fn ($autoloader) => $autoloader->handleCommands()) + ->when(config('ddd.autoload.policies') === true, fn ($autoloader) => $autoloader->handlePolicies()) + ->when(config('ddd.autoload.factories') === true, fn ($autoloader) => $autoloader->handleFactories()); } - protected static function normalizePaths($path): array + protected function normalizePaths($path): array { return collect($path) ->filter(fn ($path) => is_dir($path)) ->toArray(); } - protected static function getAllLayerPaths(): array + public function getAllLayerPaths(): array { return collect([ DomainResolver::domainPath(), @@ -63,33 +73,49 @@ protected static function getAllLayerPaths(): array ])->map(fn ($path) => app()->basePath($path))->toArray(); } - protected static function getCustomLayerPaths(): array + protected function getCustomLayerPaths(): array { return collect([ ...array_values(config('ddd.layers', [])), ])->map(fn ($path) => app()->basePath($path))->toArray(); } - protected function handleProviders(): void + protected function handleProviders() { $providers = DomainCache::has('domain-providers') ? DomainCache::get('domain-providers') - : static::discoverProviders(); + : $this->discoverProviders(); foreach ($providers as $provider) { + $this->discoveredProviders[$provider] = $provider; app()->register($provider); } + + return $this; } - protected function handleCommands(): void + protected function handleCommands() { $commands = DomainCache::has('domain-commands') ? DomainCache::get('domain-commands') - : static::discoverCommands(); + : $this->discoverCommands(); foreach ($commands as $command) { + $this->discoveredCommands[$command] = $command; $this->registerCommand($command); } + + return $this; + } + + public function getDiscoveredCommands(): array + { + return $this->discoveredCommands; + } + + public function getDiscoveredProviders(): array + { + return $this->discoveredProviders; } protected function registerCommand($class) @@ -99,7 +125,7 @@ protected function registerCommand($class) }); } - protected function handlePolicies(): void + protected function handlePolicies() { Gate::guessPolicyNamesUsing(static function (string $class): array|string { if ($model = DomainObject::fromClass($class, 'model')) { @@ -120,26 +146,28 @@ protected function handlePolicies(): void return class_exists($class); }) ?: [$classDirname.'\\Policies\\'.class_basename($class).'Policy']); }); + + return $this; } - protected function handleFactories(): void + protected function handleFactories() { Factory::guessFactoryNamesUsing(function (string $modelName) { if ($factoryName = DomainFactory::resolveFactoryName($modelName)) { return $factoryName; } - $appNamespace = static::appNamespace(); - - $modelName = Str::startsWith($modelName, $appNamespace.'Models\\') - ? Str::after($modelName, $appNamespace.'Models\\') - : Str::after($modelName, $appNamespace); + $modelName = Str::startsWith($modelName, $this->appNamespace.'Models\\') + ? Str::after($modelName, $this->appNamespace.'Models\\') + : Str::after($modelName, $this->appNamespace); return 'Database\\Factories\\'.$modelName.'Factory'; }); + + return $this; } - protected static function finder($paths) + protected function finder($paths) { $filter = app('ddd')->getAutoloadFilter() ?? function (SplFileInfo $file) { $pathAfterDomain = str($file->getRelativePath()) @@ -161,7 +189,7 @@ protected static function finder($paths) ->filter($filter); } - protected static function discoverProviders(): array + protected function discoverProviders(): array { $configValue = config('ddd.autoload.providers'); @@ -169,9 +197,9 @@ protected static function discoverProviders(): array return []; } - $paths = static::normalizePaths( + $paths = $this->normalizePaths( $configValue === true - ? static::getAllLayerPaths() + ? $this->getAllLayerPaths() : $configValue ); @@ -179,13 +207,13 @@ protected static function discoverProviders(): array return []; } - return Lody::classesFromFinder(static::finder($paths)) + return Lody::classesFromFinder($this->finder($paths)) ->isNotAbstract() ->isInstanceOf(ServiceProvider::class) ->toArray(); } - protected static function discoverCommands(): array + protected function discoverCommands(): array { $configValue = config('ddd.autoload.commands'); @@ -193,9 +221,9 @@ protected static function discoverCommands(): array return []; } - $paths = static::normalizePaths( + $paths = $this->normalizePaths( $configValue === true ? - static::getAllLayerPaths() + $this->getAllLayerPaths() : $configValue ); @@ -203,23 +231,32 @@ protected static function discoverCommands(): array return []; } - return Lody::classesFromFinder(static::finder($paths)) + return Lody::classesFromFinder($this->finder($paths)) ->isNotAbstract() ->isInstanceOf(Command::class) ->toArray(); } - public static function cacheProviders(): void + public function cacheProviders() + { + DomainCache::set('domain-providers', $this->discoverProviders()); + + return $this; + } + + public function cacheCommands() { - DomainCache::set('domain-providers', static::discoverProviders()); + DomainCache::set('domain-commands', $this->discoverCommands()); + + return $this; } - public static function cacheCommands(): void + protected function flush() { - DomainCache::set('domain-commands', static::discoverCommands()); + return $this; } - protected static function appNamespace() + protected function resolveAppNamespace() { try { return Container::getInstance() diff --git a/src/Support/DomainMigration.php b/src/Support/DomainMigration.php index 9f3a939..b6cd0ba 100644 --- a/src/Support/DomainMigration.php +++ b/src/Support/DomainMigration.php @@ -31,7 +31,7 @@ public static function paths(): array : static::discoverPaths(); } - protected static function normalizePaths($path): array + protected static function filterDirectories($path): array { return collect($path) ->filter(fn ($path) => is_dir($path)) @@ -46,7 +46,7 @@ public static function discoverPaths(): array return []; } - $paths = static::normalizePaths([ + $paths = static::filterDirectories([ app()->basePath(DomainResolver::domainPath()), ]); diff --git a/tests/.skeleton/config/ddd.php b/tests/.skeleton/config/ddd.php new file mode 100644 index 0000000..9f65237 --- /dev/null +++ b/tests/.skeleton/config/ddd.php @@ -0,0 +1,185 @@ + 'src/Domain', + 'domain_namespace' => 'Domain', + + /* + |-------------------------------------------------------------------------- + | Application Layer + |-------------------------------------------------------------------------- + | + | The path and namespace of the application layer, and the objects + | that should be recognized as part of the application layer. + | + */ + 'application_path' => 'Application', + 'application_namespace' => 'src\Application', + 'application_objects' => [ + 'controller', + 'request', + 'middleware', + ], + + /* + |-------------------------------------------------------------------------- + | Custom Layers + |-------------------------------------------------------------------------- + | + | Additional top-level namespaces and paths that should be recognized as + | layers when generating ddd:* objects. + | + | e.g., 'Infrastructure' => 'src/Infrastructure', + | + */ + 'layers' => [ + 'Infrastructure' => 'src/Infrastructure', + ], + + /* + |-------------------------------------------------------------------------- + | Object Namespaces + |-------------------------------------------------------------------------- + | + | This value contains the default namespaces of ddd:* generated + | objects relative to the layer of which the object belongs to. + | + */ + 'namespaces' => [ + 'model' => 'Models', + 'data_transfer_object' => 'Data', + 'view_model' => 'ViewModels', + 'value_object' => 'ValueObjects', + 'action' => 'Actions', + 'cast' => 'Casts', + 'class' => '', + 'channel' => 'Channels', + 'command' => 'Commands', + 'controller' => 'Controllers', + 'enum' => 'Enums', + 'event' => 'Events', + 'exception' => 'Exceptions', + 'factory' => 'Database\Factories', + 'interface' => '', + 'job' => 'Jobs', + 'listener' => 'Listeners', + 'mail' => 'Mail', + 'middleware' => 'Middleware', + 'migration' => 'Database\Migrations', + 'notification' => 'Notifications', + 'observer' => 'Observers', + 'policy' => 'Policies', + 'provider' => 'Providers', + 'resource' => 'Resources', + 'request' => 'Requests', + 'rule' => 'Rules', + 'scope' => 'Scopes', + 'seeder' => 'Database\Seeders', + 'trait' => '', + ], + + /* + |-------------------------------------------------------------------------- + | Base Model + |-------------------------------------------------------------------------- + | + | The base model class which generated domain models should extend. If + | set to null, the generated models will extend Laravel's default. + | + */ + 'base_model' => null, + + /* + |-------------------------------------------------------------------------- + | Base DTO + |-------------------------------------------------------------------------- + | + | The base class which generated data transfer objects should extend. By + | default, generated DTOs will extend `Spatie\LaravelData\Data` from + | Spatie's Laravel-data package, a highly recommended data object + | package to work with. + | + */ + 'base_dto' => 'Spatie\LaravelData\Data', + + /* + |-------------------------------------------------------------------------- + | Base ViewModel + |-------------------------------------------------------------------------- + | + | The base class which generated view models should extend. By default, + | generated domain models will extend `Domain\Shared\ViewModels\BaseViewModel`, + | which will be created if it doesn't already exist. + | + */ + 'base_view_model' => 'Domain\Shared\ViewModels\ViewModel', + + /* + |-------------------------------------------------------------------------- + | Base Action + |-------------------------------------------------------------------------- + | + | The base class which generated action objects should extend. By default, + | generated actions are based on the `lorisleiva/laravel-actions` package + | and do not extend anything. + | + */ + 'base_action' => null, + + /* + |-------------------------------------------------------------------------- + | Autoloading + |-------------------------------------------------------------------------- + | + | Configure whether domain providers, commands, policies, factories, + | and migrations should be auto-discovered and registered. + | + */ + 'autoload' => [ + 'providers' => true, + 'commands' => true, + 'policies' => true, + 'factories' => true, + 'migrations' => 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 + |-------------------------------------------------------------------------- + | + | The folder where the domain cache files will be stored. Used for domain + | autoloading. + | + */ + 'cache_directory' => 'bootstrap/cache/ddd', +]; diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 4fb976b..d62e31b 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -1,31 +1,14 @@ 'src/Domain', - 'ddd.domain_namespace' => 'Domain', - 'ddd.application_namespace' => 'Application', - 'ddd.application_path' => 'src/Application', - 'ddd.application_objects' => [ - 'controller', - 'request', - 'middleware', - ], - 'ddd.layers' => [ - 'Infrastructure' => 'src/Infrastructure', - ], - 'ddd.autoload_ignore' => [ - 'Tests', - 'Database/Migrations', - ], - 'cache.default' => 'file', - ]); + $this->setupTestApplication(); }); afterEach(function () { @@ -33,17 +16,11 @@ }); describe('without autoload', function () { - beforeEach(function () { - Config::set('ddd.autoload.commands', false); - - $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); - }); - - $this->setupTestApplication(); - }); - it('does not register the command', function ($className, $command) { + $this->refreshApplicationWithConfig([ + 'ddd.autoload.commands' => false, + ]); + expect(class_exists($className))->toBeTrue(); expect(fn () => Artisan::call($command))->toThrow(CommandNotFoundException::class); })->with([ @@ -54,19 +31,17 @@ }); describe('with autoload', function () { - beforeEach(function () { - Config::set('ddd.autoload.commands', true); - - $this->setupTestApplication(); - }); - it('registers existing commands', function ($className, $command, $output) { - expect(class_exists($className))->toBeTrue(); - $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); + app('ddd.autoloader')->boot(); }); + $this->refreshApplicationWithConfig([ + 'ddd.autoload.commands' => true, + ]); + + expect(class_exists($className))->toBeTrue(); + expect(collect(Artisan::all())) ->has($command) ->toBeTrue(); @@ -78,45 +53,20 @@ ['Infrastructure\Commands\LogPrune', 'log:prune', 'System logs pruned!'], ['Application\Commands\ApplicationSync', 'application:sync', 'Application state synced!'], ]); - - it('registers newly created commands', function () { - $command = 'app:invoice-void'; - - $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); - }); - - expect(collect(Artisan::all())) - ->has($command) - ->toBeFalse(); - - Artisan::call('ddd:command', [ - 'name' => 'InvoiceVoid', - '--domain' => 'Invoicing', - ]); - - expect(collect(Artisan::all())) - ->has($command) - ->toBeTrue(); - - $this->artisan($command)->assertSuccessful(); - })->skip("Can't get this to work, might not be test-able without a real app environment."); }); describe('caching', function () { - beforeEach(function () { - Config::set('ddd.autoload.commands', true); - - $this->setupTestApplication(); - }); - it('remembers the last cached state', function () { DomainCache::set('domain-commands', []); $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); + app('ddd.autoloader')->boot(); }); + $this->refreshApplicationWithConfig([ + 'ddd.autoload.commands' => true, + ]); + // commands should not be recognized due to cached empty-state expect(fn () => Artisan::call('invoice:deliver'))->toThrow(CommandNotFoundException::class); expect(fn () => Artisan::call('log:prune'))->toThrow(CommandNotFoundException::class); @@ -128,9 +78,13 @@ DomainCache::clear(); $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); + app('ddd.autoloader')->boot(); }); + $this->refreshApplicationWithConfig([ + 'ddd.autoload.commands' => true, + ]); + $this->artisan('invoice:deliver')->assertSuccessful(); $this->artisan('log:prune')->assertSuccessful(); $this->artisan('application:sync')->assertSuccessful(); diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index f496b38..79a4a4e 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -2,7 +2,6 @@ use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Config; -use Lunarstorm\LaravelDDD\Support\DomainAutoloader; beforeEach(function () { $this->setupTestApplication(); @@ -15,7 +14,7 @@ Config::set('ddd.autoload.factories', true); $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); + app('ddd.autoloader')->boot(); }); }); @@ -52,7 +51,7 @@ Config::set('ddd.autoload.factories', false); $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); + app('ddd.autoloader')->boot(); }); }); diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index f7c82fd..183147b 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -5,29 +5,12 @@ use Illuminate\Support\Str; use Lunarstorm\LaravelDDD\Facades\DDD; use Lunarstorm\LaravelDDD\Support\DomainCache; +use Lunarstorm\LaravelDDD\Tests\BootsTestApplication; use Symfony\Component\Finder\SplFileInfo; -beforeEach(function () { - Config::set([ - 'ddd.domain_path' => 'src/Domain', - 'ddd.domain_namespace' => 'Domain', - 'ddd.application_namespace' => 'Application', - 'ddd.application_path' => 'src/Application', - 'ddd.application_objects' => [ - 'controller', - 'request', - 'middleware', - ], - 'ddd.layers' => [ - 'Infrastructure' => 'src/Infrastructure', - ], - 'ddd.autoload_ignore' => [ - 'Tests', - 'Database/Migrations', - ], - 'cache.default' => 'file', - ]); +uses(BootsTestApplication::class); +beforeEach(function () { $this->setupTestApplication(); }); diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index a9e560e..168ab4c 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -1,35 +1,12 @@ setupTestApplication(); - - Config::set([ - 'ddd.domain_path' => 'src/Domain', - 'ddd.domain_namespace' => 'Domain', - 'ddd.application_namespace' => 'Application', - 'ddd.application_path' => 'src/Application', - 'ddd.application_objects' => [ - 'controller', - 'request', - 'middleware', - ], - 'ddd.layers' => [ - 'Infrastructure' => 'src/Infrastructure', - ], - 'ddd.autoload_ignore' => [ - 'Tests', - 'Database/Migrations', - ], - 'cache.default' => 'file', - ]); - - $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); - }); }); it('can autoload policy', function ($class, $expectedPolicy) { diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 73c8ccb..57bcacd 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -1,102 +1,116 @@ 'src/Domain', - 'ddd.domain_namespace' => 'Domain', - 'ddd.application_namespace' => 'Application', - 'ddd.application_path' => 'src/Application', - 'ddd.application_objects' => [ - 'controller', - 'request', - 'middleware', - ], - 'ddd.layers' => [ - 'Infrastructure' => 'src/Infrastructure', - ], - 'ddd.autoload_ignore' => [ - 'Tests', - 'Database/Migrations', - ], - 'cache.default' => 'file', - ]); + // $this->refreshApplicationWithConfig([ + // 'ddd.domain_path' => 'src/Domain', + // 'ddd.domain_namespace' => 'Domain', + // 'ddd.application_namespace' => 'Application', + // 'ddd.application_path' => 'src/Application', + // 'ddd.application_objects' => [ + // 'controller', + // 'request', + // 'middleware', + // ], + // 'ddd.layers' => [ + // 'Infrastructure' => 'src/Infrastructure', + // ], + // 'ddd.autoload_ignore' => [ + // 'Tests', + // 'Database/Migrations', + // ], + // 'cache.default' => 'file', + // ]); $this->setupTestApplication(); }); -afterEach(function () { - $this->setupTestApplication(); -}); +// afterEach(function () { +// $this->setupTestApplication(); +// }); describe('without autoload', function () { - beforeEach(function () { - config([ + it('does not register the provider', function ($binding) { + // setConfigValues([ + // 'ddd.autoload.providers' => false, + // ]); + + $this->afterApplicationRefreshed(function () { + app('ddd.autoloader')->boot(); + }); + + $this->refreshApplicationWithConfig([ 'ddd.autoload.providers' => false, ]); - (new DomainAutoloader)->autoload(); - }); + expect(DDD::autoloader()->getDiscoveredProviders())->toBeEmpty(); - it('does not register the provider', function () { - expect(fn () => app('invoicing'))->toThrow(Exception::class); - }); + expect(fn () => app($binding))->toThrow(Exception::class); + })->with([ + ['invoicing'], + ['application-layer'], + ['infrastructure-layer'], + ]); }); describe('with autoload', function () { - beforeEach(function () { - config([ - 'ddd.autoload.providers' => true, - ]); - }); - it('registers the provider in domain layer', function () { $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); + app('ddd.autoloader')->boot(); }); + $this->refreshApplicationWithConfig([ + 'ddd.autoload.providers' => true, + ]); + expect(app('invoicing'))->toEqual('invoicing-singleton'); $this->artisan('invoice:deliver')->expectsOutputToContain('invoice-secret'); }); it('registers the provider in application layer', function () { $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); + app('ddd.autoloader')->boot(); }); + $this->refreshApplicationWithConfig([ + 'ddd.autoload.providers' => true, + ]); + expect(app('application-layer'))->toEqual('application-layer-singleton'); $this->artisan('application:sync')->expectsOutputToContain('application-secret'); }); it('registers the provider in custom layer', function () { $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); + app('ddd.autoloader')->boot(); }); + $this->refreshApplicationWithConfig([ + 'ddd.autoload.providers' => true, + ]); + expect(app('infrastructure-layer'))->toEqual('infrastructure-layer-singleton'); $this->artisan('log:prune')->expectsOutputToContain('infrastructure-secret'); }); }); describe('caching', function () { - beforeEach(function () { - config([ - 'ddd.autoload.providers' => true, - ]); - - $this->setupTestApplication(); - }); - it('remembers the last cached state', function () { DomainCache::set('domain-providers', []); $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); + app('ddd.autoloader')->boot(); }); + $this->refreshApplicationWithConfig([ + 'ddd.autoload.providers' => true, + ]); + expect(fn () => app('invoicing'))->toThrow(Exception::class); expect(fn () => app('application-layer'))->toThrow(Exception::class); expect(fn () => app('infrastructure-layer'))->toThrow(Exception::class); @@ -107,9 +121,13 @@ DomainCache::clear(); $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); + app('ddd.autoloader')->boot(); }); + $this->refreshApplicationWithConfig([ + 'ddd.autoload.providers' => true, + ]); + expect(app('invoicing'))->toEqual('invoicing-singleton'); $this->artisan('invoice:deliver')->expectsOutputToContain('invoice-secret'); diff --git a/tests/AutoloadingTest.php b/tests/AutoloadingTest.php new file mode 100644 index 0000000..8ed1eab --- /dev/null +++ b/tests/AutoloadingTest.php @@ -0,0 +1,40 @@ + 'src/Domain', + // 'ddd.domain_namespace' => 'Domain', + // 'ddd.application_namespace' => 'Application', + // 'ddd.application_path' => 'src/Application', + // 'ddd.application_objects' => [ + // 'controller', + // 'request', + // 'middleware', + // ], + // 'ddd.layers' => [ + // 'Infrastructure' => 'src/Infrastructure', + // ], + // 'ddd.autoload_ignore' => [ + // 'Tests', + // 'Database/Migrations', + // ], + // 'cache.default' => 'file', + // ...static::$configValues, + // ]; + + // tap($app['config'], function (Repository $config) { + // foreach (static::$configValues as $key => $value) { + // $config->set($key, $value); + // } + // }); + // } +} diff --git a/tests/BootsTestApplication.php b/tests/BootsTestApplication.php new file mode 100644 index 0000000..d653a3d --- /dev/null +++ b/tests/BootsTestApplication.php @@ -0,0 +1,5 @@ +setupTestApplication(); - Config::set([ - 'ddd.domain_path' => 'src/Domain', - 'ddd.domain_namespace' => 'Domain', - 'ddd.application_namespace' => 'Application', - 'ddd.application_path' => 'src/Application', - 'ddd.application_objects' => [ - 'controller', - 'request', - 'middleware', - ], - 'ddd.layers' => [ - 'Infrastructure' => 'src/Infrastructure', - ], - 'ddd.autoload_ignore' => [ - 'Tests', - 'Database/Migrations', - ], - 'cache.default' => 'file', - ]); - $this->artisan('optimize:clear')->execute(); DomainCache::clear(); @@ -45,10 +26,6 @@ expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); - // $this->afterApplicationCreated(function () { - // (new DomainAutoloader)->autoload(); - // }); - $this ->artisan('ddd:optimize') ->expectsOutputToContain('Caching DDD providers, commands, migration paths.') @@ -113,8 +90,6 @@ describe('laravel optimize', function () { test('optimize will include ddd:optimize', function () { - config(['cache.default' => 'file']); - expect(DomainCache::get('domain-providers'))->toBeNull(); expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); @@ -127,8 +102,6 @@ }); test('optimize:clear will clear ddd cache', function () { - config(['cache.default' => 'file']); - $this->artisan('ddd:optimize')->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); diff --git a/tests/Factory/DomainFactoryTest.php b/tests/Factory/DomainFactoryTest.php index 82e6e54..78f76a7 100644 --- a/tests/Factory/DomainFactoryTest.php +++ b/tests/Factory/DomainFactoryTest.php @@ -5,7 +5,6 @@ use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Config; use Lunarstorm\LaravelDDD\Factories\DomainFactory; -use Lunarstorm\LaravelDDD\Support\DomainAutoloader; it('can resolve the factory name of a domain model', function ($modelClass, $expectedFactoryClass) { $this->setupTestApplication(); @@ -29,7 +28,7 @@ it('can instantiate a domain model factory', function ($domainParameter, $modelName, $modelClass) { $this->afterApplicationCreated(function () { - (new DomainAutoloader)->autoload(); + app('ddd.autoloader')->boot(); }); $this->setupTestApplication(); diff --git a/tests/Generator/Model/MakeWithControllerTest.php b/tests/Generator/Model/MakeWithControllerTest.php index c5f1077..2418ad3 100644 --- a/tests/Generator/Model/MakeWithControllerTest.php +++ b/tests/Generator/Model/MakeWithControllerTest.php @@ -1,15 +1,21 @@ 'src/Domain', + 'ddd.domain_namespace' => 'Domain', + 'ddd.base_model' => DomainModel::class, + 'ddd.application_namespace' => 'App\Modules', + 'ddd.application_path' => 'app/Modules', + 'ddd.application_objects' => [ + 'controller', + ], + ]); $this->setupTestApplication(); }); diff --git a/tests/Support/AutoloaderTest.php b/tests/Support/AutoloaderTest.php index aec470b..2ee1ddf 100644 --- a/tests/Support/AutoloaderTest.php +++ b/tests/Support/AutoloaderTest.php @@ -1,13 +1,53 @@ setupTestApplication(); }); it('can run', function () { - $autoloader = new DomainAutoloader; + $autoloader = new Autoloader; - $autoloader->autoload(); + $autoloader->boot(); })->throwsNoExceptions(); + +beforeEach(function () { + config([ + 'ddd.domain_path' => 'src/Domain', + 'ddd.domain_namespace' => 'Domain', + 'ddd.application_namespace' => 'Application', + 'ddd.application_path' => 'src/Application', + 'ddd.application_objects' => [ + 'controller', + 'request', + 'middleware', + ], + 'ddd.layers' => [ + 'Infrastructure' => 'src/Infrastructure', + 'Support' => 'src/Support', + 'Library' => 'lib', + ], + 'ddd.autoload_ignore' => [ + 'Tests', + 'Database/Migrations', + ], + 'cache.default' => 'file', + ]); + + $this->setupTestApplication(); +}); + +it('can discover paths to all layers', function () { + $autoloader = new Autoloader; + + $expected = [ + app()->basePath('src/Domain'), + app()->basePath('src/Application'), + app()->basePath('src/Infrastructure'), + app()->basePath('src/Support'), + app()->basePath('lib'), + ]; + + expect($autoloader->getAllLayerPaths())->toEqualCanonicalizing($expected); +}); diff --git a/tests/TestCase.php b/tests/TestCase.php index 85e74a6..5fdb780 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -14,6 +14,8 @@ class TestCase extends Orchestra { public static $configValues = []; + public $appConfig = []; + protected function setUp(): void { $this->afterApplicationCreated(function () { @@ -36,15 +38,67 @@ public static function configValues(array $values) static::$configValues = $values; } + public static function resetConfig() + { + static::$configValues = []; + } + + protected function defineConfigBeforeEnvironment() {} + protected function defineEnvironment($app) { + if (in_array(BootsTestApplication::class, class_uses_recursive($this))) { + static::$configValues = [ + 'ddd.domain_path' => 'src/Domain', + 'ddd.domain_namespace' => 'Domain', + 'ddd.application_namespace' => 'Application', + 'ddd.application_path' => 'src/Application', + 'ddd.application_objects' => [ + 'controller', + 'request', + 'middleware', + ], + 'ddd.layers' => [ + 'Infrastructure' => 'src/Infrastructure', + ], + 'ddd.autoload_ignore' => [ + 'Tests', + 'Database/Migrations', + ], + 'cache.default' => 'file', + ...static::$configValues, + ]; + } + tap($app['config'], function (Repository $config) { foreach (static::$configValues as $key => $value) { $config->set($key, $value); } + + foreach ($this->appConfig as $key => $value) { + $config->set($key, $value); + } }); } + protected function refreshApplicationWithConfig(array $config) + { + $this->appConfig = $config; + + $this->refreshApplication(); + + $this->afterApplicationRefreshed(fn () => $this->appConfig = []); + + return $this; + } + + protected function withConfig(array $config) + { + $this->appConfig = $config; + + return $this; + } + protected function getComposerFileContents() { return file_get_contents(base_path('composer.json')); @@ -161,6 +215,7 @@ protected function setupTestApplication() File::copyDirectory(__DIR__.'/.skeleton/database', base_path('database')); File::copyDirectory(__DIR__.'/.skeleton/src', base_path('src')); File::copy(__DIR__.'/.skeleton/bootstrap/providers.php', base_path('bootstrap/providers.php')); + // File::copy(__DIR__ . '/.skeleton/config/ddd.php', config_path('ddd.php')); $this->setAutoloadPathInComposer('Domain', 'src/Domain'); $this->setAutoloadPathInComposer('Application', 'src/Application'); From f38f9601155b1f137ba349607b8627b2f6014f67 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 17:50:57 -0500 Subject: [PATCH 071/169] Prevent autoloader from booting more than once. --- src/Support/Autoloader.php | 21 ++++++++------------- tests/Autoload/CommandTest.php | 7 ++++++- tests/Autoload/FactoryTest.php | 22 +++++++++++++--------- tests/Autoload/ProviderTest.php | 4 ++-- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/Support/Autoloader.php b/src/Support/Autoloader.php index c874e93..84d3935 100644 --- a/src/Support/Autoloader.php +++ b/src/Support/Autoloader.php @@ -30,6 +30,8 @@ class Autoloader protected array $discoveredProviders = []; + protected bool $isBooted = false; + public function __construct() { $this->appNamespace = $this->resolveAppNamespace(); @@ -37,17 +39,13 @@ public function __construct() public function boot(): void { - if (! config()->has('ddd.autoload')) { + if ($this->isBooted) { return; } - // if ($discoveredCommands = $this->getDiscoveredCommands()) { - // dump('Commands were already discovered', $discoveredCommands); - // } - - // if ($discoveredProviders = $this->getDiscoveredProviders()) { - // dump('Providers were already discovered', $discoveredProviders); - // } + if (! config()->has('ddd.autoload')) { + return; + } $this ->handleProviders() @@ -55,6 +53,8 @@ public function boot(): void ->when(config('ddd.autoload.commands') === true, fn ($autoloader) => $autoloader->handleCommands()) ->when(config('ddd.autoload.policies') === true, fn ($autoloader) => $autoloader->handlePolicies()) ->when(config('ddd.autoload.factories') === true, fn ($autoloader) => $autoloader->handleFactories()); + + $this->isBooted = true; } protected function normalizePaths($path): array @@ -251,11 +251,6 @@ public function cacheCommands() return $this; } - protected function flush() - { - return $this; - } - protected function resolveAppNamespace() { try { diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index d62e31b..55dc36b 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -9,6 +9,7 @@ beforeEach(function () { $this->setupTestApplication(); + DomainCache::clear(); }); afterEach(function () { @@ -17,6 +18,10 @@ describe('without autoload', function () { it('does not register the command', function ($className, $command) { + $this->afterApplicationRefreshed(function () { + app('ddd.autoloader')->boot(); + }); + $this->refreshApplicationWithConfig([ 'ddd.autoload.commands' => false, ]); @@ -32,7 +37,7 @@ describe('with autoload', function () { it('registers existing commands', function ($className, $command, $output) { - $this->afterApplicationCreated(function () { + $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index 79a4a4e..6b6543c 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -1,21 +1,23 @@ setupTestApplication(); - - Config::set('ddd.domain_namespace', 'Domain'); }); describe('autoload enabled', function () { beforeEach(function () { - Config::set('ddd.autoload.factories', true); - - $this->afterApplicationCreated(function () { + $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); + + $this->refreshApplicationWithConfig([ + 'ddd.autoload.factories' => true, + ]); }); it('can resolve domain factory', function ($modelClass, $expectedFactoryClass) { @@ -48,11 +50,13 @@ describe('autoload disabled', function () { beforeEach(function () { - Config::set('ddd.autoload.factories', false); - - $this->afterApplicationCreated(function () { + $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); + + $this->refreshApplicationWithConfig([ + 'ddd.autoload.factories' => false, + ]); }); it('cannot resolve factories that rely on autoloading', function ($modelClass) { diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 57bcacd..6b18e18 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -60,7 +60,7 @@ describe('with autoload', function () { it('registers the provider in domain layer', function () { - $this->afterApplicationCreated(function () { + $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); @@ -73,7 +73,7 @@ }); it('registers the provider in application layer', function () { - $this->afterApplicationCreated(function () { + $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); From b700cf0c5c824b70164fa9401c2f87f149e338d8 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 17:57:30 -0500 Subject: [PATCH 072/169] Attempt fix --- tests/Autoload/ProviderTest.php | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 6b18e18..86978c4 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -7,32 +7,13 @@ uses(BootsTestApplication::class); beforeEach(function () { - // $this->refreshApplicationWithConfig([ - // 'ddd.domain_path' => 'src/Domain', - // 'ddd.domain_namespace' => 'Domain', - // 'ddd.application_namespace' => 'Application', - // 'ddd.application_path' => 'src/Application', - // 'ddd.application_objects' => [ - // 'controller', - // 'request', - // 'middleware', - // ], - // 'ddd.layers' => [ - // 'Infrastructure' => 'src/Infrastructure', - // ], - // 'ddd.autoload_ignore' => [ - // 'Tests', - // 'Database/Migrations', - // ], - // 'cache.default' => 'file', - // ]); - $this->setupTestApplication(); + DomainCache::clear(); }); -// afterEach(function () { -// $this->setupTestApplication(); -// }); +afterEach(function () { + DomainCache::clear(); +}); describe('without autoload', function () { it('does not register the provider', function ($binding) { @@ -86,7 +67,7 @@ }); it('registers the provider in custom layer', function () { - $this->afterApplicationCreated(function () { + $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); @@ -103,7 +84,7 @@ it('remembers the last cached state', function () { DomainCache::set('domain-providers', []); - $this->afterApplicationCreated(function () { + $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); @@ -120,7 +101,7 @@ DomainCache::set('domain-providers', []); DomainCache::clear(); - $this->afterApplicationCreated(function () { + $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); From e9fd489c6e30ec83035be0f1afb1a937fcdfb3e4 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 18:05:13 -0500 Subject: [PATCH 073/169] Tweak factory test. --- tests/Autoload/ProviderTest.php | 4 ---- tests/Factory/DomainFactoryTest.php | 14 ++++++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 86978c4..26ea3d8 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -17,10 +17,6 @@ describe('without autoload', function () { it('does not register the provider', function ($binding) { - // setConfigValues([ - // 'ddd.autoload.providers' => false, - // ]); - $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); diff --git a/tests/Factory/DomainFactoryTest.php b/tests/Factory/DomainFactoryTest.php index 78f76a7..217ad26 100644 --- a/tests/Factory/DomainFactoryTest.php +++ b/tests/Factory/DomainFactoryTest.php @@ -3,8 +3,10 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Artisan; -use Illuminate\Support\Facades\Config; use Lunarstorm\LaravelDDD\Factories\DomainFactory; +use Lunarstorm\LaravelDDD\Tests\BootsTestApplication; + +uses(BootsTestApplication::class); it('can resolve the factory name of a domain model', function ($modelClass, $expectedFactoryClass) { $this->setupTestApplication(); @@ -27,13 +29,17 @@ ]); it('can instantiate a domain model factory', function ($domainParameter, $modelName, $modelClass) { - $this->afterApplicationCreated(function () { + $this->setupTestApplication(); + + $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); - $this->setupTestApplication(); + $this->refreshApplicationWithConfig([ + 'ddd.base_model' => 'Lunarstorm\LaravelDDD\Models\DomainModel', + 'ddd.autoload.factories' => true, + ]); - Config::set('ddd.base_model', 'Lunarstorm\LaravelDDD\Models\DomainModel'); Artisan::call("ddd:model -f {$domainParameter}:{$modelName}"); expect(class_exists($modelClass))->toBeTrue(); From 26fb0110cb70ad86bc7b27c5b45f898fa4afcf46 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 18:14:10 -0500 Subject: [PATCH 074/169] use afterApplicationRefreshed --- src/Support/Autoloader.php | 1 + tests/Autoload/CommandTest.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Support/Autoloader.php b/src/Support/Autoloader.php index 84d3935..74e4215 100644 --- a/src/Support/Autoloader.php +++ b/src/Support/Autoloader.php @@ -40,6 +40,7 @@ public function __construct() public function boot(): void { if ($this->isBooted) { + // dump('Autoloader Already booted'); return; } diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 55dc36b..6b237d1 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -64,7 +64,7 @@ it('remembers the last cached state', function () { DomainCache::set('domain-commands', []); - $this->afterApplicationCreated(function () { + $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); @@ -82,7 +82,7 @@ DomainCache::set('domain-commands', []); DomainCache::clear(); - $this->afterApplicationCreated(function () { + $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); From 52c7bee3c25b8d9b3bd94e85a88a60ad2e5c9252 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 18:21:16 -0500 Subject: [PATCH 075/169] Fix autoload boot clauses. --- src/LaravelDDDServiceProvider.php | 3 ++- src/Support/Autoloader.php | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index 323c4ba..077fc7e 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -3,6 +3,7 @@ namespace Lunarstorm\LaravelDDD; use Illuminate\Database\Migrations\MigrationCreator; +use Lunarstorm\LaravelDDD\Facades\DDD; use Lunarstorm\LaravelDDD\Support\Autoloader; use Lunarstorm\LaravelDDD\Support\DomainMigration; use Spatie\LaravelPackageTools\Package; @@ -137,7 +138,7 @@ public function packageBooted() // 'package booted' => config('ddd') // ]); - app('ddd.autoloader')->boot(); + DDD::autoloader()->boot(); } public function packageRegistered() diff --git a/src/Support/Autoloader.php b/src/Support/Autoloader.php index 74e4215..86884fc 100644 --- a/src/Support/Autoloader.php +++ b/src/Support/Autoloader.php @@ -50,8 +50,7 @@ public function boot(): void $this ->handleProviders() - ->when(app()->runningInConsole(), fn ($autoloader) => $autoloader->handleProviders()) - ->when(config('ddd.autoload.commands') === true, fn ($autoloader) => $autoloader->handleCommands()) + ->when(app()->runningInConsole() && config('ddd.autoload.commands') === true, fn ($autoloader) => $autoloader->handleCommands()) ->when(config('ddd.autoload.policies') === true, fn ($autoloader) => $autoloader->handlePolicies()) ->when(config('ddd.autoload.factories') === true, fn ($autoloader) => $autoloader->handleFactories()); From a5aa30d86d0e7fbe232c12db0fec5c22016c5d33 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 18:25:34 -0500 Subject: [PATCH 076/169] Handle providers only when true in config --- src/LaravelDDDServiceProvider.php | 4 ---- src/Support/Autoloader.php | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index 077fc7e..46fe9b9 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -134,10 +134,6 @@ public function packageBooted() ); } - // dump([ - // 'package booted' => config('ddd') - // ]); - DDD::autoloader()->boot(); } diff --git a/src/Support/Autoloader.php b/src/Support/Autoloader.php index 86884fc..c25469a 100644 --- a/src/Support/Autoloader.php +++ b/src/Support/Autoloader.php @@ -49,7 +49,7 @@ public function boot(): void } $this - ->handleProviders() + ->when(config('ddd.autoload.providers') === true, fn ($autoloader) => $autoloader->handleProviders()) ->when(app()->runningInConsole() && config('ddd.autoload.commands') === true, fn ($autoloader) => $autoloader->handleCommands()) ->when(config('ddd.autoload.policies') === true, fn ($autoloader) => $autoloader->handlePolicies()) ->when(config('ddd.autoload.factories') === true, fn ($autoloader) => $autoloader->handleFactories()); From e32230b5cbf9637eb9100601dd6fe472ef4ad515 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 18:31:35 -0500 Subject: [PATCH 077/169] Enable autoload flags with test app config. --- tests/TestCase.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/TestCase.php b/tests/TestCase.php index 5fdb780..24a2242 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -61,6 +61,13 @@ protected function defineEnvironment($app) 'ddd.layers' => [ 'Infrastructure' => 'src/Infrastructure', ], + 'ddd.autoload' => [ + 'providers' => true, + 'commands' => true, + 'policies' => true, + 'factories' => true, + 'migrations' => true, + ], 'ddd.autoload_ignore' => [ 'Tests', 'Database/Migrations', From 13892faf8698e0c4d34d318065ff0350bb77404a Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 18:40:30 -0500 Subject: [PATCH 078/169] Skip autoload tests to see outcome. --- src/Support/Autoloader.php | 8 ++++---- tests/Autoload/CommandTest.php | 2 +- tests/Autoload/FactoryTest.php | 6 ++---- tests/Autoload/IgnoreTest.php | 2 +- tests/Autoload/PolicyTest.php | 2 +- tests/Autoload/ProviderTest.php | 2 +- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Support/Autoloader.php b/src/Support/Autoloader.php index c25469a..a1ffa4c 100644 --- a/src/Support/Autoloader.php +++ b/src/Support/Autoloader.php @@ -39,10 +39,10 @@ public function __construct() public function boot(): void { - if ($this->isBooted) { - // dump('Autoloader Already booted'); - return; - } + // if ($this->isBooted) { + // // dump('Autoloader Already booted'); + // return; + // } if (! config()->has('ddd.autoload')) { return; diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 6b237d1..10f1d27 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -10,7 +10,7 @@ beforeEach(function () { $this->setupTestApplication(); DomainCache::clear(); -}); +})->skip(); afterEach(function () { DomainCache::clear(); diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index 6b6543c..4cb27ac 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -7,7 +7,7 @@ beforeEach(function () { $this->setupTestApplication(); -}); +})->skip(); describe('autoload enabled', function () { beforeEach(function () { @@ -49,7 +49,7 @@ }); describe('autoload disabled', function () { - beforeEach(function () { + it('cannot resolve factories that rely on autoloading', function ($modelClass) { $this->afterApplicationRefreshed(function () { app('ddd.autoloader')->boot(); }); @@ -57,9 +57,7 @@ $this->refreshApplicationWithConfig([ 'ddd.autoload.factories' => false, ]); - }); - it('cannot resolve factories that rely on autoloading', function ($modelClass) { expect(fn () => $modelClass::factory())->toThrow(Error::class); })->with([ ['Domain\Invoicing\Models\VanillaModel'], diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 183147b..0b2fafe 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -12,7 +12,7 @@ beforeEach(function () { $this->setupTestApplication(); -}); +})->skip(); it('can ignore folders when autoloading', function () { Artisan::call('ddd:optimize'); diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index 168ab4c..5483148 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -7,7 +7,7 @@ beforeEach(function () { $this->setupTestApplication(); -}); +})->skip(); it('can autoload policy', function ($class, $expectedPolicy) { expect(class_exists($class))->toBeTrue(); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 26ea3d8..68aa606 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -9,7 +9,7 @@ beforeEach(function () { $this->setupTestApplication(); DomainCache::clear(); -}); +})->skip(); afterEach(function () { DomainCache::clear(); From 6cd00c4386338cec137a1dd6a706dafdbcaf210a Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 18:43:03 -0500 Subject: [PATCH 079/169] Skip describe blocks. --- tests/Autoload/CommandTest.php | 8 ++++---- tests/Autoload/FactoryTest.php | 6 +++--- tests/Autoload/ProviderTest.php | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 10f1d27..4dc9563 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -10,7 +10,7 @@ beforeEach(function () { $this->setupTestApplication(); DomainCache::clear(); -})->skip(); +}); afterEach(function () { DomainCache::clear(); @@ -33,7 +33,7 @@ ['Infrastructure\Commands\LogPrune', 'log:prune'], ['Application\Commands\ApplicationSync', 'application:sync'], ]); -}); +})->skip(); describe('with autoload', function () { it('registers existing commands', function ($className, $command, $output) { @@ -58,7 +58,7 @@ ['Infrastructure\Commands\LogPrune', 'log:prune', 'System logs pruned!'], ['Application\Commands\ApplicationSync', 'application:sync', 'Application state synced!'], ]); -}); +})->skip(); describe('caching', function () { it('remembers the last cached state', function () { @@ -94,4 +94,4 @@ $this->artisan('log:prune')->assertSuccessful(); $this->artisan('application:sync')->assertSuccessful(); }); -}); +})->skip(); diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index 4cb27ac..45efa92 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -7,7 +7,7 @@ beforeEach(function () { $this->setupTestApplication(); -})->skip(); +}); describe('autoload enabled', function () { beforeEach(function () { @@ -46,7 +46,7 @@ expect($modelClass::factory()) ->toBeInstanceOf('Database\Factories\RegularModelFactory'); }); -}); +})->skip(); describe('autoload disabled', function () { it('cannot resolve factories that rely on autoloading', function ($modelClass) { @@ -63,4 +63,4 @@ ['Domain\Invoicing\Models\VanillaModel'], ['Domain\Internal\Reporting\Models\Report'], ]); -}); +})->skip(); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 68aa606..982bc3d 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -9,7 +9,7 @@ beforeEach(function () { $this->setupTestApplication(); DomainCache::clear(); -})->skip(); +}); afterEach(function () { DomainCache::clear(); @@ -33,7 +33,7 @@ ['application-layer'], ['infrastructure-layer'], ]); -}); +})->skip(); describe('with autoload', function () { it('registers the provider in domain layer', function () { @@ -74,7 +74,7 @@ expect(app('infrastructure-layer'))->toEqual('infrastructure-layer-singleton'); $this->artisan('log:prune')->expectsOutputToContain('infrastructure-secret'); }); -}); +})->skip(); describe('caching', function () { it('remembers the last cached state', function () { @@ -114,4 +114,4 @@ expect(app('infrastructure-layer'))->toEqual('infrastructure-layer-singleton'); $this->artisan('log:prune')->expectsOutputToContain('infrastructure-secret'); }); -}); +})->skip(); From e3f72adef1c6b6d03b404f8728eb891f32f0fbbb Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 14 Nov 2024 22:36:55 -0500 Subject: [PATCH 080/169] Refactor some tests. --- src/Support/Autoloader.php | 64 ++++++++++++++++++++------------- tests/Autoload/CommandTest.php | 35 ++++++++++-------- tests/Autoload/ProviderTest.php | 3 -- tests/Command/OptimizeTest.php | 2 +- tests/TestCase.php | 6 ++-- 5 files changed, 65 insertions(+), 45 deletions(-) diff --git a/src/Support/Autoloader.php b/src/Support/Autoloader.php index a1ffa4c..479adf5 100644 --- a/src/Support/Autoloader.php +++ b/src/Support/Autoloader.php @@ -26,9 +26,9 @@ class Autoloader protected string $appNamespace; - protected array $discoveredCommands = []; + protected array $registeredCommands = []; - protected array $discoveredProviders = []; + protected array $registeredProviders = []; protected bool $isBooted = false; @@ -37,26 +37,37 @@ public function __construct() $this->appNamespace = $this->resolveAppNamespace(); } - public function boot(): void + public function boot() { - // if ($this->isBooted) { - // // dump('Autoloader Already booted'); - // return; - // } - if (! config()->has('ddd.autoload')) { - return; + return $this; } + // if ($this->isBooted) { + // return $this; + // } + $this - ->when(config('ddd.autoload.providers') === true, fn ($autoloader) => $autoloader->handleProviders()) - ->when(app()->runningInConsole() && config('ddd.autoload.commands') === true, fn ($autoloader) => $autoloader->handleCommands()) - ->when(config('ddd.autoload.policies') === true, fn ($autoloader) => $autoloader->handlePolicies()) - ->when(config('ddd.autoload.factories') === true, fn ($autoloader) => $autoloader->handleFactories()); + ->when(config('ddd.autoload.providers') === true, fn () => $this->handleProviders()) + ->when(app()->runningInConsole() && config('ddd.autoload.commands') === true, fn () => $this->handleCommands()) + ->when(config('ddd.autoload.policies') === true, fn () => $this->handlePolicies()) + ->when(config('ddd.autoload.factories') === true, fn () => $this->handleFactories()); + + if (app()->runningInConsole()) { + ConsoleApplication::starting(function ($artisan) { + foreach ($this->registeredCommands as $command) { + $artisan->resolve($command); + } + }); + } $this->isBooted = true; + + return $this; } + public function run() {} + protected function normalizePaths($path): array { return collect($path) @@ -86,8 +97,10 @@ protected function handleProviders() ? DomainCache::get('domain-providers') : $this->discoverProviders(); + $this->registeredProviders = []; + foreach ($providers as $provider) { - $this->discoveredProviders[$provider] = $provider; + $this->registeredProviders[$provider] = $provider; app()->register($provider); } @@ -100,29 +113,32 @@ protected function handleCommands() ? DomainCache::get('domain-commands') : $this->discoverCommands(); + $this->registeredCommands = []; + foreach ($commands as $command) { - $this->discoveredCommands[$command] = $command; + $this->registeredCommands[$command] = $command; $this->registerCommand($command); } return $this; } - public function getDiscoveredCommands(): array + public function getRegisteredCommands(): array { - return $this->discoveredCommands; + return $this->registeredCommands; } - public function getDiscoveredProviders(): array + public function getRegisteredProviders(): array { - return $this->discoveredProviders; + return $this->registeredProviders; } protected function registerCommand($class) { - ConsoleApplication::starting(function ($artisan) use ($class) { - $artisan->resolve($class); - }); + // ConsoleApplication::starting(function ($artisan) use ($class) { + // dump('resolving command', $class, $this->registeredCommands); + // $artisan->resolve($class); + // }); } protected function handlePolicies() @@ -222,8 +238,8 @@ protected function discoverCommands(): array } $paths = $this->normalizePaths( - $configValue === true ? - $this->getAllLayerPaths() + $configValue === true + ? $this->getAllLayerPaths() : $configValue ); diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 4dc9563..e446820 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -7,21 +7,21 @@ uses(BootsTestApplication::class); -beforeEach(function () { - $this->setupTestApplication(); - DomainCache::clear(); -}); - afterEach(function () { DomainCache::clear(); }); describe('without autoload', function () { - it('does not register the command', function ($className, $command) { + beforeEach(function () { + DomainCache::clear(); + $this->afterApplicationRefreshed(function () { + $this->setupTestApplication(); app('ddd.autoloader')->boot(); }); + }); + it('does not register the command', function ($className, $command) { $this->refreshApplicationWithConfig([ 'ddd.autoload.commands' => false, ]); @@ -33,14 +33,19 @@ ['Infrastructure\Commands\LogPrune', 'log:prune'], ['Application\Commands\ApplicationSync', 'application:sync'], ]); -})->skip(); +}); describe('with autoload', function () { - it('registers existing commands', function ($className, $command, $output) { + beforeEach(function () { + DomainCache::clear(); + $this->afterApplicationRefreshed(function () { + $this->setupTestApplication(); app('ddd.autoloader')->boot(); }); + }); + it('registers existing commands', function ($className, $command, $output) { $this->refreshApplicationWithConfig([ 'ddd.autoload.commands' => true, ]); @@ -58,13 +63,13 @@ ['Infrastructure\Commands\LogPrune', 'log:prune', 'System logs pruned!'], ['Application\Commands\ApplicationSync', 'application:sync', 'Application state synced!'], ]); -})->skip(); +}); describe('caching', function () { it('remembers the last cached state', function () { - DomainCache::set('domain-commands', []); - $this->afterApplicationRefreshed(function () { + $this->setupTestApplication(); + DomainCache::set('domain-commands', []); app('ddd.autoloader')->boot(); }); @@ -79,10 +84,10 @@ }); it('can bust the cache', function () { - DomainCache::set('domain-commands', []); - DomainCache::clear(); - $this->afterApplicationRefreshed(function () { + $this->setupTestApplication(); + DomainCache::set('domain-commands', []); + DomainCache::clear(); app('ddd.autoloader')->boot(); }); @@ -94,4 +99,4 @@ $this->artisan('log:prune')->assertSuccessful(); $this->artisan('application:sync')->assertSuccessful(); }); -})->skip(); +}); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 982bc3d..01d76a7 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -1,6 +1,5 @@ false, ]); - expect(DDD::autoloader()->getDiscoveredProviders())->toBeEmpty(); - expect(fn () => app($binding))->toThrow(Exception::class); })->with([ ['invoicing'], diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index 11932c8..510c311 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -46,7 +46,7 @@ }); it('can clear the cache', function () { - Artisan::call('ddd:cache'); + Artisan::call('ddd:optimize'); expect(DomainCache::get('domain-providers'))->not->toBeNull(); expect(DomainCache::get('domain-commands'))->not->toBeNull(); diff --git a/tests/TestCase.php b/tests/TestCase.php index 24a2242..da96a0c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -92,9 +92,11 @@ protected function refreshApplicationWithConfig(array $config) { $this->appConfig = $config; - $this->refreshApplication(); + // $this->afterApplicationRefreshed(fn () => $this->appConfig = []); - $this->afterApplicationRefreshed(fn () => $this->appConfig = []); + $this->reloadApplication(); + + $this->appConfig = []; return $this; } From ee5daafed3d8de27baa90b9b8024da545115fe94 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Fri, 15 Nov 2024 18:51:09 -0500 Subject: [PATCH 081/169] WIP: Re-working the autoloading internals. --- composer.json | 2 +- routes/testing.php | 24 +++ src/DomainManager.php | 6 +- src/Facades/Autoload.php | 25 +++ src/Facades/DDD.php | 2 +- src/LaravelDDDServiceProvider.php | 27 ++-- .../{Autoloader.php => AutoloadManager.php} | 113 ++++++++++---- tests/Autoload/CommandTest.php | 143 ++++++++++-------- tests/Autoload/FactoryTest.php | 64 ++++++-- tests/Autoload/IgnoreTest.php | 45 +++--- tests/Autoload/PolicyTest.php | 91 ++++++++--- tests/Autoload/ProviderTest.php | 138 +++++++++-------- tests/Support/AutoloaderTest.php | 6 +- tests/TestCase.php | 2 - 14 files changed, 456 insertions(+), 232 deletions(-) create mode 100644 routes/testing.php create mode 100644 src/Facades/Autoload.php rename src/Support/{Autoloader.php => AutoloadManager.php} (78%) diff --git a/composer.json b/composer.json index 7991560..2344d67 100644 --- a/composer.json +++ b/composer.json @@ -80,7 +80,7 @@ "Lunarstorm\\LaravelDDD\\LaravelDDDServiceProvider" ], "aliases": { - "LaravelDDD": "Lunarstorm\\LaravelDDD\\Facades\\LaravelDDD" + "DDD": "Lunarstorm\\LaravelDDD\\Facades\\DDD" } } }, diff --git a/routes/testing.php b/routes/testing.php new file mode 100644 index 0000000..667d8d9 --- /dev/null +++ b/routes/testing.php @@ -0,0 +1,24 @@ +middleware(['web']) + ->as('ddd.') + ->group(function () { + Route::get('/', function () { + return response('home'); + })->name('home'); + + Route::get('/config', function () { + return response(config('ddd')); + })->name('config'); + + Route::get('/autoload', function () { + return response([ + 'providers' => Autoload::getRegisteredProviders(), + 'commands' => Autoload::getRegisteredCommands(), + ]); + })->name('autoload'); + }); diff --git a/src/DomainManager.php b/src/DomainManager.php index b0f084f..2eb2ea3 100755 --- a/src/DomainManager.php +++ b/src/DomainManager.php @@ -2,7 +2,7 @@ namespace Lunarstorm\LaravelDDD; -use Lunarstorm\LaravelDDD\Support\Autoloader; +use Lunarstorm\LaravelDDD\Support\AutoloadManager; use Lunarstorm\LaravelDDD\Support\GeneratorBlueprint; use Lunarstorm\LaravelDDD\Support\Path; @@ -43,9 +43,9 @@ public function __construct() $this->commandContext = null; } - public function autoloader(): Autoloader + public function autoloader(): AutoloadManager { - return app(Autoloader::class); + return app(AutoloadManager::class); } public function composer(): ComposerManager diff --git a/src/Facades/Autoload.php b/src/Facades/Autoload.php new file mode 100644 index 0000000..055f86d --- /dev/null +++ b/src/Facades/Autoload.php @@ -0,0 +1,25 @@ +app->scoped(Autoloader::class, function () { - return new Autoloader; + $this->app->scoped(AutoloadManager::class, function () { + return new AutoloadManager; }); $this->app->scoped(ComposerManager::class, function () { @@ -34,7 +33,7 @@ public function configurePackage(Package $package): void }); $this->app->bind('ddd', DomainManager::class); - $this->app->bind('ddd.autoloader', Autoloader::class); + $this->app->bind('ddd.autoloader', AutoloadManager::class); $this->app->bind('ddd.config', ConfigManager::class); $this->app->bind('ddd.composer', ComposerManager::class); $this->app->bind('ddd.stubs', StubManager::class); @@ -92,6 +91,10 @@ public function configurePackage(Package $package): void $package->hasCommand(Commands\DomainInterfaceMakeCommand::class); $package->hasCommand(Commands\DomainTraitMakeCommand::class); } + + if ($this->app->runningUnitTests()) { + $package->hasRoutes(['testing']); + } } protected function laravelVersion($value) @@ -111,21 +114,11 @@ protected function registerMigrations() return new Commands\Migration\DomainMigrateMakeCommand($creator, $composer); }); - // $this->app->when(MigrationCreator::class) - // ->needs('$customStubPath') - // ->give(function ($app) { - // return $app->basePath('stubs'); - // }); - $this->loadMigrationsFrom(DomainMigration::paths()); } public function packageBooted() { - // $this->publishes([ - // $this->package->basePath('/../stubs') => $this->app->basePath("stubs/{$this->package->shortName()}"), - // ], "{$this->package->shortName()}-stubs"); - if ($this->app->runningInConsole() && method_exists($this, 'optimizes')) { $this->optimizes( optimize: 'ddd:optimize', @@ -134,7 +127,7 @@ public function packageBooted() ); } - DDD::autoloader()->boot(); + Autoload::boot(); } public function packageRegistered() diff --git a/src/Support/Autoloader.php b/src/Support/AutoloadManager.php similarity index 78% rename from src/Support/Autoloader.php rename to src/Support/AutoloadManager.php index 479adf5..9f16ff8 100644 --- a/src/Support/Autoloader.php +++ b/src/Support/AutoloadManager.php @@ -20,7 +20,7 @@ use Symfony\Component\Finder\SplFileInfo; use Throwable; -class Autoloader +class AutoloadManager { use Conditionable; @@ -30,7 +30,13 @@ class Autoloader protected array $registeredProviders = []; - protected bool $isBooted = false; + protected array $resolvedPolicies = []; + + protected array $resolvedFactories = []; + + protected bool $booted = false; + + protected bool $consoleBooted = false; public function __construct() { @@ -43,31 +49,44 @@ public function boot() return $this; } - // if ($this->isBooted) { - // return $this; - // } - $this + ->flush() ->when(config('ddd.autoload.providers') === true, fn () => $this->handleProviders()) ->when(app()->runningInConsole() && config('ddd.autoload.commands') === true, fn () => $this->handleCommands()) ->when(config('ddd.autoload.policies') === true, fn () => $this->handlePolicies()) - ->when(config('ddd.autoload.factories') === true, fn () => $this->handleFactories()); + ->when(config('ddd.autoload.factories') === true, fn () => $this->handleFactories()) + ->run(); - if (app()->runningInConsole()) { - ConsoleApplication::starting(function ($artisan) { - foreach ($this->registeredCommands as $command) { - $artisan->resolve($command); - } - }); + $this->booted = true; + } + + public function isBooted(): bool + { + return $this->booted; + } + + public function isConsoleBooted(): bool + { + return $this->consoleBooted; + } + + protected function flush() + { + foreach ($this->registeredProviders as $provider) { + app()->forgetInstance($provider); } - $this->isBooted = true; + $this->registeredProviders = []; + + $this->registeredCommands = []; + + $this->resolvedPolicies = []; + + $this->resolvedFactories = []; return $this; } - public function run() {} - protected function normalizePaths($path): array { return collect($path) @@ -97,6 +116,10 @@ protected function handleProviders() ? DomainCache::get('domain-providers') : $this->discoverProviders(); + foreach ($this->registeredProviders as $provider) { + app()->forgetInstance($provider); + } + $this->registeredProviders = []; foreach ($providers as $provider) { @@ -117,7 +140,25 @@ protected function handleCommands() foreach ($commands as $command) { $this->registeredCommands[$command] = $command; - $this->registerCommand($command); + } + + return $this; + } + + protected function run() + { + foreach ($this->registeredProviders as $provider) { + app()->register($provider); + } + + if (app()->runningInConsole() && ! $this->isConsoleBooted()) { + ConsoleApplication::starting(function ($artisan) { + foreach ($this->registeredCommands as $command) { + $artisan->resolve($command); + } + }); + + $this->consoleBooted = true; } return $this; @@ -133,21 +174,31 @@ public function getRegisteredProviders(): array return $this->registeredProviders; } - protected function registerCommand($class) + public function getResolvedPolicies(): array { - // ConsoleApplication::starting(function ($artisan) use ($class) { - // dump('resolving command', $class, $this->registeredCommands); - // $artisan->resolve($class); - // }); + return $this->resolvedPolicies; + } + + public function getResolvedFactories(): array + { + return $this->resolvedFactories; } protected function handlePolicies() { - Gate::guessPolicyNamesUsing(static function (string $class): array|string { + Gate::guessPolicyNamesUsing(function (string $class): array|string { + if (array_key_exists($class, $this->resolvedPolicies)) { + return $this->resolvedPolicies[$class]; + } + if ($model = DomainObject::fromClass($class, 'model')) { - return (new Domain($model->domain)) + $resolved = (new Domain($model->domain)) ->object('policy', "{$model->name}Policy") ->fullyQualifiedName; + + $this->resolvedPolicies[$class] = $resolved; + + return $resolved; } $classDirname = str_replace('/', '\\', dirname(str_replace('\\', '/', $class))); @@ -169,7 +220,13 @@ protected function handlePolicies() protected function handleFactories() { Factory::guessFactoryNamesUsing(function (string $modelName) { + if (array_key_exists($modelName, $this->resolvedFactories)) { + return $this->resolvedFactories[$modelName]; + } + if ($factoryName = DomainFactory::resolveFactoryName($modelName)) { + $this->resolvedFactories[$modelName] = $factoryName; + return $factoryName; } @@ -253,16 +310,16 @@ protected function discoverCommands(): array ->toArray(); } - public function cacheProviders() + public function cacheCommands() { - DomainCache::set('domain-providers', $this->discoverProviders()); + DomainCache::set('domain-commands', $this->discoverCommands()); return $this; } - public function cacheCommands() + public function cacheProviders() { - DomainCache::set('domain-commands', $this->discoverCommands()); + DomainCache::set('domain-providers', $this->discoverProviders()); return $this; } diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index e446820..e57bf73 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -1,102 +1,123 @@ commands = [ + 'invoice:deliver' => 'Domain\Invoicing\Commands\InvoiceDeliver', + 'log:prune' => 'Infrastructure\Commands\LogPrune', + 'application:sync' => 'Application\Commands\ApplicationSync', + ]; + + $this->setupTestApplication(); + DomainCache::clear(); +}); + afterEach(function () { DomainCache::clear(); }); -describe('without autoload', function () { - beforeEach(function () { - DomainCache::clear(); +describe('when ddd.autoload.commands = false', function () { + it('skips handling commands', function () { + config()->set('ddd.autoload.commands', false); - $this->afterApplicationRefreshed(function () { - $this->setupTestApplication(); - app('ddd.autoloader')->boot(); + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldNotReceive('handleCommands'); }); + + $mock->boot(); + + $artisanCommands = collect(Artisan::all()); + + expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); }); - it('does not register the command', function ($className, $command) { - $this->refreshApplicationWithConfig([ - 'ddd.autoload.commands' => false, - ]); - - expect(class_exists($className))->toBeTrue(); - expect(fn () => Artisan::call($command))->toThrow(CommandNotFoundException::class); - })->with([ - ['Domain\Invoicing\Commands\InvoiceDeliver', 'invoice:deliver'], - ['Infrastructure\Commands\LogPrune', 'log:prune'], - ['Application\Commands\ApplicationSync', 'application:sync'], - ]); + it('does not register the commands', function () { + config()->set('ddd.autoload.commands', false); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldNotReceive('handleCommands'); + }); + + $mock->boot(); + + expect($mock->getRegisteredCommands())->toBeEmpty(); + + $artisanCommands = collect(Artisan::all()); + + expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); + }); }); -describe('with autoload', function () { - beforeEach(function () { - DomainCache::clear(); +describe('when ddd.autoload.commands = true', function () { + it('registers the commands', function () { + config()->set('ddd.autoload.commands', true); - $this->afterApplicationRefreshed(function () { - $this->setupTestApplication(); - app('ddd.autoloader')->boot(); + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - }); - it('registers existing commands', function ($className, $command, $output) { - $this->refreshApplicationWithConfig([ - 'ddd.autoload.commands' => true, - ]); + $mock->boot(); - expect(class_exists($className))->toBeTrue(); + expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); - expect(collect(Artisan::all())) - ->has($command) - ->toBeTrue(); + $artisanCommands = collect(Artisan::all()); - Artisan::call($command); - expect(Artisan::output())->toContain($output); - })->with([ - ['Domain\Invoicing\Commands\InvoiceDeliver', 'invoice:deliver', 'Invoice delivered!'], - ['Infrastructure\Commands\LogPrune', 'log:prune', 'System logs pruned!'], - ['Application\Commands\ApplicationSync', 'application:sync', 'Application state synced!'], - ]); + expect($artisanCommands)->toHaveKeys(array_keys($this->commands)); + }); }); describe('caching', function () { it('remembers the last cached state', function () { - $this->afterApplicationRefreshed(function () { - $this->setupTestApplication(); - DomainCache::set('domain-commands', []); - app('ddd.autoloader')->boot(); + DomainCache::set('domain-commands', []); + + config()->set('ddd.autoload.commands', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.commands' => true, - ]); + $mock->boot(); + + expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing([]); + + $artisanCommands = collect(Artisan::all()); + + expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); // commands should not be recognized due to cached empty-state - expect(fn () => Artisan::call('invoice:deliver'))->toThrow(CommandNotFoundException::class); - expect(fn () => Artisan::call('log:prune'))->toThrow(CommandNotFoundException::class); - expect(fn () => Artisan::call('application:sync'))->toThrow(CommandNotFoundException::class); + foreach ($this->commands as $command => $class) { + expect(fn () => Artisan::call($command))->toThrow(CommandNotFoundException::class); + } }); it('can bust the cache', function () { - $this->afterApplicationRefreshed(function () { - $this->setupTestApplication(); - DomainCache::set('domain-commands', []); - DomainCache::clear(); - app('ddd.autoloader')->boot(); + DomainCache::set('domain-commands', []); + DomainCache::clear(); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.commands' => true, - ]); + $mock->boot(); + + expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); + + $artisanCommands = collect(Artisan::all()); + + expect($artisanCommands)->toHaveKeys(array_keys($this->commands)); - $this->artisan('invoice:deliver')->assertSuccessful(); - $this->artisan('log:prune')->assertSuccessful(); - $this->artisan('application:sync')->assertSuccessful(); + foreach ($this->commands as $command => $class) { + $this->artisan($command)->assertSuccessful(); + } }); }); diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index 45efa92..4d67b7f 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -1,26 +1,45 @@ setupTestApplication(); + DomainCache::clear(); }); -describe('autoload enabled', function () { - beforeEach(function () { - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); +afterEach(function () { + DomainCache::clear(); +}); + +describe('when ddd.autoload.factories = true', function () { + it('handles the factories', function () { + config()->set('ddd.autoload.factories', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldReceive('handleFactories')->once(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.factories' => true, - ]); + $mock->boot(); }); it('can resolve domain factory', function ($modelClass, $expectedFactoryClass) { + config()->set('ddd.autoload.factories', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); + }); + + $mock->boot(); + expect($modelClass::factory())->toBeInstanceOf($expectedFactoryClass); })->with([ // VanillaModel is a vanilla eloquent model in the domain layer @@ -37,6 +56,10 @@ ]); it('gracefully falls back for non-domain factories', function () { + config()->set('ddd.autoload.factories', true); + + Autoload::boot(); + Artisan::call('make:model RegularModel -f'); $modelClass = 'App\Models\RegularModel'; @@ -46,21 +69,32 @@ expect($modelClass::factory()) ->toBeInstanceOf('Database\Factories\RegularModelFactory'); }); -})->skip(); +}); + +describe('when ddd.autoload.factories = false', function () { + it('skips handling factories', function () { + config()->set('ddd.autoload.factories', false); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldNotReceive('handleFactories'); + }); + + $mock->boot(); + }); -describe('autoload disabled', function () { it('cannot resolve factories that rely on autoloading', function ($modelClass) { - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); + config()->set('ddd.autoload.factories', false); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.factories' => false, - ]); + $mock->boot(); expect(fn () => $modelClass::factory())->toThrow(Error::class); })->with([ ['Domain\Invoicing\Models\VanillaModel'], ['Domain\Internal\Reporting\Models\Report'], ]); -})->skip(); +}); diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 0b2fafe..18e1d81 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -11,19 +11,32 @@ uses(BootsTestApplication::class); beforeEach(function () { + $this->providers = [ + 'Domain\Invoicing\Providers\InvoiceServiceProvider', + 'Application\Providers\ApplicationServiceProvider', + 'Infrastructure\Providers\InfrastructureServiceProvider', + ]; + + $this->commands = [ + 'invoice:deliver' => 'Domain\Invoicing\Commands\InvoiceDeliver', + 'log:prune' => 'Infrastructure\Commands\LogPrune', + 'application:sync' => 'Application\Commands\ApplicationSync', + ]; + $this->setupTestApplication(); -})->skip(); + DomainCache::clear(); +}); + +afterEach(function () { + DomainCache::clear(); +}); it('can ignore folders when autoloading', function () { Artisan::call('ddd:optimize'); $expected = [ - 'Domain\Invoicing\Providers\InvoiceServiceProvider', - 'Domain\Invoicing\Commands\InvoiceDeliver', - 'Application\Providers\ApplicationServiceProvider', - 'Application\Commands\ApplicationSync', - 'Infrastructure\Providers\InfrastructureServiceProvider', - 'Infrastructure\Commands\LogPrune', + ...array_values($this->providers), + ...array_values($this->commands), ]; $cached = [ @@ -38,9 +51,7 @@ Artisan::call('ddd:optimize'); $expected = [ - 'Domain\Invoicing\Providers\InvoiceServiceProvider', - 'Application\Providers\ApplicationServiceProvider', - 'Infrastructure\Providers\InfrastructureServiceProvider', + ...array_values($this->providers), ]; $cached = [ @@ -55,9 +66,7 @@ Artisan::call('ddd:optimize'); $expected = [ - 'Domain\Invoicing\Commands\InvoiceDeliver', - 'Application\Commands\ApplicationSync', - 'Infrastructure\Commands\LogPrune', + ...array_values($this->commands), ]; $cached = [ @@ -65,19 +74,15 @@ ...DomainCache::get('domain-commands'), ]; - expect($cached)->toEqual($expected); + expect($cached)->toEqualCanonicalizing($expected); }); it('can register a custom autoload filter', function () { Artisan::call('ddd:optimize'); $expected = [ - 'Domain\Invoicing\Providers\InvoiceServiceProvider', - 'Domain\Invoicing\Commands\InvoiceDeliver', - 'Application\Providers\ApplicationServiceProvider', - 'Application\Commands\ApplicationSync', - 'Infrastructure\Providers\InfrastructureServiceProvider', - 'Infrastructure\Commands\LogPrune', + ...array_values($this->providers), + ...array_values($this->commands), ]; $cached = [ diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index 5483148..8fd521f 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -1,26 +1,83 @@ policies = [ + 'Domain\Invoicing\Models\Invoice' => 'Domain\Invoicing\Policies\InvoicePolicy', + 'Infrastructure\Models\AppSession' => 'Infrastructure\Policies\AppSessionPolicy', + 'Application\Models\Login' => 'Application\Policies\LoginPolicy', + ]; + $this->setupTestApplication(); -})->skip(); - -it('can autoload policy', function ($class, $expectedPolicy) { - expect(class_exists($class))->toBeTrue(); - expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); -})->with([ - ['Domain\Invoicing\Models\Invoice', 'Domain\Invoicing\Policies\InvoicePolicy'], - ['Infrastructure\Models\AppSession', 'Infrastructure\Policies\AppSessionPolicy'], - ['Application\Models\Login', 'Application\Policies\LoginPolicy'], -]); - -it('gracefully falls back for non-ddd policies', function ($class, $expectedPolicy) { - expect(class_exists($class))->toBeTrue(); - expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); -})->with([ - ['App\Models\Post', 'App\Policies\PostPolicy'], -]); + DomainCache::clear(); +}); + +afterEach(function () { + DomainCache::clear(); +}); + +describe('when ddd.autoload.policies = false', function () { + it('skips handling policies', function () { + config()->set('ddd.autoload.policies', false); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldNotReceive('handlePolicies'); + }); + + $mock->boot(); + }); +}); + +describe('when ddd.autoload.policies = true', function () { + it('handles policies', function () { + config()->set('ddd.autoload.policies', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldReceive('handlePolicies')->once(); + }); + + $mock->boot(); + }); + + it('can resolve the policies', function () { + config()->set('ddd.autoload.policies', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); + }); + + $mock->boot(); + + foreach ($this->policies as $class => $expectedPolicy) { + expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); + // expect($mock->getResolvedPolicies())->toHaveKey($class); + } + }); + + it('gracefully falls back for non-ddd policies', function ($class, $expectedPolicy) { + config()->set('ddd.autoload.policies', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); + }); + + $mock->boot(); + + $resolvedPolicies = $mock->getResolvedPolicies(); + + expect(class_exists($class))->toBeTrue(); + expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); + expect($resolvedPolicies)->not->toHaveKey($class); + })->with([ + ['App\Models\Post', 'App\Policies\PostPolicy'], + ]); +}); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 01d76a7..78b4490 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -1,11 +1,19 @@ providers = [ + 'Domain\Invoicing\Providers\InvoiceServiceProvider', + 'Application\Providers\ApplicationServiceProvider', + 'Infrastructure\Providers\InfrastructureServiceProvider', + ]; + $this->setupTestApplication(); DomainCache::clear(); }); @@ -14,101 +22,103 @@ DomainCache::clear(); }); -describe('without autoload', function () { - it('does not register the provider', function ($binding) { - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); - }); +describe('when ddd.autoload.providers = false', function () { + it('skips handling providers', function () { + config()->set('ddd.autoload.providers', false); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.providers' => false, - ]); - - expect(fn () => app($binding))->toThrow(Exception::class); - })->with([ - ['invoicing'], - ['application-layer'], - ['infrastructure-layer'], - ]); -})->skip(); - -describe('with autoload', function () { - it('registers the provider in domain layer', function () { - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldNotReceive('handleProviders'); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.providers' => true, - ]); + $mock->boot(); - expect(app('invoicing'))->toEqual('invoicing-singleton'); - $this->artisan('invoice:deliver')->expectsOutputToContain('invoice-secret'); + collect($this->providers)->each( + fn ($provider) => expect(app()->getProvider($provider))->toBeNull() + ); }); - it('registers the provider in application layer', function () { - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); + it('does not register the providers', function () { + config()->set('ddd.autoload.providers', false); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.providers' => true, - ]); + $mock->boot(); + + expect($mock->getRegisteredProviders())->toBeEmpty(); + + collect($this->providers)->each( + fn ($provider) => expect(app()->getProvider($provider))->toBeNull() + ); + }); +}); + +describe('when ddd.autoload.providers = true', function () { + it('handles the providers', function () { + config()->set('ddd.autoload.providers', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldReceive('handleProviders')->once(); + }); - expect(app('application-layer'))->toEqual('application-layer-singleton'); - $this->artisan('application:sync')->expectsOutputToContain('application-secret'); + $mock->boot(); }); - it('registers the provider in custom layer', function () { - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); + it('registers the providers', function () { + config()->set('ddd.autoload.providers', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.providers' => true, - ]); + $mock->boot(); + + expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); - expect(app('infrastructure-layer'))->toEqual('infrastructure-layer-singleton'); - $this->artisan('log:prune')->expectsOutputToContain('infrastructure-secret'); + collect($this->providers)->each( + fn ($provider) => expect(app()->getProvider($provider))->toBeInstanceOf($provider) + ); }); -})->skip(); +}); describe('caching', function () { it('remembers the last cached state', function () { DomainCache::set('domain-providers', []); - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); + config()->set('ddd.autoload.providers', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.providers' => true, - ]); + $mock->boot(); - expect(fn () => app('invoicing'))->toThrow(Exception::class); - expect(fn () => app('application-layer'))->toThrow(Exception::class); - expect(fn () => app('infrastructure-layer'))->toThrow(Exception::class); + expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing([]); + + collect($this->providers)->each( + fn ($provider) => expect(app()->getProvider($provider))->toBeNull() + ); }); it('can bust the cache', function () { DomainCache::set('domain-providers', []); DomainCache::clear(); - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); - }); + config()->set('ddd.autoload.providers', true); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.providers' => true, - ]); + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); + }); - expect(app('invoicing'))->toEqual('invoicing-singleton'); - $this->artisan('invoice:deliver')->expectsOutputToContain('invoice-secret'); + $mock->boot(); - expect(app('application-layer'))->toEqual('application-layer-singleton'); - $this->artisan('application:sync')->expectsOutputToContain('application-secret'); + expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); - expect(app('infrastructure-layer'))->toEqual('infrastructure-layer-singleton'); - $this->artisan('log:prune')->expectsOutputToContain('infrastructure-secret'); + collect($this->providers)->each( + fn ($provider) => expect(app()->getProvider($provider))->toBeInstanceOf($provider) + ); }); -})->skip(); +}); diff --git a/tests/Support/AutoloaderTest.php b/tests/Support/AutoloaderTest.php index 2ee1ddf..a39deab 100644 --- a/tests/Support/AutoloaderTest.php +++ b/tests/Support/AutoloaderTest.php @@ -1,13 +1,13 @@ setupTestApplication(); }); it('can run', function () { - $autoloader = new Autoloader; + $autoloader = new AutoloadManager; $autoloader->boot(); })->throwsNoExceptions(); @@ -39,7 +39,7 @@ }); it('can discover paths to all layers', function () { - $autoloader = new Autoloader; + $autoloader = new AutoloadManager; $expected = [ app()->basePath('src/Domain'), diff --git a/tests/TestCase.php b/tests/TestCase.php index da96a0c..b634255 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -43,8 +43,6 @@ public static function resetConfig() static::$configValues = []; } - protected function defineConfigBeforeEnvironment() {} - protected function defineEnvironment($app) { if (in_array(BootsTestApplication::class, class_uses_recursive($this))) { From 5b58587ec59a25df4c45b3d4d7285d634c8376d1 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Fri, 15 Nov 2024 19:33:19 -0500 Subject: [PATCH 082/169] Attempt resetting artisan on test environment. --- src/Support/AutoloadManager.php | 2 +- tests/Autoload/CommandTest.php | 5 +++-- tests/Autoload/FactoryTest.php | 1 - tests/Autoload/IgnoreTest.php | 1 - tests/Autoload/PolicyTest.php | 1 - tests/Autoload/ProviderTest.php | 1 - tests/TestCase.php | 10 ++++++++++ 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index 9f16ff8..6eccf11 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -152,7 +152,7 @@ protected function run() } if (app()->runningInConsole() && ! $this->isConsoleBooted()) { - ConsoleApplication::starting(function ($artisan) { + ConsoleApplication::starting(function (ConsoleApplication $artisan) { foreach ($this->registeredCommands as $command) { $artisan->resolve($command); } diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index e57bf73..6793d50 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -17,7 +17,6 @@ ]; $this->setupTestApplication(); - DomainCache::clear(); }); afterEach(function () { @@ -35,10 +34,12 @@ $mock->boot(); + expect($mock->getRegisteredCommands())->toBeEmpty(); + $artisanCommands = collect(Artisan::all()); expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); - }); + })->only(); it('does not register the commands', function () { config()->set('ddd.autoload.commands', false); diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index 4d67b7f..a21f23c 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -12,7 +12,6 @@ beforeEach(function () { $this->setupTestApplication(); - DomainCache::clear(); }); afterEach(function () { diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 18e1d81..e95055d 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -24,7 +24,6 @@ ]; $this->setupTestApplication(); - DomainCache::clear(); }); afterEach(function () { diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index 8fd521f..83308b6 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -16,7 +16,6 @@ ]; $this->setupTestApplication(); - DomainCache::clear(); }); afterEach(function () { diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 78b4490..fe7bafe 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -15,7 +15,6 @@ ]; $this->setupTestApplication(); - DomainCache::clear(); }); afterEach(function () { diff --git a/tests/TestCase.php b/tests/TestCase.php index b634255..632df9c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,7 @@ namespace Lunarstorm\LaravelDDD\Tests; +use Illuminate\Console\Application as ConsoleApplication; use Illuminate\Contracts\Config\Repository; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Arr; @@ -228,6 +229,15 @@ protected function setupTestApplication() $this->setAutoloadPathInComposer('Application', 'src/Application'); $this->setAutoloadPathInComposer('Infrastructure', 'src/Infrastructure'); + $this->resetArtisan(); + + return $this; + } + + protected function resetArtisan() + { + ConsoleApplication::forgetBootstrappers(); + return $this; } From 121ab0fed53971be416e01e499169e9b2e6dc460 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Fri, 15 Nov 2024 20:00:27 -0500 Subject: [PATCH 083/169] Make autoload resolve arrays static. --- src/Support/AutoloadManager.php | 52 ++++++++++++++++----------------- tests/Autoload/CommandTest.php | 2 +- tests/TestCase.php | 10 ------- 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index 6eccf11..45f299b 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -26,13 +26,13 @@ class AutoloadManager protected string $appNamespace; - protected array $registeredCommands = []; + protected static array $registeredCommands = []; - protected array $registeredProviders = []; + protected static array $registeredProviders = []; - protected array $resolvedPolicies = []; + protected static array $resolvedPolicies = []; - protected array $resolvedFactories = []; + protected static array $resolvedFactories = []; protected bool $booted = false; @@ -72,17 +72,17 @@ public function isConsoleBooted(): bool protected function flush() { - foreach ($this->registeredProviders as $provider) { + foreach (static::$registeredProviders as $provider) { app()->forgetInstance($provider); } - $this->registeredProviders = []; + static::$registeredProviders = []; - $this->registeredCommands = []; + static::$registeredCommands = []; - $this->resolvedPolicies = []; + static::$resolvedPolicies = []; - $this->resolvedFactories = []; + static::$resolvedFactories = []; return $this; } @@ -116,14 +116,14 @@ protected function handleProviders() ? DomainCache::get('domain-providers') : $this->discoverProviders(); - foreach ($this->registeredProviders as $provider) { + foreach (static::$registeredProviders as $provider) { app()->forgetInstance($provider); } - $this->registeredProviders = []; + static::$registeredProviders = []; foreach ($providers as $provider) { - $this->registeredProviders[$provider] = $provider; + static::$registeredProviders[$provider] = $provider; app()->register($provider); } @@ -136,10 +136,10 @@ protected function handleCommands() ? DomainCache::get('domain-commands') : $this->discoverCommands(); - $this->registeredCommands = []; + static::$registeredCommands = []; foreach ($commands as $command) { - $this->registeredCommands[$command] = $command; + static::$registeredCommands[$command] = $command; } return $this; @@ -147,13 +147,13 @@ protected function handleCommands() protected function run() { - foreach ($this->registeredProviders as $provider) { + foreach (static::$registeredProviders as $provider) { app()->register($provider); } if (app()->runningInConsole() && ! $this->isConsoleBooted()) { ConsoleApplication::starting(function (ConsoleApplication $artisan) { - foreach ($this->registeredCommands as $command) { + foreach (static::$registeredCommands as $command) { $artisan->resolve($command); } }); @@ -166,29 +166,29 @@ protected function run() public function getRegisteredCommands(): array { - return $this->registeredCommands; + return static::$registeredCommands; } public function getRegisteredProviders(): array { - return $this->registeredProviders; + return static::$registeredProviders; } public function getResolvedPolicies(): array { - return $this->resolvedPolicies; + return static::$resolvedPolicies; } public function getResolvedFactories(): array { - return $this->resolvedFactories; + return static::$resolvedFactories; } protected function handlePolicies() { Gate::guessPolicyNamesUsing(function (string $class): array|string { - if (array_key_exists($class, $this->resolvedPolicies)) { - return $this->resolvedPolicies[$class]; + if (array_key_exists($class, static::$resolvedPolicies)) { + return static::$resolvedPolicies[$class]; } if ($model = DomainObject::fromClass($class, 'model')) { @@ -196,7 +196,7 @@ protected function handlePolicies() ->object('policy', "{$model->name}Policy") ->fullyQualifiedName; - $this->resolvedPolicies[$class] = $resolved; + static::$resolvedPolicies[$class] = $resolved; return $resolved; } @@ -220,12 +220,12 @@ protected function handlePolicies() protected function handleFactories() { Factory::guessFactoryNamesUsing(function (string $modelName) { - if (array_key_exists($modelName, $this->resolvedFactories)) { - return $this->resolvedFactories[$modelName]; + if (array_key_exists($modelName, static::$resolvedFactories)) { + return static::$resolvedFactories[$modelName]; } if ($factoryName = DomainFactory::resolveFactoryName($modelName)) { - $this->resolvedFactories[$modelName] = $factoryName; + static::$resolvedFactories[$modelName] = $factoryName; return $factoryName; } diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 6793d50..4edbcbe 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -39,7 +39,7 @@ $artisanCommands = collect(Artisan::all()); expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); - })->only(); + }); it('does not register the commands', function () { config()->set('ddd.autoload.commands', false); diff --git a/tests/TestCase.php b/tests/TestCase.php index 632df9c..b634255 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,7 +2,6 @@ namespace Lunarstorm\LaravelDDD\Tests; -use Illuminate\Console\Application as ConsoleApplication; use Illuminate\Contracts\Config\Repository; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Arr; @@ -229,15 +228,6 @@ protected function setupTestApplication() $this->setAutoloadPathInComposer('Application', 'src/Application'); $this->setAutoloadPathInComposer('Infrastructure', 'src/Infrastructure'); - $this->resetArtisan(); - - return $this; - } - - protected function resetArtisan() - { - ConsoleApplication::forgetBootstrappers(); - return $this; } From 3980794bda790f96dae30eaf4d8c1465e0991d9a Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Fri, 15 Nov 2024 21:08:19 -0500 Subject: [PATCH 084/169] Update tests. --- tests/Autoload/CommandTest.php | 49 +++++++++++++---------------- tests/Autoload/FactoryTest.php | 19 +++++++----- tests/Autoload/PolicyTest.php | 25 +++++++++------ tests/Autoload/ProviderTest.php | 55 +++++++++++++++++++-------------- tests/TestCase.php | 3 ++ 5 files changed, 83 insertions(+), 68 deletions(-) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 4edbcbe..daaa7fc 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -1,6 +1,7 @@ boot(); expect($mock->getRegisteredCommands())->toBeEmpty(); - - $artisanCommands = collect(Artisan::all()); - - expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); }); it('does not register the commands', function () { config()->set('ddd.autoload.commands', false); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods() - ->shouldNotReceive('handleCommands'); - }); - - $mock->boot(); - - expect($mock->getRegisteredCommands())->toBeEmpty(); + Autoload::boot(); $artisanCommands = collect(Artisan::all()); @@ -63,13 +53,14 @@ it('registers the commands', function () { config()->set('ddd.autoload.commands', true); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods(); - }); + // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + // $mock->shouldAllowMockingProtectedMethods(); + // }); - $mock->boot(); + // $mock->boot(); - expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); + // expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); + Autoload::boot(); $artisanCommands = collect(Artisan::all()); @@ -83,13 +74,15 @@ config()->set('ddd.autoload.commands', true); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods(); - }); + // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + // $mock->shouldAllowMockingProtectedMethods(); + // }); - $mock->boot(); + // $mock->boot(); - expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing([]); + // expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing([]); + + Autoload::boot(); $artisanCommands = collect(Artisan::all()); @@ -105,13 +98,15 @@ DomainCache::set('domain-commands', []); DomainCache::clear(); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods(); - }); + // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + // $mock->shouldAllowMockingProtectedMethods(); + // }); - $mock->boot(); + // $mock->boot(); + + // expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); - expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); + Autoload::boot(); $artisanCommands = collect(Artisan::all()); diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index a21f23c..473ab6a 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -33,11 +33,12 @@ it('can resolve domain factory', function ($modelClass, $expectedFactoryClass) { config()->set('ddd.autoload.factories', true); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods(); - }); + // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + // $mock->shouldAllowMockingProtectedMethods(); + // }); - $mock->boot(); + // $mock->boot(); + Autoload::boot(); expect($modelClass::factory())->toBeInstanceOf($expectedFactoryClass); })->with([ @@ -85,11 +86,13 @@ it('cannot resolve factories that rely on autoloading', function ($modelClass) { config()->set('ddd.autoload.factories', false); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods(); - }); + // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + // $mock->shouldAllowMockingProtectedMethods(); + // }); - $mock->boot(); + // $mock->boot(); + + Autoload::boot(); expect(fn () => $modelClass::factory())->toThrow(Error::class); })->with([ diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index 83308b6..8891e21 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -1,6 +1,7 @@ set('ddd.autoload.policies', true); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods(); - }); + // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + // $mock->shouldAllowMockingProtectedMethods(); + // }); - $mock->boot(); + // $mock->boot(); + + Autoload::boot(); foreach ($this->policies as $class => $expectedPolicy) { expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); @@ -65,17 +68,19 @@ it('gracefully falls back for non-ddd policies', function ($class, $expectedPolicy) { config()->set('ddd.autoload.policies', true); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods(); - }); + // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + // $mock->shouldAllowMockingProtectedMethods(); + // }); - $mock->boot(); + // $mock->boot(); + + // $resolvedPolicies = $mock->getResolvedPolicies(); - $resolvedPolicies = $mock->getResolvedPolicies(); + Autoload::boot(); expect(class_exists($class))->toBeTrue(); expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); - expect($resolvedPolicies)->not->toHaveKey($class); + // expect($resolvedPolicies)->not->toHaveKey($class); })->with([ ['App\Models\Post', 'App\Policies\PostPolicy'], ]); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index fe7bafe..4d8821a 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -1,5 +1,6 @@ boot(); - collect($this->providers)->each( - fn ($provider) => expect(app()->getProvider($provider))->toBeNull() - ); + // collect($this->providers)->each( + // fn ($provider) => expect(app()->getProvider($provider))->toBeNull() + // ); }); it('does not register the providers', function () { config()->set('ddd.autoload.providers', false); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods(); - }); + // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + // $mock->shouldAllowMockingProtectedMethods(); + // }); - $mock->boot(); + // $mock->boot(); + + // expect($mock->getRegisteredProviders())->toBeEmpty(); - expect($mock->getRegisteredProviders())->toBeEmpty(); + Autoload::boot(); collect($this->providers)->each( fn ($provider) => expect(app()->getProvider($provider))->toBeNull() @@ -69,13 +72,15 @@ it('registers the providers', function () { config()->set('ddd.autoload.providers', true); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods(); - }); + // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + // $mock->shouldAllowMockingProtectedMethods(); + // }); - $mock->boot(); + // $mock->boot(); - expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); + // expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); + + Autoload::boot(); collect($this->providers)->each( fn ($provider) => expect(app()->getProvider($provider))->toBeInstanceOf($provider) @@ -89,13 +94,15 @@ config()->set('ddd.autoload.providers', true); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods(); - }); + // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + // $mock->shouldAllowMockingProtectedMethods(); + // }); - $mock->boot(); + // $mock->boot(); + + // expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing([]); - expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing([]); + Autoload::boot(); collect($this->providers)->each( fn ($provider) => expect(app()->getProvider($provider))->toBeNull() @@ -108,13 +115,15 @@ config()->set('ddd.autoload.providers', true); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods(); - }); + // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + // $mock->shouldAllowMockingProtectedMethods(); + // }); - $mock->boot(); + // $mock->boot(); + + // expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); - expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); + Autoload::boot(); collect($this->providers)->each( fn ($provider) => expect(app()->getProvider($provider))->toBeInstanceOf($provider) diff --git a/tests/TestCase.php b/tests/TestCase.php index b634255..6fb273c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -7,6 +7,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Facades\File; use Lunarstorm\LaravelDDD\LaravelDDDServiceProvider; +use Lunarstorm\LaravelDDD\Support\DomainCache; use Orchestra\Testbench\TestCase as Orchestra; use Symfony\Component\Process\Process; @@ -228,6 +229,8 @@ protected function setupTestApplication() $this->setAutoloadPathInComposer('Application', 'src/Application'); $this->setAutoloadPathInComposer('Infrastructure', 'src/Infrastructure'); + DomainCache::clear(); + return $this; } From e11e6c04e08b2a6907b195f61158a8b7701e0116 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Fri, 15 Nov 2024 21:14:39 -0500 Subject: [PATCH 085/169] Skip ignore test to see effect. --- tests/Autoload/IgnoreTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index e95055d..de0dc54 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -24,7 +24,7 @@ ]; $this->setupTestApplication(); -}); +})->skip(); afterEach(function () { DomainCache::clear(); From 9ab8b6ff1b4482adac5278afd93643b8b6790656 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Fri, 15 Nov 2024 21:46:09 -0500 Subject: [PATCH 086/169] Try limiting run() --- src/Support/AutoloadManager.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index 45f299b..83e9e5f 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -38,6 +38,8 @@ class AutoloadManager protected bool $consoleBooted = false; + protected bool $ran = false; + public function __construct() { $this->appNamespace = $this->resolveAppNamespace(); @@ -70,6 +72,11 @@ public function isConsoleBooted(): bool return $this->consoleBooted; } + public function hasRun(): bool + { + return $this->ran; + } + protected function flush() { foreach (static::$registeredProviders as $provider) { @@ -147,6 +154,10 @@ protected function handleCommands() protected function run() { + if ($this->hasRun()) { + return $this; + } + foreach (static::$registeredProviders as $provider) { app()->register($provider); } @@ -161,6 +172,8 @@ protected function run() $this->consoleBooted = true; } + $this->ran = true; + return $this; } From b8fa04cb6f9cc476db832c83e44156ceb41ed1e3 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Fri, 15 Nov 2024 21:54:02 -0500 Subject: [PATCH 087/169] Isolate the autoload run invokation. --- src/LaravelDDDServiceProvider.php | 6 ++++-- src/Support/AutoloadManager.php | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index 3b4d01f..f275afa 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -119,6 +119,10 @@ protected function registerMigrations() public function packageBooted() { + Autoload::boot(); + + Autoload::run(); + if ($this->app->runningInConsole() && method_exists($this, 'optimizes')) { $this->optimizes( optimize: 'ddd:optimize', @@ -126,8 +130,6 @@ public function packageBooted() key: 'laravel-ddd', ); } - - Autoload::boot(); } public function packageRegistered() diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index 83e9e5f..9d698e2 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -56,8 +56,7 @@ public function boot() ->when(config('ddd.autoload.providers') === true, fn () => $this->handleProviders()) ->when(app()->runningInConsole() && config('ddd.autoload.commands') === true, fn () => $this->handleCommands()) ->when(config('ddd.autoload.policies') === true, fn () => $this->handlePolicies()) - ->when(config('ddd.autoload.factories') === true, fn () => $this->handleFactories()) - ->run(); + ->when(config('ddd.autoload.factories') === true, fn () => $this->handleFactories()); $this->booted = true; } @@ -152,7 +151,7 @@ protected function handleCommands() return $this; } - protected function run() + public function run() { if ($this->hasRun()) { return $this; From c6c08a4b1a782eac52ca9b74413a1d3aee9f6ac8 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Fri, 15 Nov 2024 22:33:14 -0500 Subject: [PATCH 088/169] Require app instance --- src/LaravelDDDServiceProvider.php | 54 +++++++++++++++---------------- src/Support/AutoloadManager.php | 39 ++++++++++++++++------ tests/Support/AutoloaderTest.php | 7 ++-- 3 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index f275afa..11d843b 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -12,32 +12,6 @@ class LaravelDDDServiceProvider extends PackageServiceProvider { public function configurePackage(Package $package): void { - $this->app->scoped(DomainManager::class, function () { - return new DomainManager; - }); - - $this->app->scoped(AutoloadManager::class, function () { - return new AutoloadManager; - }); - - $this->app->scoped(ComposerManager::class, function () { - return ComposerManager::make(app()->basePath('composer.json')); - }); - - $this->app->scoped(ConfigManager::class, function () { - return new ConfigManager(config_path('ddd.php')); - }); - - $this->app->scoped(StubManager::class, function () { - return new StubManager; - }); - - $this->app->bind('ddd', DomainManager::class); - $this->app->bind('ddd.autoloader', AutoloadManager::class); - $this->app->bind('ddd.config', ConfigManager::class); - $this->app->bind('ddd.composer', ComposerManager::class); - $this->app->bind('ddd.stubs', StubManager::class); - /* * This class is a Package Service Provider * @@ -119,8 +93,6 @@ protected function registerMigrations() public function packageBooted() { - Autoload::boot(); - Autoload::run(); if ($this->app->runningInConsole() && method_exists($this, 'optimizes')) { @@ -134,6 +106,32 @@ public function packageBooted() public function packageRegistered() { + $this->app->scoped(DomainManager::class, function () { + return new DomainManager; + }); + + $this->app->scoped(AutoloadManager::class, function ($app) { + return new AutoloadManager($app); + }); + + $this->app->scoped(ComposerManager::class, function () { + return ComposerManager::make(app()->basePath('composer.json')); + }); + + $this->app->scoped(ConfigManager::class, function () { + return new ConfigManager(config_path('ddd.php')); + }); + + $this->app->scoped(StubManager::class, function () { + return new StubManager; + }); + + $this->app->bind('ddd', DomainManager::class); + $this->app->bind('ddd.autoloader', AutoloadManager::class); + $this->app->bind('ddd.config', ConfigManager::class); + $this->app->bind('ddd.composer', ComposerManager::class); + $this->app->bind('ddd.stubs', StubManager::class); + $this->registerMigrations(); } } diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index 9d698e2..f35351d 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -24,6 +24,8 @@ class AutoloadManager { use Conditionable; + protected $app; + protected string $appNamespace; protected static array $registeredCommands = []; @@ -40,11 +42,24 @@ class AutoloadManager protected bool $ran = false; - public function __construct() + /** + * Create a new autoloader instance. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return void + */ + public function __construct($app) { - $this->appNamespace = $this->resolveAppNamespace(); + $this->app = $app; + + $this->appNamespace = $this->app->getNamespace(); } + // public function __construct(protected \Illuminate\Contracts\Foundation\Application $app) + // { + // $this->appNamespace = $this->resolveAppNamespace(); + // } + public function boot() { if (! config()->has('ddd.autoload')) { @@ -54,7 +69,7 @@ public function boot() $this ->flush() ->when(config('ddd.autoload.providers') === true, fn () => $this->handleProviders()) - ->when(app()->runningInConsole() && config('ddd.autoload.commands') === true, fn () => $this->handleCommands()) + ->when($this->app->runningInConsole() && config('ddd.autoload.commands') === true, fn () => $this->handleCommands()) ->when(config('ddd.autoload.policies') === true, fn () => $this->handlePolicies()) ->when(config('ddd.autoload.factories') === true, fn () => $this->handleFactories()); @@ -79,7 +94,7 @@ public function hasRun(): bool protected function flush() { foreach (static::$registeredProviders as $provider) { - app()->forgetInstance($provider); + $this->app?->forgetInstance($provider); } static::$registeredProviders = []; @@ -106,14 +121,14 @@ public function getAllLayerPaths(): array DomainResolver::domainPath(), DomainResolver::applicationLayerPath(), ...array_values(config('ddd.layers', [])), - ])->map(fn ($path) => app()->basePath($path))->toArray(); + ])->map(fn ($path) => $this->app->basePath($path))->toArray(); } protected function getCustomLayerPaths(): array { return collect([ ...array_values(config('ddd.layers', [])), - ])->map(fn ($path) => app()->basePath($path))->toArray(); + ])->map(fn ($path) => $this->app->basePath($path))->toArray(); } protected function handleProviders() @@ -123,14 +138,14 @@ protected function handleProviders() : $this->discoverProviders(); foreach (static::$registeredProviders as $provider) { - app()->forgetInstance($provider); + $this->app->forgetInstance($provider); } static::$registeredProviders = []; foreach ($providers as $provider) { static::$registeredProviders[$provider] = $provider; - app()->register($provider); + $this->app->register($provider); } return $this; @@ -157,11 +172,15 @@ public function run() return $this; } + if (! $this->isBooted()) { + $this->boot(); + } + foreach (static::$registeredProviders as $provider) { - app()->register($provider); + $this->app->register($provider); } - if (app()->runningInConsole() && ! $this->isConsoleBooted()) { + if ($this->app->runningInConsole() && ! $this->isConsoleBooted()) { ConsoleApplication::starting(function (ConsoleApplication $artisan) { foreach (static::$registeredCommands as $command) { $artisan->resolve($command); diff --git a/tests/Support/AutoloaderTest.php b/tests/Support/AutoloaderTest.php index a39deab..0e83050 100644 --- a/tests/Support/AutoloaderTest.php +++ b/tests/Support/AutoloaderTest.php @@ -1,5 +1,6 @@ boot(); + Autoload::run(); })->throwsNoExceptions(); beforeEach(function () { @@ -39,7 +38,7 @@ }); it('can discover paths to all layers', function () { - $autoloader = new AutoloadManager; + $autoloader = app(AutoloadManager::class); $expected = [ app()->basePath('src/Domain'), From cf39cb2eb3377c1512beeb9e80ad293b5570586c Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Fri, 15 Nov 2024 22:39:12 -0500 Subject: [PATCH 089/169] Allow null for console environments. --- src/Commands/OptimizeCommand.php | 6 +++--- src/Support/AutoloadManager.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Commands/OptimizeCommand.php b/src/Commands/OptimizeCommand.php index b36f7cd..58b2245 100644 --- a/src/Commands/OptimizeCommand.php +++ b/src/Commands/OptimizeCommand.php @@ -3,7 +3,7 @@ namespace Lunarstorm\LaravelDDD\Commands; use Illuminate\Console\Command; -use Lunarstorm\LaravelDDD\Facades\DDD; +use Lunarstorm\LaravelDDD\Facades\Autoload; use Lunarstorm\LaravelDDD\Support\DomainMigration; class OptimizeCommand extends Command @@ -24,8 +24,8 @@ protected function configure() public function handle() { $this->components->info('Caching DDD providers, commands, migration paths.'); - $this->components->task('domain providers', fn () => DDD::autoloader()->cacheProviders()); - $this->components->task('domain commands', fn () => DDD::autoloader()->cacheCommands()); + $this->components->task('domain providers', fn () => Autoload::cacheProviders()); + $this->components->task('domain commands', fn () => Autoload::cacheCommands()); $this->components->task('domain migration paths', fn () => DomainMigration::cachePaths()); $this->newLine(); } diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index f35351d..25a7074 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -48,9 +48,9 @@ class AutoloadManager * @param \Illuminate\Contracts\Foundation\Application $app * @return void */ - public function __construct($app) + public function __construct($app = null) { - $this->app = $app; + $this->app = $app ?? Container::getInstance()->make(Application::class); $this->appNamespace = $this->app->getNamespace(); } From 7bad1522a82da3e97b8fdad139b71de2b0151ba3 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 11:15:25 -0500 Subject: [PATCH 090/169] Refactoring the autoloader for mocking --- src/LaravelDDDServiceProvider.php | 57 ++++++++++++++------- src/Support/AutoloadManager.php | 48 ++++++++--------- tests/Autoload/CommandTest.php | 79 ++++++++++++---------------- tests/Autoload/FactoryTest.php | 45 +++++----------- tests/Autoload/PolicyTest.php | 44 +++++----------- tests/Autoload/ProviderTest.php | 85 +++++++++++++------------------ 6 files changed, 154 insertions(+), 204 deletions(-) diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index 11d843b..70992f9 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -2,6 +2,7 @@ namespace Lunarstorm\LaravelDDD; +use Illuminate\Foundation\Application; use Lunarstorm\LaravelDDD\Facades\Autoload; use Lunarstorm\LaravelDDD\Support\AutoloadManager; use Lunarstorm\LaravelDDD\Support\DomainMigration; @@ -89,31 +90,16 @@ protected function registerMigrations() }); $this->loadMigrationsFrom(DomainMigration::paths()); - } - - public function packageBooted() - { - Autoload::run(); - if ($this->app->runningInConsole() && method_exists($this, 'optimizes')) { - $this->optimizes( - optimize: 'ddd:optimize', - clear: 'ddd:clear', - key: 'laravel-ddd', - ); - } + return $this; } - public function packageRegistered() + protected function registerBindings() { $this->app->scoped(DomainManager::class, function () { return new DomainManager; }); - $this->app->scoped(AutoloadManager::class, function ($app) { - return new AutoloadManager($app); - }); - $this->app->scoped(ComposerManager::class, function () { return ComposerManager::make(app()->basePath('composer.json')); }); @@ -126,12 +112,47 @@ public function packageRegistered() return new StubManager; }); + $this->app->scoped(AutoloadManager::class, function () { + return new AutoloadManager; + }); + $this->app->bind('ddd', DomainManager::class); $this->app->bind('ddd.autoloader', AutoloadManager::class); $this->app->bind('ddd.config', ConfigManager::class); $this->app->bind('ddd.composer', ComposerManager::class); $this->app->bind('ddd.stubs', StubManager::class); - $this->registerMigrations(); + if ($this->app->runningUnitTests()) { + // $this->app->when(AutoloadManager::class) + // ->needs(Application::class) + // ->give(function () { + // return $this->app; + // }); + + $this->app->resolving(AutoloadManager::class, function (AutoloadManager $atuoloader, Application $app) { + // dump('App resolving autoloader'); + }); + } + + return $this; + } + + public function packageBooted() + { + Autoload::run(); + + if ($this->app->runningInConsole() && method_exists($this, 'optimizes')) { + $this->optimizes( + optimize: 'ddd:optimize', + clear: 'ddd:clear', + key: 'laravel-ddd', + ); + } + } + + public function packageRegistered() + { + $this->registerMigrations() + ->registerBindings(); } } diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index 25a7074..4d1c67c 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -2,6 +2,7 @@ namespace Lunarstorm\LaravelDDD\Support; +use Closure; use Illuminate\Console\Application as ConsoleApplication; use Illuminate\Console\Command; use Illuminate\Container\Container; @@ -16,6 +17,7 @@ use Lorisleiva\Lody\Lody; use Lunarstorm\LaravelDDD\Factories\DomainFactory; use Lunarstorm\LaravelDDD\ValueObjects\DomainObject; +use Mockery; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; use Throwable; @@ -36,30 +38,25 @@ class AutoloadManager protected static array $resolvedFactories = []; + protected static ?Closure $policyResolver = null; + + protected static ?Closure $factoryResolver = null; + protected bool $booted = false; protected bool $consoleBooted = false; protected bool $ran = false; - /** - * Create a new autoloader instance. - * - * @param \Illuminate\Contracts\Foundation\Application $app - * @return void - */ - public function __construct($app = null) + public function __construct(protected ?Container $container = null) { - $this->app = $app ?? Container::getInstance()->make(Application::class); + $this->container = $container ?? Container::getInstance(); + + $this->app = $this->container->make(Application::class); $this->appNamespace = $this->app->getNamespace(); } - // public function __construct(protected \Illuminate\Contracts\Foundation\Application $app) - // { - // $this->appNamespace = $this->resolveAppNamespace(); - // } - public function boot() { if (! config()->has('ddd.autoload')) { @@ -168,9 +165,9 @@ protected function handleCommands() public function run() { - if ($this->hasRun()) { - return $this; - } + // if ($this->hasRun()) { + // return $this; + // } if (! $this->isBooted()) { $this->boot(); @@ -217,11 +214,7 @@ public function getResolvedFactories(): array protected function handlePolicies() { - Gate::guessPolicyNamesUsing(function (string $class): array|string { - if (array_key_exists($class, static::$resolvedPolicies)) { - return static::$resolvedPolicies[$class]; - } - + Gate::guessPolicyNamesUsing(static::$policyResolver = function (string $class): array|string { if ($model = DomainObject::fromClass($class, 'model')) { $resolved = (new Domain($model->domain)) ->object('policy', "{$model->name}Policy") @@ -250,11 +243,7 @@ protected function handlePolicies() protected function handleFactories() { - Factory::guessFactoryNamesUsing(function (string $modelName) { - if (array_key_exists($modelName, static::$resolvedFactories)) { - return static::$resolvedFactories[$modelName]; - } - + Factory::guessFactoryNamesUsing(static::$factoryResolver = function (string $modelName) { if ($factoryName = DomainFactory::resolveFactoryName($modelName)) { static::$resolvedFactories[$modelName] = $factoryName; @@ -365,4 +354,11 @@ protected function resolveAppNamespace() return 'App\\'; } } + + public static function partialMock() + { + return Mockery::mock(AutoloadManager::class, [null]) + ->makePartial() + ->shouldAllowMockingProtectedMethods(); + } } diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index daaa7fc..4d0c308 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -5,7 +5,6 @@ use Lunarstorm\LaravelDDD\Support\AutoloadManager; use Lunarstorm\LaravelDDD\Support\DomainCache; use Lunarstorm\LaravelDDD\Tests\BootsTestApplication; -use Mockery\MockInterface; use Symfony\Component\Console\Exception\CommandNotFoundException; uses(BootsTestApplication::class); @@ -28,43 +27,37 @@ it('skips handling commands', function () { config()->set('ddd.autoload.commands', false); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods() - ->shouldNotReceive('handleCommands'); - }); - + $mock = AutoloadManager::partialMock(); + $mock->shouldNotReceive('handleCommands'); $mock->boot(); expect($mock->getRegisteredCommands())->toBeEmpty(); }); - it('does not register the commands', function () { - config()->set('ddd.autoload.commands', false); + // it('does not register the commands', function () { + // config()->set('ddd.autoload.commands', false); - Autoload::boot(); + // Autoload::boot(); - $artisanCommands = collect(Artisan::all()); + // $artisanCommands = collect(Artisan::all()); - expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); - }); + // expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); + // }); }); describe('when ddd.autoload.commands = true', function () { it('registers the commands', function () { config()->set('ddd.autoload.commands', true); - // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - // $mock->shouldAllowMockingProtectedMethods(); - // }); - - // $mock->boot(); + $mock = AutoloadManager::partialMock(); + $mock->boot(); - // expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); - Autoload::boot(); + expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); + // Autoload::boot(); - $artisanCommands = collect(Artisan::all()); + // $artisanCommands = collect(Artisan::all()); - expect($artisanCommands)->toHaveKeys(array_keys($this->commands)); + // expect($artisanCommands)->toHaveKeys(array_keys($this->commands)); }); }); @@ -74,46 +67,40 @@ config()->set('ddd.autoload.commands', true); - // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - // $mock->shouldAllowMockingProtectedMethods(); - // }); - - // $mock->boot(); + $mock = AutoloadManager::partialMock(); + $mock->boot(); - // expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing([]); + expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing([]); - Autoload::boot(); + // Autoload::boot(); - $artisanCommands = collect(Artisan::all()); + // $artisanCommands = collect(Artisan::all()); - expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); + // expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); - // commands should not be recognized due to cached empty-state - foreach ($this->commands as $command => $class) { - expect(fn () => Artisan::call($command))->toThrow(CommandNotFoundException::class); - } + // // commands should not be recognized due to cached empty-state + // foreach ($this->commands as $command => $class) { + // expect(fn () => Artisan::call($command))->toThrow(CommandNotFoundException::class); + // } }); it('can bust the cache', function () { DomainCache::set('domain-commands', []); DomainCache::clear(); - // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - // $mock->shouldAllowMockingProtectedMethods(); - // }); - - // $mock->boot(); + $mock = AutoloadManager::partialMock(); + $mock->boot(); - // expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); + expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); - Autoload::boot(); + // Autoload::boot(); - $artisanCommands = collect(Artisan::all()); + // $artisanCommands = collect(Artisan::all()); - expect($artisanCommands)->toHaveKeys(array_keys($this->commands)); + // expect($artisanCommands)->toHaveKeys(array_keys($this->commands)); - foreach ($this->commands as $command => $class) { - $this->artisan($command)->assertSuccessful(); - } + // foreach ($this->commands as $command => $class) { + // $this->artisan($command)->assertSuccessful(); + // } }); }); diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index 473ab6a..912fe31 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -2,11 +2,9 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Facades\Artisan; -use Lunarstorm\LaravelDDD\Facades\Autoload; use Lunarstorm\LaravelDDD\Support\AutoloadManager; use Lunarstorm\LaravelDDD\Support\DomainCache; use Lunarstorm\LaravelDDD\Tests\BootsTestApplication; -use Mockery\MockInterface; uses(BootsTestApplication::class); @@ -22,23 +20,16 @@ it('handles the factories', function () { config()->set('ddd.autoload.factories', true); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods() - ->shouldReceive('handleFactories')->once(); - }); - + $mock = AutoloadManager::partialMock(); + $mock->shouldReceive('handleFactories')->once(); $mock->boot(); }); it('can resolve domain factory', function ($modelClass, $expectedFactoryClass) { config()->set('ddd.autoload.factories', true); - // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - // $mock->shouldAllowMockingProtectedMethods(); - // }); - - // $mock->boot(); - Autoload::boot(); + $mock = AutoloadManager::partialMock(); + $mock->boot(); expect($modelClass::factory())->toBeInstanceOf($expectedFactoryClass); })->with([ @@ -58,7 +49,7 @@ it('gracefully falls back for non-domain factories', function () { config()->set('ddd.autoload.factories', true); - Autoload::boot(); + $this->refreshApplication(); Artisan::call('make:model RegularModel -f'); @@ -66,8 +57,8 @@ expect(class_exists($modelClass))->toBeTrue(); - expect($modelClass::factory()) - ->toBeInstanceOf('Database\Factories\RegularModelFactory'); + expect(Factory::resolveFactoryName($modelClass)) + ->toEqual('Database\Factories\RegularModelFactory'); }); }); @@ -75,28 +66,20 @@ it('skips handling factories', function () { config()->set('ddd.autoload.factories', false); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods() - ->shouldNotReceive('handleFactories'); - }); - + $mock = AutoloadManager::partialMock(); + $mock->shouldNotReceive('handleFactories'); $mock->boot(); }); - it('cannot resolve factories that rely on autoloading', function ($modelClass) { + it('cannot resolve factories that rely on autoloading', function ($modelClass, $correctFactories) { config()->set('ddd.autoload.factories', false); - // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - // $mock->shouldAllowMockingProtectedMethods(); - // }); - - // $mock->boot(); - - Autoload::boot(); + $mock = AutoloadManager::partialMock(); + $mock->boot(); expect(fn () => $modelClass::factory())->toThrow(Error::class); })->with([ - ['Domain\Invoicing\Models\VanillaModel'], - ['Domain\Internal\Reporting\Models\Report'], + ['Domain\Invoicing\Models\VanillaModel', ['Domain\Invoicing\Database\Factories\VanillaModelFactory', 'Database\Factories\Invoicing\VanillaModelFactory']], + ['Domain\Internal\Reporting\Models\Report', ['Domain\Internal\Reporting\Database\Factories\ReportFactory', 'Database\Factories\Internal\Reporting\ReportFactory']], ]); }); diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index 8891e21..d95aa81 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -1,11 +1,9 @@ set('ddd.autoload.policies', false); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods() - ->shouldNotReceive('handlePolicies'); - }); - + $mock = AutoloadManager::partialMock(); + $mock->shouldNotReceive('handlePolicies'); $mock->boot(); }); }); @@ -40,47 +35,32 @@ it('handles policies', function () { config()->set('ddd.autoload.policies', true); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods() - ->shouldReceive('handlePolicies')->once(); - }); - + $mock = AutoloadManager::partialMock(); + $mock->shouldReceive('handlePolicies')->once(); $mock->boot(); }); it('can resolve the policies', function () { config()->set('ddd.autoload.policies', true); - // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - // $mock->shouldAllowMockingProtectedMethods(); - // }); - - // $mock->boot(); - - Autoload::boot(); + $mock = AutoloadManager::partialMock(); + $mock->boot(); foreach ($this->policies as $class => $expectedPolicy) { - expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); - // expect($mock->getResolvedPolicies())->toHaveKey($class); + $resolvedPolicy = Gate::getPolicyFor($class); + expect($mock->getResolvedPolicies())->toHaveKey($class); } - }); + })->markTestIncomplete('custom layer policies are not yet supported'); it('gracefully falls back for non-ddd policies', function ($class, $expectedPolicy) { config()->set('ddd.autoload.policies', true); - // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - // $mock->shouldAllowMockingProtectedMethods(); - // }); - - // $mock->boot(); - - // $resolvedPolicies = $mock->getResolvedPolicies(); - - Autoload::boot(); + $mock = AutoloadManager::partialMock(); + $mock->boot(); expect(class_exists($class))->toBeTrue(); expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); - // expect($resolvedPolicies)->not->toHaveKey($class); + expect($mock->getResolvedPolicies())->not->toHaveKey($class); })->with([ ['App\Models\Post', 'App\Policies\PostPolicy'], ]); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 4d8821a..abaae05 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -4,7 +4,6 @@ use Lunarstorm\LaravelDDD\Support\AutoloadManager; use Lunarstorm\LaravelDDD\Support\DomainCache; use Lunarstorm\LaravelDDD\Tests\BootsTestApplication; -use Mockery\MockInterface; uses(BootsTestApplication::class); @@ -26,13 +25,12 @@ it('skips handling providers', function () { config()->set('ddd.autoload.providers', false); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods() - ->shouldNotReceive('handleProviders'); - }); - + $mock = AutoloadManager::partialMock(); + $mock->shouldNotReceive('handleProviders'); $mock->boot(); + // Autoload::boot(); + // collect($this->providers)->each( // fn ($provider) => expect(app()->getProvider($provider))->toBeNull() // ); @@ -41,19 +39,16 @@ it('does not register the providers', function () { config()->set('ddd.autoload.providers', false); - // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - // $mock->shouldAllowMockingProtectedMethods(); - // }); - - // $mock->boot(); + $mock = AutoloadManager::partialMock(); + $mock->boot(); - // expect($mock->getRegisteredProviders())->toBeEmpty(); + expect($mock->getRegisteredProviders())->toBeEmpty(); - Autoload::boot(); + // Autoload::boot(); - collect($this->providers)->each( - fn ($provider) => expect(app()->getProvider($provider))->toBeNull() - ); + // collect($this->providers)->each( + // fn($provider) => expect(app()->getProvider($provider))->toBeNull() + // ); }); }); @@ -61,30 +56,24 @@ it('handles the providers', function () { config()->set('ddd.autoload.providers', true); - $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - $mock->shouldAllowMockingProtectedMethods() - ->shouldReceive('handleProviders')->once(); - }); - + $mock = AutoloadManager::partialMock(); + $mock->shouldReceive('handleProviders')->once(); $mock->boot(); }); it('registers the providers', function () { config()->set('ddd.autoload.providers', true); - // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - // $mock->shouldAllowMockingProtectedMethods(); - // }); - - // $mock->boot(); + $mock = AutoloadManager::partialMock(); + $mock->boot(); - // expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); + expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); - Autoload::boot(); + // Autoload::boot(); - collect($this->providers)->each( - fn ($provider) => expect(app()->getProvider($provider))->toBeInstanceOf($provider) - ); + // collect($this->providers)->each( + // fn($provider) => expect(app()->getProvider($provider))->toBeInstanceOf($provider) + // ); }); }); @@ -94,19 +83,16 @@ config()->set('ddd.autoload.providers', true); - // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - // $mock->shouldAllowMockingProtectedMethods(); - // }); - - // $mock->boot(); + $mock = AutoloadManager::partialMock(); + $mock->boot(); - // expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing([]); + expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing([]); - Autoload::boot(); + // Autoload::boot(); - collect($this->providers)->each( - fn ($provider) => expect(app()->getProvider($provider))->toBeNull() - ); + // collect($this->providers)->each( + // fn($provider) => expect(app()->getProvider($provider))->toBeNull() + // ); }); it('can bust the cache', function () { @@ -115,18 +101,15 @@ config()->set('ddd.autoload.providers', true); - // $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { - // $mock->shouldAllowMockingProtectedMethods(); - // }); - - // $mock->boot(); + $mock = AutoloadManager::partialMock(); + $mock->boot(); - // expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); + expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); - Autoload::boot(); + // Autoload::boot(); - collect($this->providers)->each( - fn ($provider) => expect(app()->getProvider($provider))->toBeInstanceOf($provider) - ); + // collect($this->providers)->each( + // fn($provider) => expect(app()->getProvider($provider))->toBeInstanceOf($provider) + // ); }); }); From df12de2d228a971301c65e00770ba14b66de4cde Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 11:40:46 -0500 Subject: [PATCH 091/169] Modify the cleanSlate() --- tests/.skeleton/composer.json | 5 ++++- tests/TestCase.php | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/.skeleton/composer.json b/tests/.skeleton/composer.json index 26fbe9e..b245a01 100644 --- a/tests/.skeleton/composer.json +++ b/tests/.skeleton/composer.json @@ -13,7 +13,10 @@ "tests/TestCase.php" ], "psr-4": { - "App\\": "app/" + "App\\": "app/", + "Domain\\": "src/Domain", + "Application\\": "src/Application", + "Infrastructure\\": "src/Infrastructure" } }, "autoload-dev": { diff --git a/tests/TestCase.php b/tests/TestCase.php index 6fb273c..6779581 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -186,8 +186,8 @@ protected function cleanSlate() // File::cleanDirectory(app_path()); File::cleanDirectory(app_path('Models')); File::cleanDirectory(base_path('database/factories')); - File::cleanDirectory(base_path('src')); + File::deleteDirectory(base_path('src')); File::deleteDirectory(resource_path('stubs/ddd')); File::deleteDirectory(base_path('stubs')); File::deleteDirectory(base_path('Custom')); @@ -220,6 +220,13 @@ protected function setupTestApplication() File::copyDirectory($folder, app_path(basename($folder))); } + // $skeletonSrcFolders = glob(__DIR__.'/.skeleton/src/*', GLOB_ONLYDIR); + + // foreach ($skeletonSrcFolders as $folder) { + // File::deleteDirectory(base_path('src/'.basename($folder))); + // File::copyDirectory($folder, base_path('src/'.basename($folder))); + // } + File::copyDirectory(__DIR__.'/.skeleton/database', base_path('database')); File::copyDirectory(__DIR__.'/.skeleton/src', base_path('src')); File::copy(__DIR__.'/.skeleton/bootstrap/providers.php', base_path('bootstrap/providers.php')); From 2fd04e295bba08d2276414649e53d94268421deb Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 11:47:11 -0500 Subject: [PATCH 092/169] Ensure providers aren't registered until run() --- src/Support/AutoloadManager.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index 4d1c67c..6bf405d 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -134,15 +134,8 @@ protected function handleProviders() ? DomainCache::get('domain-providers') : $this->discoverProviders(); - foreach (static::$registeredProviders as $provider) { - $this->app->forgetInstance($provider); - } - - static::$registeredProviders = []; - foreach ($providers as $provider) { static::$registeredProviders[$provider] = $provider; - $this->app->register($provider); } return $this; @@ -154,8 +147,6 @@ protected function handleCommands() ? DomainCache::get('domain-commands') : $this->discoverCommands(); - static::$registeredCommands = []; - foreach ($commands as $command) { static::$registeredCommands[$command] = $command; } From 96a39fe3e86b7b0b479b0f8a1e7bea777db26862 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 11:53:24 -0500 Subject: [PATCH 093/169] Add optimize:clear calls --- tests/Autoload/CommandTest.php | 2 ++ tests/Autoload/FactoryTest.php | 1 + tests/Autoload/IgnoreTest.php | 4 +++- tests/Autoload/PolicyTest.php | 3 +++ tests/Autoload/ProviderTest.php | 3 +++ 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 4d0c308..f6032ff 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -17,6 +17,8 @@ ]; $this->setupTestApplication(); + + Artisan::call('optimize:clear'); }); afterEach(function () { diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index 912fe31..6b76d17 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -10,6 +10,7 @@ beforeEach(function () { $this->setupTestApplication(); + Artisan::call('optimize:clear'); }); afterEach(function () { diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index de0dc54..84d1093 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -24,7 +24,9 @@ ]; $this->setupTestApplication(); -})->skip(); + + Artisan::call('optimize:clear'); +}); afterEach(function () { DomainCache::clear(); diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index d95aa81..1ee2855 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -1,5 +1,6 @@ setupTestApplication(); + + Artisan::call('optimize:clear'); }); afterEach(function () { diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index abaae05..1abb8b0 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -1,5 +1,6 @@ setupTestApplication(); + + Artisan::call('optimize:clear'); }); afterEach(function () { From 56f837cef2a571239a108d5e70a25e22b5effb62 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 11:58:31 -0500 Subject: [PATCH 094/169] Restore manual composer reset --- tests/Command/InstallTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Command/InstallTest.php b/tests/Command/InstallTest.php index 76d4deb..aaff525 100644 --- a/tests/Command/InstallTest.php +++ b/tests/Command/InstallTest.php @@ -57,7 +57,7 @@ unlink(config_path('ddd.php')); // Reset composer back to the factory state - // $this->setAutoloadPathInComposer('Domain', 'src/Domain', reload: true); + $this->setAutoloadPathInComposer('Domain', 'src/Domain', reload: true); })->with([ ['src/Domain', 'Domain'], ['src/Domains', 'Domains'], From 6956a9b42a7c6fa856d1b49ce98269d5dcdee7bf Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 12:04:00 -0500 Subject: [PATCH 095/169] Remove --compact option. --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 3134fe4..e904482 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -54,4 +54,4 @@ jobs: run: composer show -D - name: Execute tests - run: vendor/bin/pest --ci --compact + run: vendor/bin/pest --ci From 91d9b7fdd87c90ff6f9ac4c5428f688dd189e7db Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 12:10:16 -0500 Subject: [PATCH 096/169] Flush the autoloader when autoload disabled. --- src/Support/AutoloadManager.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index 6bf405d..207660d 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -59,8 +59,10 @@ public function __construct(protected ?Container $container = null) public function boot() { + $this->booted = true; + if (! config()->has('ddd.autoload')) { - return $this; + return $this->flush(); } $this @@ -70,7 +72,7 @@ public function boot() ->when(config('ddd.autoload.policies') === true, fn () => $this->handlePolicies()) ->when(config('ddd.autoload.factories') === true, fn () => $this->handleFactories()); - $this->booted = true; + return $this; } public function isBooted(): bool From 77b5f0e40861123dcf5edc0d3a5c4c3a3aeddac0 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 12:14:50 -0500 Subject: [PATCH 097/169] Modify the post-test clean slate. --- tests/Config/ManagerTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Config/ManagerTest.php b/tests/Config/ManagerTest.php index 066c0ea..14f3e35 100644 --- a/tests/Config/ManagerTest.php +++ b/tests/Config/ManagerTest.php @@ -11,8 +11,8 @@ }); afterEach(function () { - $this->cleanSlate(); - Artisan::call('config:clear'); + $this->setupTestApplication(); + Artisan::call('optimize:clear'); }); it('can update and merge current config file with latest copy from package', function () { @@ -50,4 +50,6 @@ // Expect the updated config to have all top-level keys from the latest config expect($updatedConfig)->toHaveKeys(array_keys($this->latestConfig)); + + unlink(config_path('ddd.php')); }); From e4bb64db6446323942ea8368cb908bea2e4de16c Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 12:16:01 -0500 Subject: [PATCH 098/169] Delete published config file after test. --- tests/Setup/PublishTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Setup/PublishTest.php b/tests/Setup/PublishTest.php index eb6ac0f..c84afdd 100644 --- a/tests/Setup/PublishTest.php +++ b/tests/Setup/PublishTest.php @@ -17,6 +17,9 @@ ]); expect(file_exists($expectedPath))->toBeTrue(); + + // Delete it + unlink($expectedPath); }); it('can publish stubs', function () { From 7eb25006baf10506bf831f14613dc16847d42c1c Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 13:06:12 -0500 Subject: [PATCH 099/169] Refactor cleanSlate() to not rely on app being booted. --- tests/TestCase.php | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 6779581..be56d7c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -27,13 +27,20 @@ protected function setUp(): void ); }); - $this->beforeApplicationDestroyed(function () { - $this->cleanSlate(); - }); + // $this->beforeApplicationDestroyed(function () { + // $this->cleanSlate(); + // }); parent::setUp(); } + protected function tearDown(): void + { + $this->cleanSlate(); + + parent::tearDown(); + } + public static function configValues(array $values) { static::$configValues = $values; @@ -181,21 +188,22 @@ protected function composerReload() protected function cleanSlate() { - File::delete(base_path('config/ddd.php')); + $basePath = $this->getBasePath(); + + File::delete($basePath.'/config/ddd.php'); - // File::cleanDirectory(app_path()); - File::cleanDirectory(app_path('Models')); - File::cleanDirectory(base_path('database/factories')); + File::cleanDirectory($basePath.'/app/Models'); + File::cleanDirectory($basePath.'/database/factories'); - File::deleteDirectory(base_path('src')); - File::deleteDirectory(resource_path('stubs/ddd')); - File::deleteDirectory(base_path('stubs')); - File::deleteDirectory(base_path('Custom')); - File::deleteDirectory(app_path('Policies')); - File::deleteDirectory(app_path('Modules')); - File::deleteDirectory(base_path('bootstrap/cache/ddd')); + File::deleteDirectory($basePath.'/src'); + File::deleteDirectory($basePath.'/resources/stubs/ddd'); + File::deleteDirectory($basePath.'/stubs'); + File::deleteDirectory($basePath.'/Custom'); + File::deleteDirectory($basePath.'/app/Policies'); + File::deleteDirectory($basePath.'/app/Modules'); + File::deleteDirectory($basePath.'/bootstrap/cache/ddd'); - File::copy(__DIR__.'/.skeleton/composer.json', base_path('composer.json')); + File::copy(__DIR__.'/.skeleton/composer.json', $basePath.'/composer.json'); return $this; } From 2725cd298258359fd672a18b68342fbe28824038 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 13:34:50 -0500 Subject: [PATCH 100/169] Fix blueprint path resolution issue. --- src/Support/GeneratorBlueprint.php | 2 +- src/Support/Layer.php | 8 +++-- tests/Generator/CustomLayerTest.php | 48 ++++++++++++++--------------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/Support/GeneratorBlueprint.php b/src/Support/GeneratorBlueprint.php index 8c1bfd1..5a371de 100644 --- a/src/Support/GeneratorBlueprint.php +++ b/src/Support/GeneratorBlueprint.php @@ -82,7 +82,7 @@ protected function resolveSchema(): ObjectSchema default => $this->layer->namespaceFor($this->type), }; - $baseName = str($this->nameInput)->replace($namespace, '') + $baseName = str($this->nameInput) ->replace(['\\', '/'], '\\') ->trim('\\') ->when($this->type === 'factory', fn ($name) => $name->finish('Factory')) diff --git a/src/Support/Layer.php b/src/Support/Layer.php index c7a5a43..1e86c0f 100644 --- a/src/Support/Layer.php +++ b/src/Support/Layer.php @@ -34,10 +34,14 @@ public function path(?string $path = null): string return $this->path; } + $baseName = class_basename($path); + $relativePath = str($path) - ->replace($this->namespace, '') + ->beforeLast($baseName) + ->replaceStart($this->namespace, '') ->replace(['\\', '/'], DIRECTORY_SEPARATOR) - ->append('.php') + ->append($baseName) + ->finish('.php') ->toString(); return Path::join($this->path, $relativePath); diff --git a/tests/Generator/CustomLayerTest.php b/tests/Generator/CustomLayerTest.php index a760c62..8c470ca 100644 --- a/tests/Generator/CustomLayerTest.php +++ b/tests/Generator/CustomLayerTest.php @@ -35,27 +35,27 @@ expect(file_get_contents($expectedPath))->toContain("namespace {$expectedNamespace};"); })->with([ - 'action' => ['action', 'SomeAction', 'CustomLayer\Actions', 'src/CustomLayer/Actions/SomeAction.php'], - 'cast' => ['cast', 'SomeCast', 'CustomLayer\Casts', 'src/CustomLayer/Casts/SomeCast.php'], - 'channel' => ['channel', 'SomeChannel', 'CustomLayer\Channels', 'src/CustomLayer/Channels/SomeChannel.php'], - 'command' => ['command', 'SomeCommand', 'CustomLayer\Commands', 'src/CustomLayer/Commands/SomeCommand.php'], - 'event' => ['event', 'SomeEvent', 'CustomLayer\Events', 'src/CustomLayer/Events/SomeEvent.php'], - 'exception' => ['exception', 'SomeException', 'CustomLayer\Exceptions', 'src/CustomLayer/Exceptions/SomeException.php'], - 'job' => ['job', 'SomeJob', 'CustomLayer\Jobs', 'src/CustomLayer/Jobs/SomeJob.php'], - 'listener' => ['listener', 'SomeListener', 'CustomLayer\Listeners', 'src/CustomLayer/Listeners/SomeListener.php'], - 'mail' => ['mail', 'SomeMail', 'CustomLayer\Mail', 'src/CustomLayer/Mail/SomeMail.php'], - 'notification' => ['notification', 'SomeNotification', 'CustomLayer\Notifications', 'src/CustomLayer/Notifications/SomeNotification.php'], - 'observer' => ['observer', 'SomeObserver', 'CustomLayer\Observers', 'src/CustomLayer/Observers/SomeObserver.php'], - 'policy' => ['policy', 'SomePolicy', 'CustomLayer\Policies', 'src/CustomLayer/Policies/SomePolicy.php'], - 'provider' => ['provider', 'SomeProvider', 'CustomLayer\Providers', 'src/CustomLayer/Providers/SomeProvider.php'], - 'resource' => ['resource', 'SomeResource', 'CustomLayer\Resources', 'src/CustomLayer/Resources/SomeResource.php'], - 'rule' => ['rule', 'SomeRule', 'CustomLayer\Rules', 'src/CustomLayer/Rules/SomeRule.php'], - 'scope' => ['scope', 'SomeScope', 'CustomLayer\Scopes', 'src/CustomLayer/Scopes/SomeScope.php'], - 'seeder' => ['seeder', 'SomeSeeder', 'CustomLayer\Database\Seeders', 'src/CustomLayer/Database/Seeders/SomeSeeder.php'], - 'class' => ['class', 'SomeClass', 'CustomLayer', 'src/CustomLayer/SomeClass.php'], - 'enum' => ['enum', 'SomeEnum', 'CustomLayer\Enums', 'src/CustomLayer/Enums/SomeEnum.php'], - 'interface' => ['interface', 'SomeInterface', 'CustomLayer', 'src/CustomLayer/SomeInterface.php'], - 'trait' => ['trait', 'SomeTrait', 'CustomLayer', 'src/CustomLayer/SomeTrait.php'], + 'action' => ['action', 'CustomLayerAction', 'CustomLayer\Actions', 'src/CustomLayer/Actions/CustomLayerAction.php'], + 'cast' => ['cast', 'CustomLayerCast', 'CustomLayer\Casts', 'src/CustomLayer/Casts/CustomLayerCast.php'], + 'channel' => ['channel', 'CustomLayerChannel', 'CustomLayer\Channels', 'src/CustomLayer/Channels/CustomLayerChannel.php'], + 'command' => ['command', 'CustomLayerCommand', 'CustomLayer\Commands', 'src/CustomLayer/Commands/CustomLayerCommand.php'], + 'event' => ['event', 'CustomLayerEvent', 'CustomLayer\Events', 'src/CustomLayer/Events/CustomLayerEvent.php'], + 'exception' => ['exception', 'CustomLayerException', 'CustomLayer\Exceptions', 'src/CustomLayer/Exceptions/CustomLayerException.php'], + 'job' => ['job', 'CustomLayerJob', 'CustomLayer\Jobs', 'src/CustomLayer/Jobs/CustomLayerJob.php'], + 'listener' => ['listener', 'CustomLayerListener', 'CustomLayer\Listeners', 'src/CustomLayer/Listeners/CustomLayerListener.php'], + 'mail' => ['mail', 'CustomLayerMail', 'CustomLayer\Mail', 'src/CustomLayer/Mail/CustomLayerMail.php'], + 'notification' => ['notification', 'CustomLayerNotification', 'CustomLayer\Notifications', 'src/CustomLayer/Notifications/CustomLayerNotification.php'], + 'observer' => ['observer', 'CustomLayerObserver', 'CustomLayer\Observers', 'src/CustomLayer/Observers/CustomLayerObserver.php'], + 'policy' => ['policy', 'CustomLayerPolicy', 'CustomLayer\Policies', 'src/CustomLayer/Policies/CustomLayerPolicy.php'], + 'provider' => ['provider', 'CustomLayerServiceProvider', 'CustomLayer\Providers', 'src/CustomLayer/Providers/CustomLayerServiceProvider.php'], + 'resource' => ['resource', 'CustomLayerResource', 'CustomLayer\Resources', 'src/CustomLayer/Resources/CustomLayerResource.php'], + 'rule' => ['rule', 'CustomLayerRule', 'CustomLayer\Rules', 'src/CustomLayer/Rules/CustomLayerRule.php'], + 'scope' => ['scope', 'CustomLayerScope', 'CustomLayer\Scopes', 'src/CustomLayer/Scopes/CustomLayerScope.php'], + 'seeder' => ['seeder', 'CustomLayerSeeder', 'CustomLayer\Database\Seeders', 'src/CustomLayer/Database/Seeders/CustomLayerSeeder.php'], + 'class' => ['class', 'CustomLayerClass', 'CustomLayer', 'src/CustomLayer/CustomLayerClass.php'], + 'enum' => ['enum', 'CustomLayerEnum', 'CustomLayer\Enums', 'src/CustomLayer/Enums/CustomLayerEnum.php'], + 'interface' => ['interface', 'CustomLayerInterface', 'CustomLayer', 'src/CustomLayer/CustomLayerInterface.php'], + 'trait' => ['trait', 'CustomLayerTrait', 'CustomLayer', 'src/CustomLayer/CustomLayerTrait.php'], ]); it('ignores custom layer if object belongs in the application layer', function ($type, $objectName, $expectedNamespace, $expectedPath) { @@ -86,7 +86,7 @@ expect(file_get_contents($expectedPath))->toContain("namespace {$expectedNamespace};"); })->with([ - 'request' => ['request', 'SomeRequest', 'Application\CustomLayer\Requests', 'src/Application/CustomLayer/Requests/SomeRequest.php'], - 'controller' => ['controller', 'SomeController', 'Application\CustomLayer\Controllers', 'src/Application/CustomLayer/Controllers/SomeController.php'], - 'middleware' => ['middleware', 'SomeMiddleware', 'Application\CustomLayer\Middleware', 'src/Application/CustomLayer/Middleware/SomeMiddleware.php'], + 'request' => ['request', 'CustomLayerRequest', 'Application\CustomLayer\Requests', 'src/Application/CustomLayer/Requests/CustomLayerRequest.php'], + 'controller' => ['controller', 'CustomLayerController', 'Application\CustomLayer\Controllers', 'src/Application/CustomLayer/Controllers/CustomLayerController.php'], + 'middleware' => ['middleware', 'CustomLayerMiddleware', 'Application\CustomLayer\Middleware', 'src/Application/CustomLayer/Middleware/CustomLayerMiddleware.php'], ]); From 18ee64d54f0a0ac373028a52cb01d68a0355099f Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 13:58:33 -0500 Subject: [PATCH 101/169] Cleanup. --- tests/Autoload/CommandTest.php | 38 --------------------------------- tests/Autoload/ProviderTest.php | 31 --------------------------- 2 files changed, 69 deletions(-) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index f6032ff..a0cc1a4 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -1,11 +1,9 @@ getRegisteredCommands())->toBeEmpty(); }); - - // it('does not register the commands', function () { - // config()->set('ddd.autoload.commands', false); - - // Autoload::boot(); - - // $artisanCommands = collect(Artisan::all()); - - // expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); - // }); }); describe('when ddd.autoload.commands = true', function () { @@ -55,11 +43,6 @@ $mock->boot(); expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); - // Autoload::boot(); - - // $artisanCommands = collect(Artisan::all()); - - // expect($artisanCommands)->toHaveKeys(array_keys($this->commands)); }); }); @@ -73,17 +56,6 @@ $mock->boot(); expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing([]); - - // Autoload::boot(); - - // $artisanCommands = collect(Artisan::all()); - - // expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); - - // // commands should not be recognized due to cached empty-state - // foreach ($this->commands as $command => $class) { - // expect(fn () => Artisan::call($command))->toThrow(CommandNotFoundException::class); - // } }); it('can bust the cache', function () { @@ -94,15 +66,5 @@ $mock->boot(); expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); - - // Autoload::boot(); - - // $artisanCommands = collect(Artisan::all()); - - // expect($artisanCommands)->toHaveKeys(array_keys($this->commands)); - - // foreach ($this->commands as $command => $class) { - // $this->artisan($command)->assertSuccessful(); - // } }); }); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 1abb8b0..99e0104 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -1,7 +1,6 @@ shouldNotReceive('handleProviders'); $mock->boot(); - - // Autoload::boot(); - - // collect($this->providers)->each( - // fn ($provider) => expect(app()->getProvider($provider))->toBeNull() - // ); }); it('does not register the providers', function () { @@ -46,12 +39,6 @@ $mock->boot(); expect($mock->getRegisteredProviders())->toBeEmpty(); - - // Autoload::boot(); - - // collect($this->providers)->each( - // fn($provider) => expect(app()->getProvider($provider))->toBeNull() - // ); }); }); @@ -71,12 +58,6 @@ $mock->boot(); expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); - - // Autoload::boot(); - - // collect($this->providers)->each( - // fn($provider) => expect(app()->getProvider($provider))->toBeInstanceOf($provider) - // ); }); }); @@ -90,12 +71,6 @@ $mock->boot(); expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing([]); - - // Autoload::boot(); - - // collect($this->providers)->each( - // fn($provider) => expect(app()->getProvider($provider))->toBeNull() - // ); }); it('can bust the cache', function () { @@ -108,11 +83,5 @@ $mock->boot(); expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); - - // Autoload::boot(); - - // collect($this->providers)->each( - // fn($provider) => expect(app()->getProvider($provider))->toBeInstanceOf($provider) - // ); }); }); From 36af4eb241faf6f20fc0c2e29d14f9bf811c9337 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 18:31:14 -0500 Subject: [PATCH 102/169] Tighten up the generator blueprint and add more test coverage. --- src/Commands/Concerns/CanPromptForDomain.php | 35 ----- .../Concerns/ResolvesDomainFromInput.php | 34 ++++- src/Support/GeneratorBlueprint.php | 43 ++++-- tests/Datasets/GeneratorSchemas.php | 27 ++++ tests/Support/BlueprintTest.php | 123 ++++++++++++++++++ tests/Support/DomainTest.php | 2 + 6 files changed, 212 insertions(+), 52 deletions(-) delete mode 100644 src/Commands/Concerns/CanPromptForDomain.php create mode 100644 tests/Datasets/GeneratorSchemas.php create mode 100644 tests/Support/BlueprintTest.php diff --git a/src/Commands/Concerns/CanPromptForDomain.php b/src/Commands/Concerns/CanPromptForDomain.php deleted file mode 100644 index f4d95e9..0000000 --- a/src/Commands/Concerns/CanPromptForDomain.php +++ /dev/null @@ -1,35 +0,0 @@ -mapWithKeys(fn ($name) => [Str::lower($name) => $name]); - - // Prompt for the domain - $domainName = suggest( - label: 'What is the domain?', - options: fn ($value) => collect($choices) - ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true)) - ->toArray(), - placeholder: 'Start typing to search...', - required: true - ); - - // Normalize the case of the domain name - // if it is an existing domain. - if ($match = $choices->get(Str::lower($domainName))) { - $domainName = $match; - } - - return $domainName; - } -} diff --git a/src/Commands/Concerns/ResolvesDomainFromInput.php b/src/Commands/Concerns/ResolvesDomainFromInput.php index 103dd77..efe35a0 100644 --- a/src/Commands/Concerns/ResolvesDomainFromInput.php +++ b/src/Commands/Concerns/ResolvesDomainFromInput.php @@ -4,13 +4,15 @@ use Illuminate\Support\Str; use Lunarstorm\LaravelDDD\Support\Domain; +use Lunarstorm\LaravelDDD\Support\DomainResolver; use Lunarstorm\LaravelDDD\Support\GeneratorBlueprint; use Symfony\Component\Console\Input\InputOption; +use function Laravel\Prompts\suggest; + trait ResolvesDomainFromInput { - use CanPromptForDomain, - HandleHooks, + use HandleHooks, HasGeneratorBlueprint, QualifiesDomainModels; @@ -48,6 +50,30 @@ protected function qualifyClass($name) return $this->blueprint->qualifyClass($name); } + protected function promptForDomainName(): string + { + $choices = collect(DomainResolver::domainChoices()) + ->mapWithKeys(fn ($name) => [Str::lower($name) => $name]); + + // Prompt for the domain + $domainName = suggest( + label: 'What is the domain?', + options: fn ($value) => collect($choices) + ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true)) + ->toArray(), + placeholder: 'Start typing to search...', + required: true + ); + + // Normalize the case of the domain name + // if it is an existing domain. + if ($match = $choices->get(Str::lower($domainName))) { + $domainName = $match; + } + + return $domainName; + } + protected function beforeHandle() { $nameInput = $this->getNameInput(); @@ -72,9 +98,11 @@ protected function beforeHandle() }; $this->blueprint = new GeneratorBlueprint( + commandName: $this->getName(), nameInput: $nameInput, domainName: $domainName, - command: $this, + arguments: $this->arguments(), + options: $this->options(), ); $this->input->setArgument('name', $this->blueprint->nameInput); diff --git a/src/Support/GeneratorBlueprint.php b/src/Support/GeneratorBlueprint.php index 5a371de..00e1354 100644 --- a/src/Support/GeneratorBlueprint.php +++ b/src/Support/GeneratorBlueprint.php @@ -11,6 +11,10 @@ class GeneratorBlueprint { public string $nameInput; + public string $normalizedName; + + public string $baseName; + public string $domainName; public ?Domain $domain = null; @@ -26,27 +30,42 @@ class GeneratorBlueprint public string $type; public function __construct( + string $commandName, string $nameInput, string $domainName, - Command $command, + array $arguments = [], + array $options = [], ) { - $this->nameInput = str($nameInput)->studly()->replace(['.', '\\', '/'], '/')->toString(); - - $this->domain = new Domain($domainName); + $this->command = new CommandContext($commandName, $arguments, $options); - $this->domainName = $this->domain->domainWithSubdomain; - - $this->command = new CommandContext($command->getName(), $command->arguments(), $command->options()); + $this->nameInput = str($nameInput)->toString(); $this->isAbsoluteName = str($this->nameInput)->startsWith('/'); $this->type = $this->guessObjectType(); + $this->normalizedName = Path::normalizeNamespace( + str($nameInput) + ->studly() + ->replace(['.', '\\', '/'], '\\') + ->trim('\\') + ->when($this->type === 'factory', fn ($name) => $name->finish('Factory')) + ->toString() + ); + + $this->baseName = class_basename($this->normalizedName); + + $this->domain = new Domain($domainName); + + $this->domainName = $this->domain->domainWithSubdomain; + $this->layer = DomainResolver::resolveLayer($this->domainName, $this->type); $this->schema = $this->resolveSchema(); } + public static function capture(Command $command) {} + protected function guessObjectType(): string { return match ($this->command->name) { @@ -82,16 +101,12 @@ protected function resolveSchema(): ObjectSchema default => $this->layer->namespaceFor($this->type), }; - $baseName = str($this->nameInput) - ->replace(['\\', '/'], '\\') - ->trim('\\') - ->when($this->type === 'factory', fn ($name) => $name->finish('Factory')) + $fullyQualifiedName = str($this->normalizedName) + ->start($namespace.'\\') ->toString(); - $fullyQualifiedName = $namespace.'\\'.$baseName; - return new ObjectSchema( - name: $this->nameInput, + name: $this->normalizedName, namespace: $namespace, fullyQualifiedName: $fullyQualifiedName, path: $this->layer->path($fullyQualifiedName), diff --git a/tests/Datasets/GeneratorSchemas.php b/tests/Datasets/GeneratorSchemas.php new file mode 100644 index 0000000..c899f7e --- /dev/null +++ b/tests/Datasets/GeneratorSchemas.php @@ -0,0 +1,27 @@ + [ + 'ddd:model', + 'Invoice', + 'Invoicing', + [ + 'name' => 'Invoice', + 'namespace' => 'Domain\Invoicing\Models', + 'fullyQualifiedName' => 'Domain\Invoicing\Models\Invoice', + 'path' => 'src/Domain/Invoicing/Models/Invoice.php', + ], + ], + + 'ddd:model Invoicing:Invoice' => [ + 'ddd:model', + 'InvoicingEntry', + 'Invoicing', + [ + 'name' => 'Invoice', + 'namespace' => 'Domain\Invoicing\Models', + 'fullyQualifiedName' => 'Domain\Invoicing\Models\Invoice', + 'path' => 'src/Domain/Invoicing/Models/Invoice.php', + ], + ], +]); diff --git a/tests/Support/BlueprintTest.php b/tests/Support/BlueprintTest.php new file mode 100644 index 0000000..a633b79 --- /dev/null +++ b/tests/Support/BlueprintTest.php @@ -0,0 +1,123 @@ +set([ + 'ddd.domain_path' => 'src/Domain', + 'ddd.domain_namespace' => 'Domain', + 'ddd.application_namespace' => 'Application', + 'ddd.application_path' => 'src/Application', + 'ddd.application_objects' => [ + 'controller', + 'request', + 'middleware', + ], + 'ddd.layers' => [ + 'Infrastructure' => 'src/Infrastructure', + 'NestedLayer' => 'src/Nested/Layer', + 'AppNested' => 'app/Nested', + ], + ]); +}); + +it('handles nested objects', function ($nameInput, $normalized) { + $blueprint = new GeneratorBlueprint( + commandName: 'ddd:model', + nameInput: $nameInput, + domainName: 'SomeDomain', + ); + + expect($blueprint->schema) + ->name->toBe($normalized) + ->namespace->toBe('Domain\SomeDomain\Models'); +})->with([ + ['Nested\\Thing', 'Nested\\Thing'], + ['Nested/Thing', 'Nested\\Thing'], + ['Nested/Thing/Deeply', 'Nested\\Thing\\Deeply'], + ['Nested\\Thing/Deeply', 'Nested\\Thing\\Deeply'], +]); + +it('handles objects in the application layer', function ($command, $domainName, $nameInput, $expectedName, $expectedNamespace, $expectedFqn, $expectedPath) { + $blueprint = new GeneratorBlueprint( + commandName: $command, + nameInput: $nameInput, + domainName: $domainName, + ); + + expect($blueprint->schema) + ->name->toBe($expectedName) + ->namespace->toBe($expectedNamespace) + ->fullyQualifiedName->toBe($expectedFqn) + ->path->toBe($expectedPath); +})->with([ + ['ddd:controller', 'SomeDomain', 'ApplicationController', 'ApplicationController', 'Application\\SomeDomain\\Controllers', 'Application\\SomeDomain\\Controllers\\ApplicationController', 'src/Application/SomeDomain/Controllers/ApplicationController.php'], + ['ddd:controller', 'SomeDomain', 'Application', 'Application', 'Application\\SomeDomain\\Controllers', 'Application\\SomeDomain\\Controllers\\Application', 'src/Application/SomeDomain/Controllers/Application.php'], + ['ddd:middleware', 'SomeDomain', 'CrazyMiddleware', 'CrazyMiddleware', 'Application\\SomeDomain\\Middleware', 'Application\\SomeDomain\\Middleware\\CrazyMiddleware', 'src/Application/SomeDomain/Middleware/CrazyMiddleware.php'], + ['ddd:request', 'SomeDomain', 'LazyRequest', 'LazyRequest', 'Application\\SomeDomain\\Requests', 'Application\\SomeDomain\\Requests\\LazyRequest', 'src/Application/SomeDomain/Requests/LazyRequest.php'], +]); + +it('handles objects in custom layers', function ($command, $domainName, $nameInput, $expectedName, $expectedNamespace, $expectedFqn, $expectedPath) { + $blueprint = new GeneratorBlueprint( + commandName: $command, + nameInput: $nameInput, + domainName: $domainName, + ); + + expect($blueprint->schema) + ->name->toBe($expectedName) + ->namespace->toBe($expectedNamespace) + ->fullyQualifiedName->toBe($expectedFqn) + ->path->toBe($expectedPath); +})->with([ + ['ddd:model', 'Infrastructure', 'System', 'System', 'Infrastructure\\Models', 'Infrastructure\\Models\\System', 'src/Infrastructure/Models/System.php'], + ['ddd:factory', 'Infrastructure', 'System', 'SystemFactory', 'Infrastructure\\Database\\Factories', 'Infrastructure\\Database\\Factories\\SystemFactory', 'src/Infrastructure/Database/Factories/SystemFactory.php'], + ['ddd:provider', 'Infrastructure', 'InfrastructureServiceProvider', 'InfrastructureServiceProvider', 'Infrastructure\\Providers', 'Infrastructure\\Providers\\InfrastructureServiceProvider', 'src/Infrastructure/Providers/InfrastructureServiceProvider.php'], + ['ddd:provider', 'Infrastructure', 'Infrastructure\\InfrastructureServiceProvider', 'Infrastructure\\InfrastructureServiceProvider', 'Infrastructure\\Providers', 'Infrastructure\\Providers\\Infrastructure\\InfrastructureServiceProvider', 'src/Infrastructure/Providers/Infrastructure/InfrastructureServiceProvider.php'], + ['ddd:provider', 'Infrastructure', 'InfrastructureServiceProvider', 'InfrastructureServiceProvider', 'Infrastructure\\Providers', 'Infrastructure\\Providers\\InfrastructureServiceProvider', 'src/Infrastructure/Providers/InfrastructureServiceProvider.php'], + ['ddd:provider', 'AppNested', 'CrazyServiceProvider', 'CrazyServiceProvider', 'AppNested\\Providers', 'AppNested\\Providers\\CrazyServiceProvider', 'app/Nested/Providers/CrazyServiceProvider.php'], + ['ddd:provider', 'NestedLayer', 'CrazyServiceProvider', 'CrazyServiceProvider', 'NestedLayer\\Providers', 'NestedLayer\\Providers\\CrazyServiceProvider', 'src/Nested/Layer/Providers/CrazyServiceProvider.php'], +]); + +it('handles objects whose name contains the domain name', function ($command, $domainName, $nameInput, $expectedName, $expectedNamespace, $expectedFqn, $expectedPath) { + $blueprint = new GeneratorBlueprint( + commandName: $command, + nameInput: $nameInput, + domainName: $domainName, + ); + + expect($blueprint->schema) + ->name->toBe($expectedName) + ->namespace->toBe($expectedNamespace) + ->fullyQualifiedName->toBe($expectedFqn) + ->path->toBe($expectedPath); +})->with([ + ['ddd:model', 'SomeDomain', 'SomeDomain', 'SomeDomain', 'Domain\\SomeDomain\\Models', 'Domain\\SomeDomain\\Models\\SomeDomain', 'src/Domain/SomeDomain/Models/SomeDomain.php'], + ['ddd:model', 'SomeDomain', 'SomeDomainModel', 'SomeDomainModel', 'Domain\\SomeDomain\\Models', 'Domain\\SomeDomain\\Models\\SomeDomainModel', 'src/Domain/SomeDomain/Models/SomeDomainModel.php'], + ['ddd:model', 'SomeDomain', 'Nested\\SomeDomain', 'Nested\\SomeDomain', 'Domain\\SomeDomain\\Models', 'Domain\\SomeDomain\\Models\\Nested\\SomeDomain', 'src/Domain/SomeDomain/Models/Nested/SomeDomain.php'], + ['ddd:model', 'SomeDomain', 'SomeDomain\\SomeDomain', 'SomeDomain\\SomeDomain', 'Domain\\SomeDomain\\Models', 'Domain\\SomeDomain\\Models\\SomeDomain\\SomeDomain', 'src/Domain/SomeDomain/Models/SomeDomain/SomeDomain.php'], + ['ddd:model', 'SomeDomain', 'SomeDomain\\SomeDomainModel', 'SomeDomain\\SomeDomainModel', 'Domain\\SomeDomain\\Models', 'Domain\\SomeDomain\\Models\\SomeDomain\\SomeDomainModel', 'src/Domain/SomeDomain/Models/SomeDomain/SomeDomainModel.php'], + ['ddd:model', 'Infrastructure', 'Infrastructure', 'Infrastructure', 'Infrastructure\\Models', 'Infrastructure\\Models\\Infrastructure', 'src/Infrastructure/Models/Infrastructure.php'], + ['ddd:model', 'Infrastructure', 'Nested\\Infrastructure', 'Nested\\Infrastructure', 'Infrastructure\\Models', 'Infrastructure\\Models\\Nested\\Infrastructure', 'src/Infrastructure/Models/Nested/Infrastructure.php'], + ['ddd:controller', 'SomeDomain', 'SomeDomain', 'SomeDomain', 'Application\\SomeDomain\\Controllers', 'Application\\SomeDomain\\Controllers\\SomeDomain', 'src/Application/SomeDomain/Controllers/SomeDomain.php'], + ['ddd:controller', 'SomeDomain', 'SomeDomainController', 'SomeDomainController', 'Application\\SomeDomain\\Controllers', 'Application\\SomeDomain\\Controllers\\SomeDomainController', 'src/Application/SomeDomain/Controllers/SomeDomainController.php'], + ['ddd:controller', 'SomeDomain', 'SomeDomain\\SomeDomain', 'SomeDomain\\SomeDomain', 'Application\\SomeDomain\\Controllers', 'Application\\SomeDomain\\Controllers\\SomeDomain\\SomeDomain', 'src/Application/SomeDomain/Controllers/SomeDomain/SomeDomain.php'], +]); + +it('handles absolute-path names', function ($command, $domainName, $nameInput, $expectedName, $expectedNamespace, $expectedFqn, $expectedPath) { + $blueprint = new GeneratorBlueprint( + commandName: $command, + nameInput: $nameInput, + domainName: $domainName, + ); + + expect($blueprint->schema) + ->name->toBe($expectedName) + ->namespace->toBe($expectedNamespace) + ->fullyQualifiedName->toBe($expectedFqn) + ->path->toBe($expectedPath); +})->with([ + ['ddd:model', 'SomeDomain', '/RootModel', 'RootModel', 'Domain\\SomeDomain', 'Domain\\SomeDomain\\RootModel', 'src/Domain/SomeDomain/RootModel.php'], + ['ddd:model', 'SomeDomain', '/CustomLocation/Thing', 'CustomLocation\\Thing', 'Domain\\SomeDomain', 'Domain\\SomeDomain\\CustomLocation\\Thing', 'src/Domain/SomeDomain/CustomLocation/Thing.php'], + ['ddd:model', 'SomeDomain', '/Custom/Nested/Thing', 'Custom\\Nested\\Thing', 'Domain\\SomeDomain', 'Domain\\SomeDomain\\Custom\\Nested\\Thing', 'src/Domain/SomeDomain/Custom/Nested/Thing.php'], +]); diff --git a/tests/Support/DomainTest.php b/tests/Support/DomainTest.php index a88fbcf..accef75 100644 --- a/tests/Support/DomainTest.php +++ b/tests/Support/DomainTest.php @@ -28,6 +28,8 @@ ->path->toBe(Path::normalize($expectedPath)); })->with([ ['Reporting', 'InvoiceReport', 'Domain\\Reporting\\Models\\InvoiceReport', 'src/Domain/Reporting/Models/InvoiceReport.php'], + ['Reporting', 'ReportingLog', 'Domain\\Reporting\\Models\\ReportingLog', 'src/Domain/Reporting/Models/ReportingLog.php'], + ['Reporting', 'Reporting\Log', 'Domain\\Reporting\\Models\\Reporting\\Log', 'src/Domain/Reporting/Models/Reporting/Log.php'], ['Reporting.Internal', 'InvoiceReport', 'Domain\\Reporting\\Internal\\Models\\InvoiceReport', 'src/Domain/Reporting/Internal/Models/InvoiceReport.php'], ]); From 482dc1262cd6deff716523264e09ff4256e750a7 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 18:43:54 -0500 Subject: [PATCH 103/169] Fix tests. --- src/Commands/DomainModelMakeCommand.php | 5 ++++- src/Commands/DomainViewModelMakeCommand.php | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Commands/DomainModelMakeCommand.php b/src/Commands/DomainModelMakeCommand.php index de45c24..9739576 100644 --- a/src/Commands/DomainModelMakeCommand.php +++ b/src/Commands/DomainModelMakeCommand.php @@ -92,7 +92,10 @@ protected function createBaseModelIfNeeded() $domain = DomainResolver::guessDomainFromClass($baseModel); - $name = Str::after($baseModel, $domain); + $name = str($baseModel) + ->after($domain) + ->replace(['\\', '/'], '/') + ->toString(); $this->call(DomainBaseModelMakeCommand::class, [ '--domain' => $domain, diff --git a/src/Commands/DomainViewModelMakeCommand.php b/src/Commands/DomainViewModelMakeCommand.php index c457405..e75700b 100644 --- a/src/Commands/DomainViewModelMakeCommand.php +++ b/src/Commands/DomainViewModelMakeCommand.php @@ -2,7 +2,6 @@ namespace Lunarstorm\LaravelDDD\Commands; -use Illuminate\Support\Str; use Lunarstorm\LaravelDDD\Commands\Concerns\HasDomainStubs; use Lunarstorm\LaravelDDD\Support\DomainResolver; @@ -55,7 +54,10 @@ public function handle() $domain = DomainResolver::guessDomainFromClass($baseViewModel); - $name = Str::after($baseViewModel, $domain); + $name = str($baseViewModel) + ->after($domain) + ->replace(['\\', '/'], '/') + ->toString(); $this->call(DomainBaseViewModelMakeCommand::class, [ '--domain' => $domain, From 5e17ad28290c951122737bd81c02afdb04627af4 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 18:51:42 -0500 Subject: [PATCH 104/169] Update hooks --- tests/Autoload/IgnoreTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 84d1093..5a9a01b 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -25,11 +25,15 @@ $this->setupTestApplication(); + DomainCache::clear(); + Artisan::call('optimize:clear'); }); afterEach(function () { DomainCache::clear(); + + Artisan::call('optimize:clear'); }); it('can ignore folders when autoloading', function () { From 0ff126b8f39bb544dba2a818e865fb160d95519d Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 18:52:51 -0500 Subject: [PATCH 105/169] Clear caches after app created. --- tests/TestCase.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index be56d7c..9b165ba 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Config\Repository; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\File; use Lunarstorm\LaravelDDD\LaravelDDDServiceProvider; use Lunarstorm\LaravelDDD\Support\DomainCache; @@ -25,11 +26,14 @@ protected function setUp(): void Factory::guessFactoryNamesUsing( fn (string $modelName) => 'Lunarstorm\\LaravelDDD\\Database\\Factories\\'.class_basename($modelName).'Factory' ); + + DomainCache::clear(); + Artisan::call('optimize:clear'); }); - // $this->beforeApplicationDestroyed(function () { - // $this->cleanSlate(); - // }); + $this->beforeApplicationDestroyed(function () { + $this->cleanSlate(); + }); parent::setUp(); } From 9e3a6ecb03d82ff41dcba5d9657da420c611ff65 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 19:54:08 -0500 Subject: [PATCH 106/169] Remove autoload run test. --- tests/Support/AutoloaderTest.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/Support/AutoloaderTest.php b/tests/Support/AutoloaderTest.php index 0e83050..1221cfb 100644 --- a/tests/Support/AutoloaderTest.php +++ b/tests/Support/AutoloaderTest.php @@ -1,16 +1,11 @@ setupTestApplication(); }); -it('can run', function () { - Autoload::run(); -})->throwsNoExceptions(); - beforeEach(function () { config([ 'ddd.domain_path' => 'src/Domain', From e45a2c2bdbf4bce933dd716fc35c37d21fb1d49e Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 20:01:18 -0500 Subject: [PATCH 107/169] ddd:clear before and after each test --- tests/Autoload/IgnoreTest.php | 6 ++---- tests/TestCase.php | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 5a9a01b..6ed4734 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -26,14 +26,12 @@ $this->setupTestApplication(); DomainCache::clear(); - - Artisan::call('optimize:clear'); + Artisan::call('ddd:clear'); }); afterEach(function () { DomainCache::clear(); - - Artisan::call('optimize:clear'); + Artisan::call('ddd:clear'); }); it('can ignore folders when autoloading', function () { diff --git a/tests/TestCase.php b/tests/TestCase.php index 9b165ba..c3bb2f7 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -28,7 +28,7 @@ protected function setUp(): void ); DomainCache::clear(); - Artisan::call('optimize:clear'); + Artisan::call('ddd:clear'); }); $this->beforeApplicationDestroyed(function () { From 18ca89bcb26baec4c285a07a1af7157d7768f529 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 20:12:52 -0500 Subject: [PATCH 108/169] Normalize path expectation. --- tests/Expectations.php | 4 ++++ tests/Support/BlueprintTest.php | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/Expectations.php b/tests/Expectations.php index f2cdd6d..8e4d405 100644 --- a/tests/Expectations.php +++ b/tests/Expectations.php @@ -15,6 +15,10 @@ return $this->toContain(Path::normalize($path)); }); +expect()->extend('toEqualPath', function ($path) { + return $this->toEqual(Path::normalize($path)); +}); + expect()->extend('toGenerateFileWithNamespace', function ($expectedPath, $expectedNamespace) { $command = $this->value; diff --git a/tests/Support/BlueprintTest.php b/tests/Support/BlueprintTest.php index a633b79..23b72c0 100644 --- a/tests/Support/BlueprintTest.php +++ b/tests/Support/BlueprintTest.php @@ -49,7 +49,7 @@ ->name->toBe($expectedName) ->namespace->toBe($expectedNamespace) ->fullyQualifiedName->toBe($expectedFqn) - ->path->toBe($expectedPath); + ->path->toEqualPath($expectedPath); })->with([ ['ddd:controller', 'SomeDomain', 'ApplicationController', 'ApplicationController', 'Application\\SomeDomain\\Controllers', 'Application\\SomeDomain\\Controllers\\ApplicationController', 'src/Application/SomeDomain/Controllers/ApplicationController.php'], ['ddd:controller', 'SomeDomain', 'Application', 'Application', 'Application\\SomeDomain\\Controllers', 'Application\\SomeDomain\\Controllers\\Application', 'src/Application/SomeDomain/Controllers/Application.php'], @@ -68,7 +68,7 @@ ->name->toBe($expectedName) ->namespace->toBe($expectedNamespace) ->fullyQualifiedName->toBe($expectedFqn) - ->path->toBe($expectedPath); + ->path->toEqualPath($expectedPath); })->with([ ['ddd:model', 'Infrastructure', 'System', 'System', 'Infrastructure\\Models', 'Infrastructure\\Models\\System', 'src/Infrastructure/Models/System.php'], ['ddd:factory', 'Infrastructure', 'System', 'SystemFactory', 'Infrastructure\\Database\\Factories', 'Infrastructure\\Database\\Factories\\SystemFactory', 'src/Infrastructure/Database/Factories/SystemFactory.php'], @@ -90,7 +90,7 @@ ->name->toBe($expectedName) ->namespace->toBe($expectedNamespace) ->fullyQualifiedName->toBe($expectedFqn) - ->path->toBe($expectedPath); + ->path->toEqualPath($expectedPath); })->with([ ['ddd:model', 'SomeDomain', 'SomeDomain', 'SomeDomain', 'Domain\\SomeDomain\\Models', 'Domain\\SomeDomain\\Models\\SomeDomain', 'src/Domain/SomeDomain/Models/SomeDomain.php'], ['ddd:model', 'SomeDomain', 'SomeDomainModel', 'SomeDomainModel', 'Domain\\SomeDomain\\Models', 'Domain\\SomeDomain\\Models\\SomeDomainModel', 'src/Domain/SomeDomain/Models/SomeDomainModel.php'], @@ -115,7 +115,7 @@ ->name->toBe($expectedName) ->namespace->toBe($expectedNamespace) ->fullyQualifiedName->toBe($expectedFqn) - ->path->toBe($expectedPath); + ->path->toEqualPath($expectedPath); })->with([ ['ddd:model', 'SomeDomain', '/RootModel', 'RootModel', 'Domain\\SomeDomain', 'Domain\\SomeDomain\\RootModel', 'src/Domain/SomeDomain/RootModel.php'], ['ddd:model', 'SomeDomain', '/CustomLocation/Thing', 'CustomLocation\\Thing', 'Domain\\SomeDomain', 'Domain\\SomeDomain\\CustomLocation\\Thing', 'src/Domain/SomeDomain/CustomLocation/Thing.php'], From fb79601e31bc59ce766d14839a64b08214f68929 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 20:37:34 -0500 Subject: [PATCH 109/169] Normalize hooks to use ddd:clear --- tests/Autoload/CommandTest.php | 4 +++- tests/Autoload/FactoryTest.php | 4 +++- tests/Autoload/PolicyTest.php | 4 +++- tests/Autoload/ProviderTest.php | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index a0cc1a4..a8962fd 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -16,11 +16,13 @@ $this->setupTestApplication(); - Artisan::call('optimize:clear'); + DomainCache::clear(); + Artisan::call('ddd:clear'); }); afterEach(function () { DomainCache::clear(); + Artisan::call('ddd:clear'); }); describe('when ddd.autoload.commands = false', function () { diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index 6b76d17..dc8e822 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -10,11 +10,13 @@ beforeEach(function () { $this->setupTestApplication(); - Artisan::call('optimize:clear'); + DomainCache::clear(); + Artisan::call('ddd:clear'); }); afterEach(function () { DomainCache::clear(); + Artisan::call('ddd:clear'); }); describe('when ddd.autoload.factories = true', function () { diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index 1ee2855..3b11b25 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -17,11 +17,13 @@ $this->setupTestApplication(); - Artisan::call('optimize:clear'); + DomainCache::clear(); + Artisan::call('ddd:clear'); }); afterEach(function () { DomainCache::clear(); + Artisan::call('ddd:clear'); }); describe('when ddd.autoload.policies = false', function () { diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 99e0104..4da3952 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -16,11 +16,13 @@ $this->setupTestApplication(); - Artisan::call('optimize:clear'); + DomainCache::clear(); + Artisan::call('ddd:clear'); }); afterEach(function () { DomainCache::clear(); + Artisan::call('ddd:clear'); }); describe('when ddd.autoload.providers = false', function () { From 57774a46b0afc6605d2554cf36a2fffe815cb2d8 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 20:54:14 -0500 Subject: [PATCH 110/169] User autoload ->run instead of ->boot --- tests/Autoload/CommandTest.php | 8 ++++---- tests/Autoload/FactoryTest.php | 8 ++++---- tests/Autoload/PolicyTest.php | 8 ++++---- tests/Autoload/ProviderTest.php | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index a8962fd..1138c12 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -31,7 +31,7 @@ $mock = AutoloadManager::partialMock(); $mock->shouldNotReceive('handleCommands'); - $mock->boot(); + $mock->run(); expect($mock->getRegisteredCommands())->toBeEmpty(); }); @@ -42,7 +42,7 @@ config()->set('ddd.autoload.commands', true); $mock = AutoloadManager::partialMock(); - $mock->boot(); + $mock->run(); expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); }); @@ -55,7 +55,7 @@ config()->set('ddd.autoload.commands', true); $mock = AutoloadManager::partialMock(); - $mock->boot(); + $mock->run(); expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing([]); }); @@ -65,7 +65,7 @@ DomainCache::clear(); $mock = AutoloadManager::partialMock(); - $mock->boot(); + $mock->run(); expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); }); diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index dc8e822..3b49942 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -25,14 +25,14 @@ $mock = AutoloadManager::partialMock(); $mock->shouldReceive('handleFactories')->once(); - $mock->boot(); + $mock->run(); }); it('can resolve domain factory', function ($modelClass, $expectedFactoryClass) { config()->set('ddd.autoload.factories', true); $mock = AutoloadManager::partialMock(); - $mock->boot(); + $mock->run(); expect($modelClass::factory())->toBeInstanceOf($expectedFactoryClass); })->with([ @@ -71,14 +71,14 @@ $mock = AutoloadManager::partialMock(); $mock->shouldNotReceive('handleFactories'); - $mock->boot(); + $mock->run(); }); it('cannot resolve factories that rely on autoloading', function ($modelClass, $correctFactories) { config()->set('ddd.autoload.factories', false); $mock = AutoloadManager::partialMock(); - $mock->boot(); + $mock->run(); expect(fn () => $modelClass::factory())->toThrow(Error::class); })->with([ diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index 3b11b25..5c1859c 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -32,7 +32,7 @@ $mock = AutoloadManager::partialMock(); $mock->shouldNotReceive('handlePolicies'); - $mock->boot(); + $mock->run(); }); }); @@ -42,14 +42,14 @@ $mock = AutoloadManager::partialMock(); $mock->shouldReceive('handlePolicies')->once(); - $mock->boot(); + $mock->run(); }); it('can resolve the policies', function () { config()->set('ddd.autoload.policies', true); $mock = AutoloadManager::partialMock(); - $mock->boot(); + $mock->run(); foreach ($this->policies as $class => $expectedPolicy) { $resolvedPolicy = Gate::getPolicyFor($class); @@ -61,7 +61,7 @@ config()->set('ddd.autoload.policies', true); $mock = AutoloadManager::partialMock(); - $mock->boot(); + $mock->run(); expect(class_exists($class))->toBeTrue(); expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 4da3952..da2864f 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -31,14 +31,14 @@ $mock = AutoloadManager::partialMock(); $mock->shouldNotReceive('handleProviders'); - $mock->boot(); + $mock->run(); }); it('does not register the providers', function () { config()->set('ddd.autoload.providers', false); $mock = AutoloadManager::partialMock(); - $mock->boot(); + $mock->run(); expect($mock->getRegisteredProviders())->toBeEmpty(); }); @@ -50,14 +50,14 @@ $mock = AutoloadManager::partialMock(); $mock->shouldReceive('handleProviders')->once(); - $mock->boot(); + $mock->run(); }); it('registers the providers', function () { config()->set('ddd.autoload.providers', true); $mock = AutoloadManager::partialMock(); - $mock->boot(); + $mock->run(); expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); }); @@ -70,7 +70,7 @@ config()->set('ddd.autoload.providers', true); $mock = AutoloadManager::partialMock(); - $mock->boot(); + $mock->run(); expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing([]); }); @@ -82,7 +82,7 @@ config()->set('ddd.autoload.providers', true); $mock = AutoloadManager::partialMock(); - $mock->boot(); + $mock->run(); expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); }); From a1d0321d0f1a25caf8026576ee6a2c0cd8b9fc45 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 21:01:02 -0500 Subject: [PATCH 111/169] Always return isBooted false when mocking. --- src/Support/AutoloadManager.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index 207660d..6cd2665 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -350,8 +350,12 @@ protected function resolveAppNamespace() public static function partialMock() { - return Mockery::mock(AutoloadManager::class, [null]) + $mock = Mockery::mock(AutoloadManager::class, [null]) ->makePartial() ->shouldAllowMockingProtectedMethods(); + + $mock->shouldReceive('isBooted')->andReturn(false); + + return $mock; } } From 7cff718dfc4b88da240dae2cd9513f6d3a94c352 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 21:17:54 -0500 Subject: [PATCH 112/169] Attempt fix --- tests/Autoload/IgnoreTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 6ed4734..15627fd 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -27,6 +27,14 @@ DomainCache::clear(); Artisan::call('ddd:clear'); + + Config::set('ddd.autoload', [ + 'providers' => true, + 'commands' => true, + 'factories' => true, + 'policies' => true, + 'migrations' => true, + ]); }); afterEach(function () { @@ -49,6 +57,8 @@ expect($cached)->toEqualCanonicalizing($expected); + DomainCache::clear(); + Config::set('ddd.autoload_ignore', ['Commands']); Artisan::call('ddd:optimize'); @@ -95,6 +105,8 @@ expect($cached)->toEqualCanonicalizing($expected); + DomainCache::clear(); + $secret = null; DDD::filterAutoloadPathsUsing(function (SplFileInfo $file) use (&$secret) { From 265f5cedd9bd7f4493539547c93c4bc8dbf0d6f1 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 21:35:40 -0500 Subject: [PATCH 113/169] Update --- tests/Autoload/CommandTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 1138c12..7fe422c 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -64,6 +64,8 @@ DomainCache::set('domain-commands', []); DomainCache::clear(); + config()->set('ddd.autoload.commands', true); + $mock = AutoloadManager::partialMock(); $mock->run(); From a07b674afef5df49ace6cec51793ebc0bea4d333 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 22:39:12 -0500 Subject: [PATCH 114/169] Assert against the discovery methods instead of cache --- src/Support/AutoloadManager.php | 4 +-- tests/Autoload/IgnoreTest.php | 54 ++++++++++++--------------------- 2 files changed, 22 insertions(+), 36 deletions(-) diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index 6cd2665..a3a7051 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -275,7 +275,7 @@ protected function finder($paths) ->filter($filter); } - protected function discoverProviders(): array + public function discoverProviders(): array { $configValue = config('ddd.autoload.providers'); @@ -299,7 +299,7 @@ protected function discoverProviders(): array ->toArray(); } - protected function discoverCommands(): array + public function discoverCommands(): array { $configValue = config('ddd.autoload.commands'); diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 15627fd..c9627b7 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -43,69 +43,57 @@ }); it('can ignore folders when autoloading', function () { - Artisan::call('ddd:optimize'); - $expected = [ ...array_values($this->providers), ...array_values($this->commands), ]; - $cached = [ - ...DomainCache::get('domain-providers'), - ...DomainCache::get('domain-commands'), + $discovered = [ + ...DDD::autoloader()->discoverProviders(), + ...DDD::autoloader()->discoverCommands(), ]; - expect($cached)->toEqualCanonicalizing($expected); - - DomainCache::clear(); + expect($discovered)->toEqualCanonicalizing($expected); Config::set('ddd.autoload_ignore', ['Commands']); - Artisan::call('ddd:optimize'); - $expected = [ ...array_values($this->providers), ]; - $cached = [ - ...DomainCache::get('domain-providers'), - ...DomainCache::get('domain-commands'), + $discovered = [ + ...DDD::autoloader()->discoverProviders(), + ...DDD::autoloader()->discoverCommands(), ]; - expect($cached)->toEqualCanonicalizing($expected); + expect($discovered)->toEqualCanonicalizing($expected); Config::set('ddd.autoload_ignore', ['Providers']); - Artisan::call('ddd:optimize'); - $expected = [ ...array_values($this->commands), ]; - $cached = [ - ...DomainCache::get('domain-providers'), - ...DomainCache::get('domain-commands'), + $discovered = [ + ...DDD::autoloader()->discoverProviders(), + ...DDD::autoloader()->discoverCommands(), ]; - expect($cached)->toEqualCanonicalizing($expected); + expect($discovered)->toEqualCanonicalizing($expected); }); it('can register a custom autoload filter', function () { - Artisan::call('ddd:optimize'); - $expected = [ ...array_values($this->providers), ...array_values($this->commands), ]; - $cached = [ - ...DomainCache::get('domain-providers'), - ...DomainCache::get('domain-commands'), + $discovered = [ + ...DDD::autoloader()->discoverProviders(), + ...DDD::autoloader()->discoverCommands(), ]; - expect($cached)->toEqualCanonicalizing($expected); - - DomainCache::clear(); + expect($discovered)->toEqualCanonicalizing($expected); $secret = null; @@ -126,14 +114,12 @@ } }); - Artisan::call('ddd:optimize'); - - $cached = [ - ...DomainCache::get('domain-providers'), - ...DomainCache::get('domain-commands'), + $discovered = [ + ...DDD::autoloader()->discoverProviders(), + ...DDD::autoloader()->discoverCommands(), ]; - expect($cached)->toEqual([]); + expect($discovered)->toEqual([]); expect($secret)->toEqual('i-was-invoked'); }); From 951ff9a5595e12954358b9032bd5690ad9679b44 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 22:49:41 -0500 Subject: [PATCH 115/169] wip --- src/Support/AutoloadManager.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index a3a7051..ad66b74 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -296,6 +296,7 @@ public function discoverProviders(): array return Lody::classesFromFinder($this->finder($paths)) ->isNotAbstract() ->isInstanceOf(ServiceProvider::class) + ->values() ->toArray(); } @@ -320,6 +321,7 @@ public function discoverCommands(): array return Lody::classesFromFinder($this->finder($paths)) ->isNotAbstract() ->isInstanceOf(Command::class) + ->values() ->toArray(); } From 0add0d19a198c575db5e0d62b9db0238db4d3d36 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 22:53:29 -0500 Subject: [PATCH 116/169] wip --- tests/Autoload/IgnoreTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index c9627b7..266f704 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -66,7 +66,7 @@ ...DDD::autoloader()->discoverCommands(), ]; - expect($discovered)->toEqualCanonicalizing($expected); + expect($discovered)->each(fn ($item) => $item->toBeIn($expected)); Config::set('ddd.autoload_ignore', ['Providers']); @@ -79,7 +79,7 @@ ...DDD::autoloader()->discoverCommands(), ]; - expect($discovered)->toEqualCanonicalizing($expected); + expect($discovered)->each(fn ($item) => $item->toBeIn($expected)); }); it('can register a custom autoload filter', function () { @@ -93,7 +93,7 @@ ...DDD::autoloader()->discoverCommands(), ]; - expect($discovered)->toEqualCanonicalizing($expected); + expect($discovered)->each(fn ($item) => $item->toBeIn($expected)); $secret = null; From 93c617b0998a599e5b707683b56468e3b83dbd29 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 22:56:10 -0500 Subject: [PATCH 117/169] Assert the counts too --- tests/Autoload/IgnoreTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 266f704..1327a67 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -66,6 +66,7 @@ ...DDD::autoloader()->discoverCommands(), ]; + expect($discovered)->toHaveCount(count($expected)); expect($discovered)->each(fn ($item) => $item->toBeIn($expected)); Config::set('ddd.autoload_ignore', ['Providers']); @@ -79,6 +80,7 @@ ...DDD::autoloader()->discoverCommands(), ]; + expect($discovered)->toHaveCount(count($expected)); expect($discovered)->each(fn ($item) => $item->toBeIn($expected)); }); @@ -93,6 +95,7 @@ ...DDD::autoloader()->discoverCommands(), ]; + expect($discovered)->toHaveCount(count($expected)); expect($discovered)->each(fn ($item) => $item->toBeIn($expected)); $secret = null; @@ -119,7 +122,7 @@ ...DDD::autoloader()->discoverCommands(), ]; - expect($discovered)->toEqual([]); + expect($discovered)->toHaveCount(0); expect($secret)->toEqual('i-was-invoked'); }); From 6ed447209f4b9badc4299f256e1b8c57c0366b00 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 23:04:17 -0500 Subject: [PATCH 118/169] wip --- tests/Autoload/CommandTest.php | 13 ++++++++++--- tests/Autoload/ProviderTest.php | 13 ++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 7fe422c..c6845c6 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -44,7 +44,10 @@ $mock = AutoloadManager::partialMock(); $mock->run(); - expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); + $expected = array_values($this->commands); + $registered = array_values($mock->getRegisteredCommands()); + expect($registered)->toHaveCount(count($expected)); + expect($registered)->each(fn ($item) => $item->toBeIn($expected)); }); }); @@ -57,7 +60,8 @@ $mock = AutoloadManager::partialMock(); $mock->run(); - expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing([]); + $registered = array_values($mock->getRegisteredCommands()); + expect($registered)->toHaveCount(0); }); it('can bust the cache', function () { @@ -69,6 +73,9 @@ $mock = AutoloadManager::partialMock(); $mock->run(); - expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); + $expected = array_values($this->commands); + $registered = array_values($mock->getRegisteredCommands()); + expect($registered)->toHaveCount(count($expected)); + expect($registered)->each(fn ($item) => $item->toBeIn($expected)); }); }); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index da2864f..6ae82ad 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -59,7 +59,10 @@ $mock = AutoloadManager::partialMock(); $mock->run(); - expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); + $expected = array_values($this->providers); + $registered = array_values($mock->getRegisteredProviders()); + expect($registered)->toHaveCount(count($expected)); + expect($registered)->each(fn ($item) => $item->toBeIn($expected)); }); }); @@ -72,7 +75,8 @@ $mock = AutoloadManager::partialMock(); $mock->run(); - expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing([]); + $registered = array_values($mock->getRegisteredProviders()); + expect($registered)->toHaveCount(0); }); it('can bust the cache', function () { @@ -84,6 +88,9 @@ $mock = AutoloadManager::partialMock(); $mock->run(); - expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); + $expected = array_values($this->providers); + $registered = array_values($mock->getRegisteredProviders()); + expect($registered)->toHaveCount(count($expected)); + expect($registered)->each(fn ($item) => $item->toBeIn($expected)); }); }); From 2f6a3b55deaa471e4fcf7b424f039bf5a0ddcaee Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 23:41:03 -0500 Subject: [PATCH 119/169] wip --- tests/Autoload/CommandTest.php | 4 ++-- tests/Autoload/IgnoreTest.php | 6 +++--- tests/Autoload/ProviderTest.php | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index c6845c6..c63cf25 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -46,8 +46,8 @@ $expected = array_values($this->commands); $registered = array_values($mock->getRegisteredCommands()); + expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); - expect($registered)->each(fn ($item) => $item->toBeIn($expected)); }); }); @@ -75,7 +75,7 @@ $expected = array_values($this->commands); $registered = array_values($mock->getRegisteredCommands()); + expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); - expect($registered)->each(fn ($item) => $item->toBeIn($expected)); }); }); diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 1327a67..15bf1ed 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -66,8 +66,8 @@ ...DDD::autoloader()->discoverCommands(), ]; + expect($expected)->each(fn ($item) => $item->toBeIn($discovered)); expect($discovered)->toHaveCount(count($expected)); - expect($discovered)->each(fn ($item) => $item->toBeIn($expected)); Config::set('ddd.autoload_ignore', ['Providers']); @@ -80,8 +80,8 @@ ...DDD::autoloader()->discoverCommands(), ]; + expect($expected)->each(fn ($item) => $item->toBeIn($discovered)); expect($discovered)->toHaveCount(count($expected)); - expect($discovered)->each(fn ($item) => $item->toBeIn($expected)); }); it('can register a custom autoload filter', function () { @@ -95,8 +95,8 @@ ...DDD::autoloader()->discoverCommands(), ]; + expect($expected)->each(fn ($item) => $item->toBeIn($discovered)); expect($discovered)->toHaveCount(count($expected)); - expect($discovered)->each(fn ($item) => $item->toBeIn($expected)); $secret = null; diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 6ae82ad..d0ecc86 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -61,8 +61,8 @@ $expected = array_values($this->providers); $registered = array_values($mock->getRegisteredProviders()); + expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); - expect($registered)->each(fn ($item) => $item->toBeIn($expected)); }); }); @@ -90,7 +90,7 @@ $expected = array_values($this->providers); $registered = array_values($mock->getRegisteredProviders()); + expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); - expect($registered)->each(fn ($item) => $item->toBeIn($expected)); }); }); From f30bd3d65f417a41d989b30d08375cd5344d426d Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sat, 16 Nov 2024 23:47:41 -0500 Subject: [PATCH 120/169] Assert config values --- tests/Autoload/IgnoreTest.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 15bf1ed..93490e4 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -43,6 +43,12 @@ }); it('can ignore folders when autoloading', function () { + expect(config('ddd.domain_path'))->toEqual('src/Domain'); + expect(config('ddd.domain_namespace'))->toEqual('Domain'); + expect(config('ddd.application_path'))->toEqual('src/Application'); + expect(config('ddd.application_namespace'))->toEqual('Application'); + expect(config('ddd.layers'))->toContain('src/Infrastructure'); + $expected = [ ...array_values($this->providers), ...array_values($this->commands), @@ -53,7 +59,8 @@ ...DDD::autoloader()->discoverCommands(), ]; - expect($discovered)->toEqualCanonicalizing($expected); + expect($expected)->each(fn ($item) => $item->toBeIn($discovered)); + expect($discovered)->toHaveCount(count($expected)); Config::set('ddd.autoload_ignore', ['Commands']); From b3f65771ef7e6c2f7d06f9dc6433c41f3b9987a2 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 00:04:44 -0500 Subject: [PATCH 121/169] wip --- tests/TestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index c3bb2f7..391821a 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -61,8 +61,8 @@ protected function defineEnvironment($app) static::$configValues = [ 'ddd.domain_path' => 'src/Domain', 'ddd.domain_namespace' => 'Domain', - 'ddd.application_namespace' => 'Application', 'ddd.application_path' => 'src/Application', + 'ddd.application_namespace' => 'Application', 'ddd.application_objects' => [ 'controller', 'request', From e40ea3dd14ecd13d73da47af51c4877f1f8d65d3 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 00:27:31 -0500 Subject: [PATCH 122/169] verify layer paths during autoload --- tests/Autoload/CommandTest.php | 19 +++++++++++++++++++ tests/Autoload/IgnoreTest.php | 7 +++++++ tests/Autoload/ProviderTest.php | 31 +++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index c63cf25..ffce170 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -3,6 +3,7 @@ use Illuminate\Support\Facades\Artisan; use Lunarstorm\LaravelDDD\Support\AutoloadManager; use Lunarstorm\LaravelDDD\Support\DomainCache; +use Lunarstorm\LaravelDDD\Support\Path; use Lunarstorm\LaravelDDD\Tests\BootsTestApplication; uses(BootsTestApplication::class); @@ -44,6 +45,12 @@ $mock = AutoloadManager::partialMock(); $mock->run(); + expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ + Path::normalize(base_path('src/Domain')), + Path::normalize(base_path('src/Application')), + Path::normalize(base_path('src/Infrastructure')), + ]); + $expected = array_values($this->commands); $registered = array_values($mock->getRegisteredCommands()); expect($expected)->each(fn ($item) => $item->toBeIn($registered)); @@ -60,6 +67,12 @@ $mock = AutoloadManager::partialMock(); $mock->run(); + expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ + Path::normalize(base_path('src/Domain')), + Path::normalize(base_path('src/Application')), + Path::normalize(base_path('src/Infrastructure')), + ]); + $registered = array_values($mock->getRegisteredCommands()); expect($registered)->toHaveCount(0); }); @@ -73,6 +86,12 @@ $mock = AutoloadManager::partialMock(); $mock->run(); + expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ + Path::normalize(base_path('src/Domain')), + Path::normalize(base_path('src/Application')), + Path::normalize(base_path('src/Infrastructure')), + ]); + $expected = array_values($this->commands); $registered = array_values($mock->getRegisteredCommands()); expect($expected)->each(fn ($item) => $item->toBeIn($registered)); diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 93490e4..2a4833e 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -5,6 +5,7 @@ use Illuminate\Support\Str; use Lunarstorm\LaravelDDD\Facades\DDD; use Lunarstorm\LaravelDDD\Support\DomainCache; +use Lunarstorm\LaravelDDD\Support\Path; use Lunarstorm\LaravelDDD\Tests\BootsTestApplication; use Symfony\Component\Finder\SplFileInfo; @@ -35,6 +36,12 @@ 'policies' => true, 'migrations' => true, ]); + + expect(DDD::autoloader()->getAllLayerPaths())->toEqualCanonicalizing([ + Path::normalize(base_path('src/Domain')), + Path::normalize(base_path('src/Application')), + Path::normalize(base_path('src/Infrastructure')), + ]); }); afterEach(function () { diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index d0ecc86..59e7840 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -3,6 +3,7 @@ use Illuminate\Support\Facades\Artisan; use Lunarstorm\LaravelDDD\Support\AutoloadManager; use Lunarstorm\LaravelDDD\Support\DomainCache; +use Lunarstorm\LaravelDDD\Support\Path; use Lunarstorm\LaravelDDD\Tests\BootsTestApplication; uses(BootsTestApplication::class); @@ -40,6 +41,12 @@ $mock = AutoloadManager::partialMock(); $mock->run(); + expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ + Path::normalize(base_path('src/Domain')), + Path::normalize(base_path('src/Application')), + Path::normalize(base_path('src/Infrastructure')), + ]); + expect($mock->getRegisteredProviders())->toBeEmpty(); }); }); @@ -51,6 +58,12 @@ $mock = AutoloadManager::partialMock(); $mock->shouldReceive('handleProviders')->once(); $mock->run(); + + expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ + Path::normalize(base_path('src/Domain')), + Path::normalize(base_path('src/Application')), + Path::normalize(base_path('src/Infrastructure')), + ]); }); it('registers the providers', function () { @@ -59,6 +72,12 @@ $mock = AutoloadManager::partialMock(); $mock->run(); + expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ + Path::normalize(base_path('src/Domain')), + Path::normalize(base_path('src/Application')), + Path::normalize(base_path('src/Infrastructure')), + ]); + $expected = array_values($this->providers); $registered = array_values($mock->getRegisteredProviders()); expect($expected)->each(fn ($item) => $item->toBeIn($registered)); @@ -75,6 +94,12 @@ $mock = AutoloadManager::partialMock(); $mock->run(); + expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ + Path::normalize(base_path('src/Domain')), + Path::normalize(base_path('src/Application')), + Path::normalize(base_path('src/Infrastructure')), + ]); + $registered = array_values($mock->getRegisteredProviders()); expect($registered)->toHaveCount(0); }); @@ -88,6 +113,12 @@ $mock = AutoloadManager::partialMock(); $mock->run(); + expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ + Path::normalize(base_path('src/Domain')), + Path::normalize(base_path('src/Application')), + Path::normalize(base_path('src/Infrastructure')), + ]); + $expected = array_values($this->providers); $registered = array_values($mock->getRegisteredProviders()); expect($expected)->each(fn ($item) => $item->toBeIn($registered)); From 565448b105bddb84a19d13c3f46f6bed2c3274be Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 00:39:31 -0500 Subject: [PATCH 123/169] wip --- tests/Autoload/CommandTest.php | 5 +++++ tests/Autoload/IgnoreTest.php | 5 +++++ tests/Autoload/ProviderTest.php | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index ffce170..e712402 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -19,6 +19,11 @@ DomainCache::clear(); Artisan::call('ddd:clear'); + + expect(config('ddd.autoload_ignore'))->toEqualCanonicalizing([ + 'Tests', + 'Database/Migrations', + ]); }); afterEach(function () { diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 2a4833e..d3f733f 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -42,6 +42,11 @@ Path::normalize(base_path('src/Application')), Path::normalize(base_path('src/Infrastructure')), ]); + + expect(config('ddd.autoload_ignore'))->toEqualCanonicalizing([ + 'Tests', + 'Database/Migrations', + ]); }); afterEach(function () { diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 59e7840..09059bc 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -19,6 +19,11 @@ DomainCache::clear(); Artisan::call('ddd:clear'); + + expect(config('ddd.autoload_ignore'))->toEqualCanonicalizing([ + 'Tests', + 'Database/Migrations', + ]); }); afterEach(function () { From 32331db9948037f1832fe1d3811b492804ad7d6c Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 00:43:40 -0500 Subject: [PATCH 124/169] Add composer reload hook --- tests/TestCase.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/TestCase.php b/tests/TestCase.php index 391821a..1778f44 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -36,6 +36,10 @@ protected function setUp(): void }); parent::setUp(); + + if (in_array(BootsTestApplication::class, class_uses_recursive($this))) { + $this->composerReload(); + } } protected function tearDown(): void From e8165d7fd17e14e72aaee54e1d8688161485d432 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 00:53:05 -0500 Subject: [PATCH 125/169] wip --- tests/Autoload/CommandTest.php | 2 ++ tests/Autoload/ProviderTest.php | 2 ++ tests/TestCase.php | 6 +++--- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index e712402..13de9ab 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -58,6 +58,7 @@ $expected = array_values($this->commands); $registered = array_values($mock->getRegisteredCommands()); + expect($mock->discoverCommands())->toEqualCanonicalizing($expected); expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); }); @@ -99,6 +100,7 @@ $expected = array_values($this->commands); $registered = array_values($mock->getRegisteredCommands()); + expect($mock->discoverCommands())->toEqualCanonicalizing($expected); expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); }); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 09059bc..a193e7c 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -85,6 +85,7 @@ $expected = array_values($this->providers); $registered = array_values($mock->getRegisteredProviders()); + expect($mock->discoverProviders())->toEqualCanonicalizing($expected); expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); }); @@ -126,6 +127,7 @@ $expected = array_values($this->providers); $registered = array_values($mock->getRegisteredProviders()); + expect($mock->discoverProviders())->toEqualCanonicalizing($expected); expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); }); diff --git a/tests/TestCase.php b/tests/TestCase.php index 1778f44..0637ee3 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -37,9 +37,9 @@ protected function setUp(): void parent::setUp(); - if (in_array(BootsTestApplication::class, class_uses_recursive($this))) { - $this->composerReload(); - } + // if (in_array(BootsTestApplication::class, class_uses_recursive($this))) { + // $this->composerReload(); + // } } protected function tearDown(): void From c55e2ca992c1ff8aae9eb81c1ddccb27bdd83240 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 01:07:52 -0500 Subject: [PATCH 126/169] wip --- tests/Autoload/CommandTest.php | 9 +++++++++ tests/Autoload/ProviderTest.php | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 13de9ab..a03ff7c 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -56,6 +56,9 @@ Path::normalize(base_path('src/Infrastructure')), ]); + collect($mock->getAllLayerPaths()) + ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); + $expected = array_values($this->commands); $registered = array_values($mock->getRegisteredCommands()); expect($mock->discoverCommands())->toEqualCanonicalizing($expected); @@ -79,6 +82,9 @@ Path::normalize(base_path('src/Infrastructure')), ]); + collect($mock->getAllLayerPaths()) + ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); + $registered = array_values($mock->getRegisteredCommands()); expect($registered)->toHaveCount(0); }); @@ -98,6 +104,9 @@ Path::normalize(base_path('src/Infrastructure')), ]); + collect($mock->getAllLayerPaths()) + ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); + $expected = array_values($this->commands); $registered = array_values($mock->getRegisteredCommands()); expect($mock->discoverCommands())->toEqualCanonicalizing($expected); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index a193e7c..c59dbdd 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -52,6 +52,9 @@ Path::normalize(base_path('src/Infrastructure')), ]); + collect($mock->getAllLayerPaths()) + ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); + expect($mock->getRegisteredProviders())->toBeEmpty(); }); }); @@ -69,6 +72,9 @@ Path::normalize(base_path('src/Application')), Path::normalize(base_path('src/Infrastructure')), ]); + + collect($mock->getAllLayerPaths()) + ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); }); it('registers the providers', function () { @@ -83,6 +89,9 @@ Path::normalize(base_path('src/Infrastructure')), ]); + collect($mock->getAllLayerPaths()) + ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); + $expected = array_values($this->providers); $registered = array_values($mock->getRegisteredProviders()); expect($mock->discoverProviders())->toEqualCanonicalizing($expected); @@ -106,6 +115,9 @@ Path::normalize(base_path('src/Infrastructure')), ]); + collect($mock->getAllLayerPaths()) + ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); + $registered = array_values($mock->getRegisteredProviders()); expect($registered)->toHaveCount(0); }); @@ -125,6 +137,9 @@ Path::normalize(base_path('src/Infrastructure')), ]); + collect($mock->getAllLayerPaths()) + ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); + $expected = array_values($this->providers); $registered = array_values($mock->getRegisteredProviders()); expect($mock->discoverProviders())->toEqualCanonicalizing($expected); From 9a6fa663b6e713f0ec6417b2a70953d235055143 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 01:12:08 -0500 Subject: [PATCH 127/169] wip --- tests/Autoload/CommandTest.php | 4 ++++ tests/Autoload/IgnoreTest.php | 8 ++++++++ tests/Autoload/ProviderTest.php | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index a03ff7c..3d74782 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -24,6 +24,10 @@ 'Tests', 'Database/Migrations', ]); + + foreach ($this->commands as $command) { + expect(class_exists($command))->toBeTrue("{$command} class does not exist"); + } }); afterEach(function () { diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index d3f733f..f73b508 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -47,6 +47,14 @@ 'Tests', 'Database/Migrations', ]); + + foreach ($this->providers as $provider) { + expect(class_exists($provider))->toBeTrue("{$provider} class does not exist"); + } + + foreach ($this->commands as $command) { + expect(class_exists($command))->toBeTrue("{$command} class does not exist"); + } }); afterEach(function () { diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index c59dbdd..88d6359 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -24,6 +24,10 @@ 'Tests', 'Database/Migrations', ]); + + foreach ($this->providers as $provider) { + expect(class_exists($provider))->toBeTrue("{$provider} class does not exist"); + } }); afterEach(function () { From d70c6308444970fdcc7ec678cd7fd24521d9a16d Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 09:50:49 -0500 Subject: [PATCH 128/169] wip --- tests/Autoload/CommandTest.php | 2 +- tests/Autoload/IgnoreTest.php | 4 ++-- tests/Autoload/ProviderTest.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index 3d74782..c66a756 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -10,9 +10,9 @@ beforeEach(function () { $this->commands = [ + 'application:sync' => 'Application\Commands\ApplicationSync', 'invoice:deliver' => 'Domain\Invoicing\Commands\InvoiceDeliver', 'log:prune' => 'Infrastructure\Commands\LogPrune', - 'application:sync' => 'Application\Commands\ApplicationSync', ]; $this->setupTestApplication(); diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index f73b508..dbb526e 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -13,15 +13,15 @@ beforeEach(function () { $this->providers = [ - 'Domain\Invoicing\Providers\InvoiceServiceProvider', 'Application\Providers\ApplicationServiceProvider', + 'Domain\Invoicing\Providers\InvoiceServiceProvider', 'Infrastructure\Providers\InfrastructureServiceProvider', ]; $this->commands = [ + 'application:sync' => 'Application\Commands\ApplicationSync', 'invoice:deliver' => 'Domain\Invoicing\Commands\InvoiceDeliver', 'log:prune' => 'Infrastructure\Commands\LogPrune', - 'application:sync' => 'Application\Commands\ApplicationSync', ]; $this->setupTestApplication(); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 88d6359..8d88649 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -10,8 +10,8 @@ beforeEach(function () { $this->providers = [ - 'Domain\Invoicing\Providers\InvoiceServiceProvider', 'Application\Providers\ApplicationServiceProvider', + 'Domain\Invoicing\Providers\InvoiceServiceProvider', 'Infrastructure\Providers\InfrastructureServiceProvider', ]; From d9ea57f664d526db280c385cc14efb621ea50cf2 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 09:58:40 -0500 Subject: [PATCH 129/169] wip --- tests/Autoload/ProviderTest.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 8d88649..3ade9dd 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -24,10 +24,6 @@ 'Tests', 'Database/Migrations', ]); - - foreach ($this->providers as $provider) { - expect(class_exists($provider))->toBeTrue("{$provider} class does not exist"); - } }); afterEach(function () { @@ -97,8 +93,13 @@ ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); $expected = array_values($this->providers); + + foreach ($expected as $provider) { + expect(class_exists($provider))->toBeTrue("class_exists false on expected {$provider}"); + } + $registered = array_values($mock->getRegisteredProviders()); - expect($mock->discoverProviders())->toEqualCanonicalizing($expected); + expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); }); From 062ba943591910bc8c4159eb10c4c3e85b971c32 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 10:06:08 -0500 Subject: [PATCH 130/169] wip --- tests/Autoload/ProviderTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 3ade9dd..33942c2 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -80,9 +80,13 @@ it('registers the providers', function () { config()->set('ddd.autoload.providers', true); + expect(DomainCache::has('domain-providers'))->toBeFalse(); + $mock = AutoloadManager::partialMock(); $mock->run(); + expect(DomainCache::has('domain-providers'))->toBeFalse(); + expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ Path::normalize(base_path('src/Domain')), Path::normalize(base_path('src/Application')), From 1606c2b43ef030e002eb767fd8961578e9e4079f Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 10:15:47 -0500 Subject: [PATCH 131/169] Normalize paths for windows --- src/Support/AutoloadManager.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index ad66b74..6271a26 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -120,14 +120,14 @@ public function getAllLayerPaths(): array DomainResolver::domainPath(), DomainResolver::applicationLayerPath(), ...array_values(config('ddd.layers', [])), - ])->map(fn ($path) => $this->app->basePath($path))->toArray(); + ])->map(fn ($path) => Path::normalize($this->app->basePath($path)))->toArray(); } protected function getCustomLayerPaths(): array { return collect([ ...array_values(config('ddd.layers', [])), - ])->map(fn ($path) => $this->app->basePath($path))->toArray(); + ])->map(fn ($path) => Path::normalize($this->app->basePath($path)))->toArray(); } protected function handleProviders() From 9e2790994784db85cb41c57514786962a4b43e3f Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 10:25:19 -0500 Subject: [PATCH 132/169] wip --- tests/Autoload/ProviderTest.php | 12 +++++++++--- tests/TestCase.php | 7 ++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 33942c2..371eaeb 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -67,6 +67,8 @@ $mock->shouldReceive('handleProviders')->once(); $mock->run(); + expect(DomainCache::has('domain-providers'))->toBeFalse(); + expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ Path::normalize(base_path('src/Domain')), Path::normalize(base_path('src/Application')), @@ -80,8 +82,6 @@ it('registers the providers', function () { config()->set('ddd.autoload.providers', true); - expect(DomainCache::has('domain-providers'))->toBeFalse(); - $mock = AutoloadManager::partialMock(); $mock->run(); @@ -118,6 +118,8 @@ $mock = AutoloadManager::partialMock(); $mock->run(); + expect(DomainCache::has('domain-providers'))->toBeTrue(); + expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ Path::normalize(base_path('src/Domain')), Path::normalize(base_path('src/Application')), @@ -150,8 +152,12 @@ ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); $expected = array_values($this->providers); + + foreach ($expected as $provider) { + expect(class_exists($provider))->toBeTrue("class_exists false on expected {$provider}"); + } + $registered = array_values($mock->getRegisteredProviders()); - expect($mock->discoverProviders())->toEqualCanonicalizing($expected); expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); }); diff --git a/tests/TestCase.php b/tests/TestCase.php index 0637ee3..73d9aa4 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -86,6 +86,7 @@ protected function defineEnvironment($app) 'Tests', 'Database/Migrations', ], + 'ddd.cache_directory' => 'bootstrap/cache/ddd', 'cache.default' => 'file', ...static::$configValues, ]; @@ -202,6 +203,7 @@ protected function cleanSlate() File::cleanDirectory($basePath.'/app/Models'); File::cleanDirectory($basePath.'/database/factories'); + File::cleanDirectory($basePath.'/bootstrap/cache/ddd'); File::deleteDirectory($basePath.'/src'); File::deleteDirectory($basePath.'/resources/stubs/ddd'); @@ -209,7 +211,6 @@ protected function cleanSlate() File::deleteDirectory($basePath.'/Custom'); File::deleteDirectory($basePath.'/app/Policies'); File::deleteDirectory($basePath.'/app/Modules'); - File::deleteDirectory($basePath.'/bootstrap/cache/ddd'); File::copy(__DIR__.'/.skeleton/composer.json', $basePath.'/composer.json'); @@ -227,8 +228,12 @@ protected function setupTestApplication() { $this->cleanSlate(); + $basePath = $this->getBasePath(); + File::ensureDirectoryExists(app_path()); File::ensureDirectoryExists(app_path('Models')); + File::ensureDirectoryExists(database_path('factories')); + File::ensureDirectoryExists($basePath.'/bootstrap/cache/ddd'); $skeletonAppFolders = glob(__DIR__.'/.skeleton/app/*', GLOB_ONLYDIR); From 73b11665f54e0b19becff2683e2dbae06bec5243 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 10:32:07 -0500 Subject: [PATCH 133/169] Add file_exists to verify --- tests/Autoload/ProviderTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 371eaeb..3fa596d 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -24,6 +24,8 @@ 'Tests', 'Database/Migrations', ]); + + expect(file_exists(base_path('src/Application/Providers/ApplicationServiceProvider.php')))->toBeTrue(); }); afterEach(function () { From 2a5e606f06119c1697ff70bbfde2810f4becdee5 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 10:44:32 -0500 Subject: [PATCH 134/169] wip --- src/Support/AutoloadManager.php | 11 +++++++++++ tests/Autoload/ProviderTest.php | 6 ++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index 6271a26..d71ec1f 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -48,6 +48,8 @@ class AutoloadManager protected bool $ran = false; + protected static ?Closure $registeringProviderCallback = null; + public function __construct(protected ?Container $container = null) { $this->container = $container ?? Container::getInstance(); @@ -130,6 +132,11 @@ protected function getCustomLayerPaths(): array ])->map(fn ($path) => Path::normalize($this->app->basePath($path)))->toArray(); } + public static function registeringProvider(Closure $callback) + { + static::$registeringProviderCallback = $callback; + } + protected function handleProviders() { $providers = DomainCache::has('domain-providers') @@ -167,6 +174,10 @@ public function run() } foreach (static::$registeredProviders as $provider) { + if (static::$registeringProviderCallback) { + call_user_func(static::$registeringProviderCallback, $provider); + } + $this->app->register($provider); } diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 3fa596d..753bc51 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -24,8 +24,6 @@ 'Tests', 'Database/Migrations', ]); - - expect(file_exists(base_path('src/Application/Providers/ApplicationServiceProvider.php')))->toBeTrue(); }); afterEach(function () { @@ -84,6 +82,10 @@ it('registers the providers', function () { config()->set('ddd.autoload.providers', true); + AutoloadManager::registeringProvider(function ($provider) { + dump('registering provider: '.$provider); + }); + $mock = AutoloadManager::partialMock(); $mock->run(); From 69efd65253b53327becb335973484e01088b8871 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 13:00:30 -0500 Subject: [PATCH 135/169] Restore config command test and fix some things with the skeleton app. --- src/Commands/ConfigCommand.php | 4 ++++ src/LaravelDDDServiceProvider.php | 7 +++++++ tests/.skeleton/config/ddd.php | 4 ++-- tests/Command/ConfigTest.php | 23 ++++++++++------------- tests/TestCase.php | 2 +- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/Commands/ConfigCommand.php b/src/Commands/ConfigCommand.php index fdd779e..581389c 100644 --- a/src/Commands/ConfigCommand.php +++ b/src/Commands/ConfigCommand.php @@ -257,6 +257,10 @@ function ($responses) use ($choices) { $this->info('Building configuration...'); + foreach ($responses as $key => $value) { + $responses[$key] = $value ?: $config->get($key); + } + DDD::config()->fill($responses)->save(); $this->info('Configuration updated: '.config_path('ddd.php')); diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index 70992f9..fcd6518 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -2,6 +2,7 @@ namespace Lunarstorm\LaravelDDD; +use Illuminate\Database\Migrations\MigrationCreator; use Illuminate\Foundation\Application; use Lunarstorm\LaravelDDD\Facades\Autoload; use Lunarstorm\LaravelDDD\Support\AutoloadManager; @@ -89,6 +90,12 @@ protected function registerMigrations() return new Commands\Migration\DomainMigrateMakeCommand($creator, $composer); }); + $this->app->when(MigrationCreator::class) + ->needs('$customStubPath') + ->give(function ($app) { + return $app->basePath('stubs'); + }); + $this->loadMigrationsFrom(DomainMigration::paths()); return $this; diff --git a/tests/.skeleton/config/ddd.php b/tests/.skeleton/config/ddd.php index 9f65237..c330b61 100644 --- a/tests/.skeleton/config/ddd.php +++ b/tests/.skeleton/config/ddd.php @@ -22,8 +22,8 @@ | that should be recognized as part of the application layer. | */ - 'application_path' => 'Application', - 'application_namespace' => 'src\Application', + 'application_path' => 'src/Application', + 'application_namespace' => 'Application', 'application_objects' => [ 'controller', 'request', diff --git a/tests/Command/ConfigTest.php b/tests/Command/ConfigTest.php index b2d7da0..40c5e62 100644 --- a/tests/Command/ConfigTest.php +++ b/tests/Command/ConfigTest.php @@ -2,18 +2,22 @@ use Illuminate\Support\Facades\Artisan; use Lunarstorm\LaravelDDD\Facades\DDD; +use Lunarstorm\LaravelDDD\Tests\BootsTestApplication; + +uses(BootsTestApplication::class); beforeEach(function () { - $this->cleanSlate(); $this->setupTestApplication(); + Artisan::call('config:clear'); - $this->composerReload(); -})->skip(); + Artisan::call('ddd:clear'); +}); afterEach(function () { $this->cleanSlate(); + Artisan::call('config:clear'); - $this->composerReload(); + Artisan::call('ddd:clear'); }); it('can run the config wizard', function () { @@ -44,15 +48,8 @@ expect(config('ddd.domain_path'))->toBe('src/CustomDomain'); expect(config('ddd.domain_namespace'))->toBe('CustomDomain'); - expect(config('ddd.application'))->toBe([ - 'path' => 'app/Modules', - 'namespace' => 'App\Modules', - 'objects' => [ - 'controller', - 'request', - 'middleware', - ], - ]); + expect(config('ddd.application_path'))->toBe('src/Application'); + expect(config('ddd.application_namespace'))->toBe('Application'); expect(config('ddd.layers'))->toBe([ 'Support' => 'src/Support', ]); diff --git a/tests/TestCase.php b/tests/TestCase.php index 73d9aa4..c1efbbb 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -251,7 +251,7 @@ protected function setupTestApplication() File::copyDirectory(__DIR__.'/.skeleton/database', base_path('database')); File::copyDirectory(__DIR__.'/.skeleton/src', base_path('src')); File::copy(__DIR__.'/.skeleton/bootstrap/providers.php', base_path('bootstrap/providers.php')); - // File::copy(__DIR__ . '/.skeleton/config/ddd.php', config_path('ddd.php')); + File::copy(__DIR__.'/.skeleton/config/ddd.php', config_path('ddd.php')); $this->setAutoloadPathInComposer('Domain', 'src/Domain'); $this->setAutoloadPathInComposer('Application', 'src/Application'); From c1d44fce08e1681b2a03721c82a2373f86e882b9 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 13:20:49 -0500 Subject: [PATCH 136/169] Remove ddd:clear call --- tests/TestCase.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index c1efbbb..2789c61 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -28,7 +28,6 @@ protected function setUp(): void ); DomainCache::clear(); - Artisan::call('ddd:clear'); }); $this->beforeApplicationDestroyed(function () { From 9f7060cc72fd9f108dcdf825e7e9a0a5e163710a Mon Sep 17 00:00:00 2001 From: JasperTey Date: Sun, 17 Nov 2024 18:21:11 +0000 Subject: [PATCH 137/169] Fix styling --- tests/TestCase.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 2789c61..c3ba476 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -5,7 +5,6 @@ use Illuminate\Contracts\Config\Repository; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Arr; -use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\File; use Lunarstorm\LaravelDDD\LaravelDDDServiceProvider; use Lunarstorm\LaravelDDD\Support\DomainCache; From 8c775adef5fc3732a2e507ace65d786ed6b2a18b Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 13:53:55 -0500 Subject: [PATCH 138/169] Fix some issues with config and composer file during tests. --- src/Facades/DDD.php | 1 + src/LaravelDDDServiceProvider.php | 17 ++++++++--------- src/Support/AutoloadManager.php | 3 ++- tests/Command/ConfigTest.php | 2 +- tests/Command/InstallTest.php | 5 ++++- tests/Command/UpgradeTest.php | 15 +++++++++------ tests/TestCase.php | 14 ++++++++++++-- 7 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/Facades/DDD.php b/src/Facades/DDD.php index 4866f90..f8f0c52 100644 --- a/src/Facades/DDD.php +++ b/src/Facades/DDD.php @@ -8,6 +8,7 @@ * @see \Lunarstorm\LaravelDDD\DomainManager * * @method static void filterAutoloadPathsUsing(callable $filter) + * @method static callable getAutoloadFilter() * @method static void resolveObjectSchemaUsing(callable $resolver) * @method static string packagePath(string $path = '') * @method static \Lunarstorm\LaravelDDD\Support\AutoloadManager autoloader() diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index fcd6518..cbdd19a 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -71,6 +71,8 @@ public function configurePackage(Package $package): void if ($this->app->runningUnitTests()) { $package->hasRoutes(['testing']); } + + $this->registerBindings(); } protected function laravelVersion($value) @@ -80,6 +82,10 @@ protected function laravelVersion($value) protected function registerMigrations() { + $this->app->when(MigrationCreator::class) + ->needs('$customStubPath') + ->give(fn () => $this->app->basePath('stubs')); + $this->app->singleton(Commands\Migration\DomainMigrateMakeCommand::class, function ($app) { // Once we have the migration creator registered, we will create the command // and inject the creator. The creator is responsible for the actual file @@ -90,12 +96,6 @@ protected function registerMigrations() return new Commands\Migration\DomainMigrateMakeCommand($creator, $composer); }); - $this->app->when(MigrationCreator::class) - ->needs('$customStubPath') - ->give(function ($app) { - return $app->basePath('stubs'); - }); - $this->loadMigrationsFrom(DomainMigration::paths()); return $this; @@ -108,7 +108,7 @@ protected function registerBindings() }); $this->app->scoped(ComposerManager::class, function () { - return ComposerManager::make(app()->basePath('composer.json')); + return ComposerManager::make($this->app->basePath('composer.json')); }); $this->app->scoped(ConfigManager::class, function () { @@ -159,7 +159,6 @@ public function packageBooted() public function packageRegistered() { - $this->registerMigrations() - ->registerBindings(); + $this->registerMigrations(); } } diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index d71ec1f..58ec892 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -15,6 +15,7 @@ use Illuminate\Support\Str; use Illuminate\Support\Traits\Conditionable; use Lorisleiva\Lody\Lody; +use Lunarstorm\LaravelDDD\Facades\DDD; use Lunarstorm\LaravelDDD\Factories\DomainFactory; use Lunarstorm\LaravelDDD\ValueObjects\DomainObject; use Mockery; @@ -266,7 +267,7 @@ protected function handleFactories() protected function finder($paths) { - $filter = app('ddd')->getAutoloadFilter() ?? function (SplFileInfo $file) { + $filter = DDD::getAutoloadFilter() ?? function (SplFileInfo $file) { $pathAfterDomain = str($file->getRelativePath()) ->replace('\\', '/') ->after('/') diff --git a/tests/Command/ConfigTest.php b/tests/Command/ConfigTest.php index 40c5e62..08d4916 100644 --- a/tests/Command/ConfigTest.php +++ b/tests/Command/ConfigTest.php @@ -11,7 +11,7 @@ Artisan::call('config:clear'); Artisan::call('ddd:clear'); -}); +})->skip('causing issues in test suite'); afterEach(function () { $this->cleanSlate(); diff --git a/tests/Command/InstallTest.php b/tests/Command/InstallTest.php index aaff525..3969c96 100644 --- a/tests/Command/InstallTest.php +++ b/tests/Command/InstallTest.php @@ -4,10 +4,13 @@ beforeEach(function () { $this->setupTestApplication(); + + $this->originalComposerContents = file_get_contents(base_path('composer.json')); }); afterEach(function () { - $this->setupTestApplication()->composerReload(); + // $this->setupTestApplication()->composerReload(); + file_put_contents(base_path('composer.json'), $this->originalComposerContents); }); it('publishes config', function () { diff --git a/tests/Command/UpgradeTest.php b/tests/Command/UpgradeTest.php index 9d649e6..1ddb156 100644 --- a/tests/Command/UpgradeTest.php +++ b/tests/Command/UpgradeTest.php @@ -5,11 +5,11 @@ use Illuminate\Support\Facades\File; it('can upgrade 0.x config to 1.x', function (string $pathToOldConfig, array $expectedValues) { - $path = config_path('ddd.php'); + $configFilePath = config_path('ddd.php'); - File::copy($pathToOldConfig, $path); + File::copy($pathToOldConfig, $configFilePath); - expect(file_exists($path))->toBeTrue(); + expect(file_exists($configFilePath))->toBeTrue(); $this->artisan('ddd:upgrade') ->expectsOutputToContain('Configuration upgraded successfully.') @@ -21,10 +21,13 @@ $configAsArray = require config_path('ddd.php'); - foreach ($expectedValues as $path => $value) { - expect(data_get($configAsArray, $path)) - ->toEqual($value, "Config {$path} does not match expected value."); + foreach ($expectedValues as $key => $value) { + expect(data_get($configAsArray, $key)) + ->toEqual($value, "Config {$key} does not match expected value."); } + + // Delete the config file after the test + unlink($configFilePath); })->with('configUpgrades'); it('skips upgrade if config file was not published', function () { diff --git a/tests/TestCase.php b/tests/TestCase.php index c3ba476..d9e04fb 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -17,8 +17,12 @@ class TestCase extends Orchestra public $appConfig = []; + protected $originalComposerContents; + protected function setUp(): void { + $this->originalComposerContents = $this->getComposerFileContents(); + $this->afterApplicationCreated(function () { $this->cleanSlate(); @@ -42,8 +46,12 @@ protected function setUp(): void protected function tearDown(): void { + $basePath = $this->getBasePath(); + $this->cleanSlate(); + file_put_contents($basePath.'/composer.json', $this->originalComposerContents); + parent::tearDown(); } @@ -123,7 +131,9 @@ protected function withConfig(array $config) protected function getComposerFileContents() { - return file_get_contents(base_path('composer.json')); + $basePath = $this->getBasePath(); + + return file_get_contents($basePath.'/composer.json'); } protected function getComposerFileAsArray() @@ -210,7 +220,7 @@ protected function cleanSlate() File::deleteDirectory($basePath.'/app/Policies'); File::deleteDirectory($basePath.'/app/Modules'); - File::copy(__DIR__.'/.skeleton/composer.json', $basePath.'/composer.json'); + // File::copy(__DIR__.'/.skeleton/composer.json', $basePath.'/composer.json'); return $this; } From ddc6c9843ec00a8cb9ef841d2b779553bcd47f1b Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 13:56:19 -0500 Subject: [PATCH 139/169] Fix phpstan issues --- src/Facades/Autoload.php | 1 + src/Facades/DDD.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Facades/Autoload.php b/src/Facades/Autoload.php index 055f86d..36928b8 100644 --- a/src/Facades/Autoload.php +++ b/src/Facades/Autoload.php @@ -9,6 +9,7 @@ * @see \Lunarstorm\LaravelDDD\Support\AutoloadManager * * @method static void boot() + * @method static void run() * @method static array getAllLayerPaths() * @method static array getCustomLayerPaths() * @method static array getRegisteredCommands() diff --git a/src/Facades/DDD.php b/src/Facades/DDD.php index f8f0c52..392906b 100644 --- a/src/Facades/DDD.php +++ b/src/Facades/DDD.php @@ -8,7 +8,7 @@ * @see \Lunarstorm\LaravelDDD\DomainManager * * @method static void filterAutoloadPathsUsing(callable $filter) - * @method static callable getAutoloadFilter() + * @method static ?callable getAutoloadFilter() * @method static void resolveObjectSchemaUsing(callable $resolver) * @method static string packagePath(string $path = '') * @method static \Lunarstorm\LaravelDDD\Support\AutoloadManager autoloader() From ca243e2fd9b5c23fb13dd9c87590b2d0a4c507bd Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 14:00:45 -0500 Subject: [PATCH 140/169] copy skeleton composer --- tests/.skeleton/composer.json | 6 ++---- tests/TestCase.php | 16 ++++++---------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/tests/.skeleton/composer.json b/tests/.skeleton/composer.json index b245a01..2c9dd27 100644 --- a/tests/.skeleton/composer.json +++ b/tests/.skeleton/composer.json @@ -8,12 +8,10 @@ "license": "MIT", "type": "project", "autoload": { - "classmap": [ - "database", - "tests/TestCase.php" - ], "psr-4": { "App\\": "app/", + "Database\\Factories\\": "database/factories/", + "Database\\Seeders\\": "database/seeders/", "Domain\\": "src/Domain", "Application\\": "src/Application", "Infrastructure\\": "src/Infrastructure" diff --git a/tests/TestCase.php b/tests/TestCase.php index d9e04fb..5b54c0e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -249,21 +249,17 @@ protected function setupTestApplication() File::copyDirectory($folder, app_path(basename($folder))); } - // $skeletonSrcFolders = glob(__DIR__.'/.skeleton/src/*', GLOB_ONLYDIR); - - // foreach ($skeletonSrcFolders as $folder) { - // File::deleteDirectory(base_path('src/'.basename($folder))); - // File::copyDirectory($folder, base_path('src/'.basename($folder))); - // } - File::copyDirectory(__DIR__.'/.skeleton/database', base_path('database')); File::copyDirectory(__DIR__.'/.skeleton/src', base_path('src')); File::copy(__DIR__.'/.skeleton/bootstrap/providers.php', base_path('bootstrap/providers.php')); File::copy(__DIR__.'/.skeleton/config/ddd.php', config_path('ddd.php')); + File::copy(__DIR__.'/.skeleton/composer.json', $basePath.'/composer.json'); + + $this->composerReload(); - $this->setAutoloadPathInComposer('Domain', 'src/Domain'); - $this->setAutoloadPathInComposer('Application', 'src/Application'); - $this->setAutoloadPathInComposer('Infrastructure', 'src/Infrastructure'); + // $this->setAutoloadPathInComposer('Domain', 'src/Domain'); + // $this->setAutoloadPathInComposer('Application', 'src/Application'); + // $this->setAutoloadPathInComposer('Infrastructure', 'src/Infrastructure'); DomainCache::clear(); From d5e049997f3767a092c9f9c1b5ee86bd12370a33 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 14:15:11 -0500 Subject: [PATCH 141/169] Normalize paths for windows. --- tests/Support/AutoloaderTest.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/Support/AutoloaderTest.php b/tests/Support/AutoloaderTest.php index 1221cfb..7f381df 100644 --- a/tests/Support/AutoloaderTest.php +++ b/tests/Support/AutoloaderTest.php @@ -1,6 +1,7 @@ setupTestApplication(); @@ -29,19 +30,19 @@ 'cache.default' => 'file', ]); - $this->setupTestApplication(); -}); - -it('can discover paths to all layers', function () { - $autoloader = app(AutoloadManager::class); - - $expected = [ + $this->expectedPaths = collect([ app()->basePath('src/Domain'), app()->basePath('src/Application'), app()->basePath('src/Infrastructure'), app()->basePath('src/Support'), app()->basePath('lib'), - ]; + ])->map(fn ($path) => Path::normalize($path))->toArray(); + + $this->setupTestApplication(); +}); + +it('can discover paths to all layers', function () { + $autoloader = app(AutoloadManager::class); - expect($autoloader->getAllLayerPaths())->toEqualCanonicalizing($expected); + expect($autoloader->getAllLayerPaths())->toEqualCanonicalizing($this->expectedPaths); }); From 21d8f4c81867789dba16af5eb94315e6de33687c Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 15:00:36 -0500 Subject: [PATCH 142/169] Try adding provider singleton tests back in. --- src/LaravelDDDServiceProvider.php | 13 ---- .../Providers/ApplicationServiceProvider.php | 6 +- .../Providers/InvoiceServiceProvider.php | 4 +- .../InfrastructureServiceProvider.php | 6 +- tests/Autoload/CommandTest.php | 30 -------- tests/Autoload/IgnoreTest.php | 20 ----- tests/Autoload/ProviderTest.php | 75 +++---------------- tests/AutoloadingTest.php | 40 ---------- 8 files changed, 22 insertions(+), 172 deletions(-) delete mode 100644 tests/AutoloadingTest.php diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index cbdd19a..cd21e3f 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -3,7 +3,6 @@ namespace Lunarstorm\LaravelDDD; use Illuminate\Database\Migrations\MigrationCreator; -use Illuminate\Foundation\Application; use Lunarstorm\LaravelDDD\Facades\Autoload; use Lunarstorm\LaravelDDD\Support\AutoloadManager; use Lunarstorm\LaravelDDD\Support\DomainMigration; @@ -129,18 +128,6 @@ protected function registerBindings() $this->app->bind('ddd.composer', ComposerManager::class); $this->app->bind('ddd.stubs', StubManager::class); - if ($this->app->runningUnitTests()) { - // $this->app->when(AutoloadManager::class) - // ->needs(Application::class) - // ->give(function () { - // return $this->app; - // }); - - $this->app->resolving(AutoloadManager::class, function (AutoloadManager $atuoloader, Application $app) { - // dump('App resolving autoloader'); - }); - } - return $this; } diff --git a/tests/.skeleton/src/Application/Providers/ApplicationServiceProvider.php b/tests/.skeleton/src/Application/Providers/ApplicationServiceProvider.php index 1b366e1..f5ec9d7 100644 --- a/tests/.skeleton/src/Application/Providers/ApplicationServiceProvider.php +++ b/tests/.skeleton/src/Application/Providers/ApplicationServiceProvider.php @@ -5,13 +5,14 @@ use Infrastructure\Models\AppSession; use Illuminate\Foundation\Application; use Illuminate\Support\ServiceProvider; +use Infrastructure\Support\Clipboard; class ApplicationServiceProvider extends ServiceProvider { public function register() { - $this->app->singleton('application-layer', function (Application $app) { - return 'application-layer-singleton'; + $this->app->singleton('application-singleton', function (Application $app) { + return 'application-singleton'; }); } @@ -23,5 +24,6 @@ public function register() public function boot() { AppSession::setSecret('application-secret'); + Clipboard::set('application-secret', 'application-secret'); } } diff --git a/tests/.skeleton/src/Domain/Invoicing/Providers/InvoiceServiceProvider.php b/tests/.skeleton/src/Domain/Invoicing/Providers/InvoiceServiceProvider.php index 5e257e5..7ddb52a 100644 --- a/tests/.skeleton/src/Domain/Invoicing/Providers/InvoiceServiceProvider.php +++ b/tests/.skeleton/src/Domain/Invoicing/Providers/InvoiceServiceProvider.php @@ -5,12 +5,13 @@ use Domain\Invoicing\Models\Invoice; use Illuminate\Foundation\Application; use Illuminate\Support\ServiceProvider; +use Infrastructure\Support\Clipboard; class InvoiceServiceProvider extends ServiceProvider { public function register() { - $this->app->singleton('invoicing', function (Application $app) { + $this->app->singleton('invoicing-singleton', function (Application $app) { return 'invoicing-singleton'; }); } @@ -23,5 +24,6 @@ public function register() public function boot() { Invoice::setSecret('invoice-secret'); + Clipboard::set('invoicing-secret', 'invoicing-secret'); } } diff --git a/tests/.skeleton/src/Infrastructure/Providers/InfrastructureServiceProvider.php b/tests/.skeleton/src/Infrastructure/Providers/InfrastructureServiceProvider.php index 5174324..45df08b 100644 --- a/tests/.skeleton/src/Infrastructure/Providers/InfrastructureServiceProvider.php +++ b/tests/.skeleton/src/Infrastructure/Providers/InfrastructureServiceProvider.php @@ -10,8 +10,8 @@ class InfrastructureServiceProvider extends ServiceProvider { public function register() { - $this->app->singleton('infrastructure-layer', function (Application $app) { - return 'infrastructure-layer-singleton'; + $this->app->singleton('infrastructure-singleton', function (Application $app) { + return 'infrastructure-singleton'; }); } @@ -22,6 +22,6 @@ public function register() */ public function boot() { - Clipboard::set('secret', 'infrastructure-secret'); + Clipboard::set('infrastructure-secret', 'infrastructure-secret'); } } diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index c66a756..da029af 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -3,7 +3,6 @@ use Illuminate\Support\Facades\Artisan; use Lunarstorm\LaravelDDD\Support\AutoloadManager; use Lunarstorm\LaravelDDD\Support\DomainCache; -use Lunarstorm\LaravelDDD\Support\Path; use Lunarstorm\LaravelDDD\Tests\BootsTestApplication; uses(BootsTestApplication::class); @@ -54,18 +53,8 @@ $mock = AutoloadManager::partialMock(); $mock->run(); - expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ - Path::normalize(base_path('src/Domain')), - Path::normalize(base_path('src/Application')), - Path::normalize(base_path('src/Infrastructure')), - ]); - - collect($mock->getAllLayerPaths()) - ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); - $expected = array_values($this->commands); $registered = array_values($mock->getRegisteredCommands()); - expect($mock->discoverCommands())->toEqualCanonicalizing($expected); expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); }); @@ -80,15 +69,6 @@ $mock = AutoloadManager::partialMock(); $mock->run(); - expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ - Path::normalize(base_path('src/Domain')), - Path::normalize(base_path('src/Application')), - Path::normalize(base_path('src/Infrastructure')), - ]); - - collect($mock->getAllLayerPaths()) - ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); - $registered = array_values($mock->getRegisteredCommands()); expect($registered)->toHaveCount(0); }); @@ -102,18 +82,8 @@ $mock = AutoloadManager::partialMock(); $mock->run(); - expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ - Path::normalize(base_path('src/Domain')), - Path::normalize(base_path('src/Application')), - Path::normalize(base_path('src/Infrastructure')), - ]); - - collect($mock->getAllLayerPaths()) - ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); - $expected = array_values($this->commands); $registered = array_values($mock->getRegisteredCommands()); - expect($mock->discoverCommands())->toEqualCanonicalizing($expected); expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); }); diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index dbb526e..5be1acf 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -5,7 +5,6 @@ use Illuminate\Support\Str; use Lunarstorm\LaravelDDD\Facades\DDD; use Lunarstorm\LaravelDDD\Support\DomainCache; -use Lunarstorm\LaravelDDD\Support\Path; use Lunarstorm\LaravelDDD\Tests\BootsTestApplication; use Symfony\Component\Finder\SplFileInfo; @@ -36,25 +35,6 @@ 'policies' => true, 'migrations' => true, ]); - - expect(DDD::autoloader()->getAllLayerPaths())->toEqualCanonicalizing([ - Path::normalize(base_path('src/Domain')), - Path::normalize(base_path('src/Application')), - Path::normalize(base_path('src/Infrastructure')), - ]); - - expect(config('ddd.autoload_ignore'))->toEqualCanonicalizing([ - 'Tests', - 'Database/Migrations', - ]); - - foreach ($this->providers as $provider) { - expect(class_exists($provider))->toBeTrue("{$provider} class does not exist"); - } - - foreach ($this->commands as $command) { - expect(class_exists($command))->toBeTrue("{$command} class does not exist"); - } }); afterEach(function () { diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 753bc51..871eba6 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -3,7 +3,6 @@ use Illuminate\Support\Facades\Artisan; use Lunarstorm\LaravelDDD\Support\AutoloadManager; use Lunarstorm\LaravelDDD\Support\DomainCache; -use Lunarstorm\LaravelDDD\Support\Path; use Lunarstorm\LaravelDDD\Tests\BootsTestApplication; uses(BootsTestApplication::class); @@ -46,16 +45,11 @@ $mock = AutoloadManager::partialMock(); $mock->run(); - expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ - Path::normalize(base_path('src/Domain')), - Path::normalize(base_path('src/Application')), - Path::normalize(base_path('src/Infrastructure')), - ]); - - collect($mock->getAllLayerPaths()) - ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); - expect($mock->getRegisteredProviders())->toBeEmpty(); + + expect(fn () => app('invoicing-singleton'))->toThrow(Exception::class); + expect(fn () => app('application-singleton'))->toThrow(Exception::class); + expect(fn () => app('infrastructure-singleton'))->toThrow(Exception::class); }); }); @@ -66,50 +60,24 @@ $mock = AutoloadManager::partialMock(); $mock->shouldReceive('handleProviders')->once(); $mock->run(); - - expect(DomainCache::has('domain-providers'))->toBeFalse(); - - expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ - Path::normalize(base_path('src/Domain')), - Path::normalize(base_path('src/Application')), - Path::normalize(base_path('src/Infrastructure')), - ]); - - collect($mock->getAllLayerPaths()) - ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); }); it('registers the providers', function () { config()->set('ddd.autoload.providers', true); - AutoloadManager::registeringProvider(function ($provider) { - dump('registering provider: '.$provider); - }); - $mock = AutoloadManager::partialMock(); $mock->run(); expect(DomainCache::has('domain-providers'))->toBeFalse(); - expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ - Path::normalize(base_path('src/Domain')), - Path::normalize(base_path('src/Application')), - Path::normalize(base_path('src/Infrastructure')), - ]); - - collect($mock->getAllLayerPaths()) - ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); - $expected = array_values($this->providers); - - foreach ($expected as $provider) { - expect(class_exists($provider))->toBeTrue("class_exists false on expected {$provider}"); - } - $registered = array_values($mock->getRegisteredProviders()); - expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); + + expect(app('application-singleton'))->toEqual('application-singleton'); + expect(app('invoicing-singleton'))->toEqual('invoicing-singleton'); + expect(app('infrastructure-singleton'))->toEqual('infrastructure-singleton'); }); }); @@ -124,15 +92,6 @@ expect(DomainCache::has('domain-providers'))->toBeTrue(); - expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ - Path::normalize(base_path('src/Domain')), - Path::normalize(base_path('src/Application')), - Path::normalize(base_path('src/Infrastructure')), - ]); - - collect($mock->getAllLayerPaths()) - ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); - $registered = array_values($mock->getRegisteredProviders()); expect($registered)->toHaveCount(0); }); @@ -146,23 +105,13 @@ $mock = AutoloadManager::partialMock(); $mock->run(); - expect($mock->getAllLayerPaths())->toEqualCanonicalizing([ - Path::normalize(base_path('src/Domain')), - Path::normalize(base_path('src/Application')), - Path::normalize(base_path('src/Infrastructure')), - ]); - - collect($mock->getAllLayerPaths()) - ->each(fn ($path) => expect(is_dir($path))->toBeTrue("{$path} is not a directory")); - $expected = array_values($this->providers); - - foreach ($expected as $provider) { - expect(class_exists($provider))->toBeTrue("class_exists false on expected {$provider}"); - } - $registered = array_values($mock->getRegisteredProviders()); expect($expected)->each(fn ($item) => $item->toBeIn($registered)); expect($registered)->toHaveCount(count($expected)); + + expect(app('application-singleton'))->toEqual('application-singleton'); + expect(app('invoicing-singleton'))->toEqual('invoicing-singleton'); + expect(app('infrastructure-singleton'))->toEqual('infrastructure-singleton'); }); }); diff --git a/tests/AutoloadingTest.php b/tests/AutoloadingTest.php deleted file mode 100644 index 8ed1eab..0000000 --- a/tests/AutoloadingTest.php +++ /dev/null @@ -1,40 +0,0 @@ - 'src/Domain', - // 'ddd.domain_namespace' => 'Domain', - // 'ddd.application_namespace' => 'Application', - // 'ddd.application_path' => 'src/Application', - // 'ddd.application_objects' => [ - // 'controller', - // 'request', - // 'middleware', - // ], - // 'ddd.layers' => [ - // 'Infrastructure' => 'src/Infrastructure', - // ], - // 'ddd.autoload_ignore' => [ - // 'Tests', - // 'Database/Migrations', - // ], - // 'cache.default' => 'file', - // ...static::$configValues, - // ]; - - // tap($app['config'], function (Repository $config) { - // foreach (static::$configValues as $key => $value) { - // $config->set($key, $value); - // } - // }); - // } -} From 5352bdd03e9a29521d0441b9062ad4bb0e0a8852 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 15:13:05 -0500 Subject: [PATCH 143/169] Cleanup --- tests/Autoload/CommandTest.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index da029af..3202d83 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -18,15 +18,6 @@ DomainCache::clear(); Artisan::call('ddd:clear'); - - expect(config('ddd.autoload_ignore'))->toEqualCanonicalizing([ - 'Tests', - 'Database/Migrations', - ]); - - foreach ($this->commands as $command) { - expect(class_exists($command))->toBeTrue("{$command} class does not exist"); - } }); afterEach(function () { From 7374d690156c5a1b26d673150a16c21a024b6fd7 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 16:56:06 -0500 Subject: [PATCH 144/169] Try restoring factory instance assertion. --- tests/Autoload/FactoryTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index 3b49942..2de51c0 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -62,6 +62,9 @@ expect(Factory::resolveFactoryName($modelClass)) ->toEqual('Database\Factories\RegularModelFactory'); + + expect($modelClass::factory()) + ->toBeInstanceOf('Database\Factories\RegularModelFactory'); }); }); From e18f7f8b11a92145167d5b046cf0d10099afcbe3 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 17:01:02 -0500 Subject: [PATCH 145/169] Remove app refresh calls. --- tests/Factory/DomainFactoryTest.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/Factory/DomainFactoryTest.php b/tests/Factory/DomainFactoryTest.php index 217ad26..ad38912 100644 --- a/tests/Factory/DomainFactoryTest.php +++ b/tests/Factory/DomainFactoryTest.php @@ -31,11 +31,7 @@ it('can instantiate a domain model factory', function ($domainParameter, $modelName, $modelClass) { $this->setupTestApplication(); - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); - }); - - $this->refreshApplicationWithConfig([ + config()->set([ 'ddd.base_model' => 'Lunarstorm\LaravelDDD\Models\DomainModel', 'ddd.autoload.factories' => true, ]); From b9755b92bcbb08633556c1033badc9254ada33ec Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 17:06:23 -0500 Subject: [PATCH 146/169] Revert --- tests/Factory/DomainFactoryTest.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/Factory/DomainFactoryTest.php b/tests/Factory/DomainFactoryTest.php index ad38912..3274504 100644 --- a/tests/Factory/DomainFactoryTest.php +++ b/tests/Factory/DomainFactoryTest.php @@ -31,7 +31,11 @@ it('can instantiate a domain model factory', function ($domainParameter, $modelName, $modelClass) { $this->setupTestApplication(); - config()->set([ + // $this->afterApplicationRefreshed(function () { + // app('ddd.autoloader')->boot(); + // }); + + $this->refreshApplicationWithConfig([ 'ddd.base_model' => 'Lunarstorm\LaravelDDD\Models\DomainModel', 'ddd.autoload.factories' => true, ]); From 2d1b19fdbcf70033f2252bf9bf6f1033e9029ba0 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 17:06:32 -0500 Subject: [PATCH 147/169] Revert 2 --- tests/Factory/DomainFactoryTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Factory/DomainFactoryTest.php b/tests/Factory/DomainFactoryTest.php index 3274504..217ad26 100644 --- a/tests/Factory/DomainFactoryTest.php +++ b/tests/Factory/DomainFactoryTest.php @@ -31,9 +31,9 @@ it('can instantiate a domain model factory', function ($domainParameter, $modelName, $modelClass) { $this->setupTestApplication(); - // $this->afterApplicationRefreshed(function () { - // app('ddd.autoloader')->boot(); - // }); + $this->afterApplicationRefreshed(function () { + app('ddd.autoloader')->boot(); + }); $this->refreshApplicationWithConfig([ 'ddd.base_model' => 'Lunarstorm\LaravelDDD\Models\DomainModel', From d3bc39affd8bbc4b9fca332ae8ac253a7e9f1ec9 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 18:13:33 -0500 Subject: [PATCH 148/169] Attempt to get the install/config/optimize tests working idempotently. --- src/ConfigManager.php | 4 ++- src/LaravelDDDServiceProvider.php | 2 +- tests/Command/ConfigTest.php | 52 ++++++++++++++++++++----------- tests/Command/InstallTest.php | 4 +-- tests/Command/OptimizeTest.php | 27 +++++++++------- tests/TestCase.php | 2 ++ 6 files changed, 56 insertions(+), 35 deletions(-) diff --git a/src/ConfigManager.php b/src/ConfigManager.php index 78668cd..39dcdfc 100755 --- a/src/ConfigManager.php +++ b/src/ConfigManager.php @@ -15,8 +15,10 @@ class ConfigManager protected string $stub; - public function __construct(public string $configPath) + public function __construct(public ?string $configPath = null) { + $this->configPath = $configPath ?? app()->configPath('ddd.php'); + $this->packageConfig = require DDD::packagePath('config/ddd.php'); $this->config = file_exists($configPath) ? require ($configPath) : $this->packageConfig; diff --git a/src/LaravelDDDServiceProvider.php b/src/LaravelDDDServiceProvider.php index cd21e3f..cb3965e 100644 --- a/src/LaravelDDDServiceProvider.php +++ b/src/LaravelDDDServiceProvider.php @@ -111,7 +111,7 @@ protected function registerBindings() }); $this->app->scoped(ConfigManager::class, function () { - return new ConfigManager(config_path('ddd.php')); + return new ConfigManager($this->app->configPath('ddd.php')); }); $this->app->scoped(StubManager::class, function () { diff --git a/tests/Command/ConfigTest.php b/tests/Command/ConfigTest.php index 08d4916..bf782b1 100644 --- a/tests/Command/ConfigTest.php +++ b/tests/Command/ConfigTest.php @@ -1,6 +1,6 @@ setupTestApplication(); - Artisan::call('config:clear'); - Artisan::call('ddd:clear'); -})->skip('causing issues in test suite'); + // $this->app->when(MigrationCreator::class) + // ->needs('$customStubPath') + // ->give(fn() => $this->app->basePath('stubs')); + + $this->originalComposerContents = file_get_contents(base_path('composer.json')); +}); afterEach(function () { $this->cleanSlate(); - Artisan::call('config:clear'); - Artisan::call('ddd:clear'); + file_put_contents(base_path('composer.json'), $this->originalComposerContents); }); it('can run the config wizard', function () { - Artisan::call('config:cache'); + $this->artisan('config:cache')->assertSuccessful()->execute(); expect(config('ddd.domain_path'))->toBe('src/Domain'); expect(config('ddd.domain_namespace'))->toBe('Domain'); @@ -29,7 +31,7 @@ 'Infrastructure' => 'src/Infrastructure', ]); - $path = config_path('ddd.php'); + $configPath = config_path('ddd.php'); $this->artisan('ddd:config') ->expectsQuestion('Laravel-DDD Config Utility', 'wizard') @@ -38,13 +40,13 @@ ->expectsQuestion('Path to Application Layer', null) ->expectsQuestion('Custom Layers (Optional)', ['Support' => 'src/Support']) ->expectsOutput('Building configuration...') - ->expectsOutput("Configuration updated: {$path}") + ->expectsOutput("Configuration updated: {$configPath}") ->assertSuccessful() ->execute(); - expect(file_exists($path))->toBeTrue(); + expect(file_exists($configPath))->toBeTrue(); - Artisan::call('config:cache'); + $this->artisan('config:cache')->assertSuccessful()->execute(); expect(config('ddd.domain_path'))->toBe('src/CustomDomain'); expect(config('ddd.domain_namespace'))->toBe('CustomDomain'); @@ -53,36 +55,42 @@ expect(config('ddd.layers'))->toBe([ 'Support' => 'src/Support', ]); + + $this->artisan('config:clear')->assertSuccessful()->execute(); + + unlink($configPath); }); it('can update and merge ddd.php with latest package version', function () { - $path = config_path('ddd.php'); + $configPath = config_path('ddd.php'); $originalContents = <<<'PHP' artisan('ddd:config') ->expectsQuestion('Laravel-DDD Config Utility', 'update') ->expectsQuestion('Are you sure you want to update ddd.php and merge with latest copy from the package?', true) ->expectsOutput('Merging ddd.php...') - ->expectsOutput("Configuration updated: {$path}") + ->expectsOutput("Configuration updated: {$configPath}") ->expectsOutput('Note: Some values may require manual adjustment.') ->assertSuccessful() ->execute(); $packageConfigContents = file_get_contents(DDD::packagePath('config/ddd.php')); - expect($updatedContents = file_get_contents($path)) + expect($updatedContents = file_get_contents($configPath)) ->not->toEqual($originalContents); - $updatedConfigArray = include $path; + $updatedConfigArray = include $configPath; $packageConfigArray = include DDD::packagePath('config/ddd.php'); expect($updatedConfigArray)->toHaveKeys(array_keys($packageConfigArray)); + + unlink($configPath); }); it('can sync composer.json from ddd.php ', function () { @@ -107,7 +115,7 @@ file_put_contents(config_path('ddd.php'), $configContent); - Artisan::call('config:cache'); + $this->artisan('config:cache')->assertSuccessful()->execute(); $composerContents = file_get_contents(base_path('composer.json')); @@ -146,6 +154,10 @@ $composerContents = file_get_contents(base_path('composer.json')); expect($composerContents)->toContain(...$fragments); + + $this->artisan('config:clear')->assertSuccessful()->execute(); + + unlink(config_path('ddd.php')); }); it('can detect domain namespace from composer.json', function () { @@ -156,6 +168,8 @@ $sampleComposer ); + $configPath = config_path('ddd.php'); + $this->artisan('ddd:config') ->expectsQuestion('Laravel-DDD Config Utility', 'detect') ->expectsOutputToContain(...[ @@ -166,7 +180,7 @@ 'Domain', ]) ->expectsQuestion('Update configuration with these values?', true) - ->expectsOutput('Configuration updated: '.config_path('ddd.php')) + ->expectsOutput('Configuration updated: '.$configPath) ->assertSuccessful() ->execute(); @@ -174,4 +188,6 @@ expect(data_get($configValues, 'domain_path'))->toBe('lib/CustomDomain'); expect(data_get($configValues, 'domain_namespace'))->toBe('Domain'); + + unlink($configPath); }); diff --git a/tests/Command/InstallTest.php b/tests/Command/InstallTest.php index 3969c96..54bbc2a 100644 --- a/tests/Command/InstallTest.php +++ b/tests/Command/InstallTest.php @@ -3,13 +3,11 @@ use Illuminate\Support\Facades\Config; beforeEach(function () { - $this->setupTestApplication(); - $this->originalComposerContents = file_get_contents(base_path('composer.json')); + $this->setupTestApplication(); }); afterEach(function () { - // $this->setupTestApplication()->composerReload(); file_put_contents(base_path('composer.json'), $this->originalComposerContents); }); diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index 510c311..653dca9 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -1,6 +1,5 @@ setupTestApplication(); - $this->artisan('optimize:clear')->execute(); - DomainCache::clear(); + + $this->originalComposerContents = file_get_contents(base_path('composer.json')); }); afterEach(function () { - $this->artisan('optimize:clear')->execute(); - DomainCache::clear(); + + file_put_contents(base_path('composer.json'), $this->originalComposerContents); }); it('can optimize discovered domain providers, commands, migrations', function () { @@ -46,7 +45,7 @@ }); it('can clear the cache', function () { - Artisan::call('ddd:optimize'); + $this->artisan('ddd:optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); expect(DomainCache::get('domain-commands'))->not->toBeNull(); @@ -67,20 +66,20 @@ expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); - $this->artisan('ddd:optimize')->execute(); + $this->artisan('ddd:optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); expect(DomainCache::get('domain-commands'))->not->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->not->toBeNull(); - $this->artisan('cache:clear')->execute(); + $this->artisan('cache:clear')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); expect(DomainCache::get('domain-commands'))->not->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->not->toBeNull(); if (Feature::LaravelPackageOptimizeCommands->missing()) { - $this->artisan('optimize:clear')->execute(); + $this->artisan('optimize:clear')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); expect(DomainCache::get('domain-commands'))->not->toBeNull(); @@ -94,24 +93,28 @@ expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); - $this->artisan('optimize')->execute(); + $this->artisan('optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); expect(DomainCache::get('domain-commands'))->not->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->not->toBeNull(); + + $this->artisan('optimize:clear')->assertSuccessful()->execute(); }); test('optimize:clear will clear ddd cache', function () { - $this->artisan('ddd:optimize')->execute(); + $this->artisan('ddd:optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); expect(DomainCache::get('domain-commands'))->not->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->not->toBeNull(); - $this->artisan('optimize:clear')->execute(); + $this->artisan('optimize:clear')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->toBeNull(); expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); + + $this->artisan('optimize:clear')->assertSuccessful()->execute(); }); })->skipOnLaravelVersionsBelow(Feature::LaravelPackageOptimizeCommands->value); diff --git a/tests/TestCase.php b/tests/TestCase.php index 5b54c0e..25ea735 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -211,12 +211,14 @@ protected function cleanSlate() File::cleanDirectory($basePath.'/app/Models'); File::cleanDirectory($basePath.'/database/factories'); + File::cleanDirectory($basePath.'/bootstrap/cache'); File::cleanDirectory($basePath.'/bootstrap/cache/ddd'); File::deleteDirectory($basePath.'/src'); File::deleteDirectory($basePath.'/resources/stubs/ddd'); File::deleteDirectory($basePath.'/stubs'); File::deleteDirectory($basePath.'/Custom'); + File::deleteDirectory($basePath.'/Other'); File::deleteDirectory($basePath.'/app/Policies'); File::deleteDirectory($basePath.'/app/Modules'); From 2fcc2967aceded0443ec8f4c2d2f5ededc952a06 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 18:35:41 -0500 Subject: [PATCH 149/169] Trying to resolve optimize issues --- tests/Command/ConfigTest.php | 2 ++ tests/Command/OptimizeTest.php | 6 ++++++ tests/TestCase.php | 3 +++ 3 files changed, 11 insertions(+) diff --git a/tests/Command/ConfigTest.php b/tests/Command/ConfigTest.php index bf782b1..7bef034 100644 --- a/tests/Command/ConfigTest.php +++ b/tests/Command/ConfigTest.php @@ -20,6 +20,8 @@ $this->cleanSlate(); file_put_contents(base_path('composer.json'), $this->originalComposerContents); + + $this->artisan('optimize:clear')->assertSuccessful()->execute(); }); it('can run the config wizard', function () { diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index 653dca9..6391e95 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -12,12 +12,18 @@ DomainCache::clear(); $this->originalComposerContents = file_get_contents(base_path('composer.json')); + + $this->artisan('clear-compiled')->assertSuccessful()->execute(); + $this->artisan('optimize:clear')->assertSuccessful()->execute(); }); afterEach(function () { DomainCache::clear(); file_put_contents(base_path('composer.json'), $this->originalComposerContents); + + $this->artisan('clear-compiled')->assertSuccessful()->execute(); + $this->artisan('optimize:clear')->assertSuccessful()->execute(); }); it('can optimize discovered domain providers, commands, migrations', function () { diff --git a/tests/TestCase.php b/tests/TestCase.php index 25ea735..54f6f89 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -46,6 +46,9 @@ protected function setUp(): void protected function tearDown(): void { + $this->artisan('clear-compiled')->assertSuccessful()->execute(); + $this->artisan('optimize:clear')->assertSuccessful()->execute(); + $basePath = $this->getBasePath(); $this->cleanSlate(); From 1dd94d9bc901e4132a7ca559152f39cb13ff22e2 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 19:14:23 -0500 Subject: [PATCH 150/169] More config/optimize fixes and config laravel 10 test clause --- src/Commands/ConfigCommand.php | 6 ++++++ tests/Command/ConfigTest.php | 15 ++++++++++++++- tests/Command/OptimizeTest.php | 8 ++++---- tests/Pest.php | 9 +++++++++ tests/TestCase.php | 7 ------- 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/Commands/ConfigCommand.php b/src/Commands/ConfigCommand.php index 581389c..046b91f 100644 --- a/src/Commands/ConfigCommand.php +++ b/src/Commands/ConfigCommand.php @@ -105,6 +105,12 @@ protected function layers() protected function wizard(): int { + if (! app('ddd')->laravelVersion(11)) { + $this->error('This command is only available in Laravel 11 and above.'); + + return self::FAILURE; + } + $namespaces = collect($this->composer->getPsr4Namespaces()); $layers = $namespaces->map(fn ($path, $namespace) => new Layer($namespace, $path)); diff --git a/tests/Command/ConfigTest.php b/tests/Command/ConfigTest.php index 7bef034..ae99a42 100644 --- a/tests/Command/ConfigTest.php +++ b/tests/Command/ConfigTest.php @@ -14,6 +14,9 @@ // ->give(fn() => $this->app->basePath('stubs')); $this->originalComposerContents = file_get_contents(base_path('composer.json')); + + // $this->artisan('clear-compiled')->assertSuccessful()->execute(); + // $this->artisan('optimize:clear')->assertSuccessful()->execute(); }); afterEach(function () { @@ -33,6 +36,8 @@ 'Infrastructure' => 'src/Infrastructure', ]); + $this->reloadApplication(); + $configPath = config_path('ddd.php'); $this->artisan('ddd:config') @@ -61,7 +66,15 @@ $this->artisan('config:clear')->assertSuccessful()->execute(); unlink($configPath); -}); +})->skipOnLaravelVersionsBelow(11); + +it('requires laravel 11 to run the wizard', function () { + $this->artisan('ddd:config') + ->expectsQuestion('Laravel-DDD Config Utility', 'wizard') + ->expectsOutput('This command is only available in Laravel 11 and above.') + ->assertFailure() + ->execute(); +})->onlyOnLaravelVersionsBelow(11); it('can update and merge ddd.php with latest package version', function () { $configPath = config_path('ddd.php'); diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index 6391e95..c8a0d4d 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -13,8 +13,8 @@ $this->originalComposerContents = file_get_contents(base_path('composer.json')); - $this->artisan('clear-compiled')->assertSuccessful()->execute(); - $this->artisan('optimize:clear')->assertSuccessful()->execute(); + // $this->artisan('clear-compiled')->assertSuccessful()->execute(); + // $this->artisan('optimize:clear')->assertSuccessful()->execute(); }); afterEach(function () { @@ -22,8 +22,8 @@ file_put_contents(base_path('composer.json'), $this->originalComposerContents); - $this->artisan('clear-compiled')->assertSuccessful()->execute(); - $this->artisan('optimize:clear')->assertSuccessful()->execute(); + // $this->artisan('clear-compiled')->assertSuccessful()->execute(); + // $this->artisan('optimize:clear')->assertSuccessful()->execute(); }); it('can optimize discovered domain providers, commands, migrations', function () { diff --git a/tests/Pest.php b/tests/Pest.php index d31d21e..da75808 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -13,6 +13,15 @@ function skipOnLaravelVersionsBelow($minimumVersion) } } +function onlyOnLaravelVersionsBelow($minimumVersion) +{ + $version = app()->version(); + + if (! version_compare($version, $minimumVersion, '<')) { + test()->markTestSkipped("Does not apply to Laravel {$minimumVersion}+ (Current version: {$version})."); + } +} + function setConfigValues(array $values) { TestCase::configValues($values); diff --git a/tests/TestCase.php b/tests/TestCase.php index 54f6f89..c86b26d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -38,17 +38,10 @@ protected function setUp(): void }); parent::setUp(); - - // if (in_array(BootsTestApplication::class, class_uses_recursive($this))) { - // $this->composerReload(); - // } } protected function tearDown(): void { - $this->artisan('clear-compiled')->assertSuccessful()->execute(); - $this->artisan('optimize:clear')->assertSuccessful()->execute(); - $basePath = $this->getBasePath(); $this->cleanSlate(); From db9480b180b46a52c305c4bbdfb48fefe553b611 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 19:17:36 -0500 Subject: [PATCH 151/169] Syntax typo --- tests/Command/ConfigTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Command/ConfigTest.php b/tests/Command/ConfigTest.php index ae99a42..5b0c7e2 100644 --- a/tests/Command/ConfigTest.php +++ b/tests/Command/ConfigTest.php @@ -72,7 +72,7 @@ $this->artisan('ddd:config') ->expectsQuestion('Laravel-DDD Config Utility', 'wizard') ->expectsOutput('This command is only available in Laravel 11 and above.') - ->assertFailure() + ->assertFailed() ->execute(); })->onlyOnLaravelVersionsBelow(11); From 5c89d4c12d2d086f3ff3a953e0ee28e1d3a348fc Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 19:27:21 -0500 Subject: [PATCH 152/169] Check existence of prompt form() function --- src/Commands/ConfigCommand.php | 4 ++-- tests/Command/ConfigTest.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Commands/ConfigCommand.php b/src/Commands/ConfigCommand.php index 046b91f..1f8872d 100644 --- a/src/Commands/ConfigCommand.php +++ b/src/Commands/ConfigCommand.php @@ -105,8 +105,8 @@ protected function layers() protected function wizard(): int { - if (! app('ddd')->laravelVersion(11)) { - $this->error('This command is only available in Laravel 11 and above.'); + if (! function_exists('\Laravel\Prompts\form')) { + $this->error('This command is not supported with your currently installed version of Laravel Prompts.'); return self::FAILURE; } diff --git a/tests/Command/ConfigTest.php b/tests/Command/ConfigTest.php index 5b0c7e2..5d6ef8e 100644 --- a/tests/Command/ConfigTest.php +++ b/tests/Command/ConfigTest.php @@ -66,15 +66,15 @@ $this->artisan('config:clear')->assertSuccessful()->execute(); unlink($configPath); -})->skipOnLaravelVersionsBelow(11); +})->skip(fn () => ! function_exists('\Laravel\Prompts\form')); -it('requires laravel 11 to run the wizard', function () { +it('requires \Laravel\Prompts\form to run the wizard', function () { $this->artisan('ddd:config') ->expectsQuestion('Laravel-DDD Config Utility', 'wizard') - ->expectsOutput('This command is only available in Laravel 11 and above.') + ->expectsOutput('This command is not supported with your currently installed version of Laravel Prompts.') ->assertFailed() ->execute(); -})->onlyOnLaravelVersionsBelow(11); +})->skip(fn () => function_exists('\Laravel\Prompts\form')); it('can update and merge ddd.php with latest package version', function () { $configPath = config_path('ddd.php'); From 691268ad165c0c09f65f56728dafe92fd5fdfca8 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 19:46:50 -0500 Subject: [PATCH 153/169] Fine tune the laravel prompts support. --- composer.json | 2 +- src/Commands/ConfigCommand.php | 8 +++++++- tests/Command/ConfigTest.php | 7 ++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 2344d67..fe107dc 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "php": "^8.1|^8.2|^8.3", "illuminate/contracts": "^10.25|^11.0", "laravel/pint": "^1.18", - "laravel/prompts": "^0.1.16|^0.3.1", + "laravel/prompts": "^0.1.16|^0.2|^0.3.1", "lorisleiva/lody": "^0.5.0", "spatie/laravel-package-tools": "^1.13.0", "symfony/var-exporter": "^6|^7.1" diff --git a/src/Commands/ConfigCommand.php b/src/Commands/ConfigCommand.php index 1f8872d..e4acee0 100644 --- a/src/Commands/ConfigCommand.php +++ b/src/Commands/ConfigCommand.php @@ -103,9 +103,15 @@ protected function layers() return self::SUCCESS; } + public static function hasRequiredVersionOfLaravelPrompts(): bool + { + return function_exists('\Laravel\Prompts\form') + && method_exists(\Laravel\Prompts\FormBuilder::class, 'addIf'); + } + protected function wizard(): int { - if (! function_exists('\Laravel\Prompts\form')) { + if (! static::hasRequiredVersionOfLaravelPrompts()) { $this->error('This command is not supported with your currently installed version of Laravel Prompts.'); return self::FAILURE; diff --git a/tests/Command/ConfigTest.php b/tests/Command/ConfigTest.php index 5d6ef8e..5627e9f 100644 --- a/tests/Command/ConfigTest.php +++ b/tests/Command/ConfigTest.php @@ -1,6 +1,7 @@ artisan('config:clear')->assertSuccessful()->execute(); unlink($configPath); -})->skip(fn () => ! function_exists('\Laravel\Prompts\form')); +})->skip(fn () => ! ConfigCommand::hasRequiredVersionOfLaravelPrompts()); -it('requires \Laravel\Prompts\form to run the wizard', function () { +it('requires supported version of Laravel Prompts to run the wizard', function () { $this->artisan('ddd:config') ->expectsQuestion('Laravel-DDD Config Utility', 'wizard') ->expectsOutput('This command is not supported with your currently installed version of Laravel Prompts.') ->assertFailed() ->execute(); -})->skip(fn () => function_exists('\Laravel\Prompts\form')); +})->skip(fn () => ConfigCommand::hasRequiredVersionOfLaravelPrompts()); it('can update and merge ddd.php with latest package version', function () { $configPath = config_path('ddd.php'); From 07ab68f218428832434680669d125077383e3588 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 19:52:29 -0500 Subject: [PATCH 154/169] Try adding reloadApplication hooks --- tests/Command/OptimizeTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index c8a0d4d..109e714 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -99,6 +99,8 @@ expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); + $this->reloadApplication(); + $this->artisan('optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); @@ -115,6 +117,8 @@ expect(DomainCache::get('domain-commands'))->not->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->not->toBeNull(); + $this->reloadApplication(); + $this->artisan('optimize:clear')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->toBeNull(); From afb0b69172058fc631030ad0639fef6670487400 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 19:58:32 -0500 Subject: [PATCH 155/169] wip --- tests/Command/OptimizeTest.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index 109e714..fc11da0 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -94,13 +94,22 @@ }); describe('laravel optimize', function () { + beforeEach(function () { + $this->artisan('clear-compiled')->assertSuccessful()->execute(); + $this->artisan('optimize:clear')->assertSuccessful()->execute(); + }); + + afterEach(function () { + $this->artisan('clear-compiled')->assertSuccessful()->execute(); + $this->artisan('optimize:clear')->assertSuccessful()->execute(); + $this->reloadApplication(); + }); + test('optimize will include ddd:optimize', function () { expect(DomainCache::get('domain-providers'))->toBeNull(); expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); - $this->reloadApplication(); - $this->artisan('optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); @@ -117,8 +126,6 @@ expect(DomainCache::get('domain-commands'))->not->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->not->toBeNull(); - $this->reloadApplication(); - $this->artisan('optimize:clear')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->toBeNull(); From 6345fccecf818b2e50f9125a1d953a753e4d9075 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 20:04:18 -0500 Subject: [PATCH 156/169] Bump laravel-data dev dependency --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fe107dc..882a0f7 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "spatie/laravel-data": "^4.10" + "spatie/laravel-data": "^4.11.1" }, "suggest": { "spatie/laravel-data": "Recommended for Data Transfer Objects.", From 62eee63b09490cd665e9668b87f56c4b469f7278 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 20:06:45 -0500 Subject: [PATCH 157/169] Try disabling laravel-data caching --- tests/Command/OptimizeTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index fc11da0..c854c89 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -15,6 +15,7 @@ // $this->artisan('clear-compiled')->assertSuccessful()->execute(); // $this->artisan('optimize:clear')->assertSuccessful()->execute(); + config()->set('data.structure_caching.enabled', false); }); afterEach(function () { From fab902567ae8201e848bc9c220fb28319fd0b049 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 20:09:54 -0500 Subject: [PATCH 158/169] Disable laravel-data caching in test environtment globally. --- tests/Command/OptimizeTest.php | 7 ------- tests/TestCase.php | 3 +++ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index c854c89..49ab16e 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -12,19 +12,12 @@ DomainCache::clear(); $this->originalComposerContents = file_get_contents(base_path('composer.json')); - - // $this->artisan('clear-compiled')->assertSuccessful()->execute(); - // $this->artisan('optimize:clear')->assertSuccessful()->execute(); - config()->set('data.structure_caching.enabled', false); }); afterEach(function () { DomainCache::clear(); file_put_contents(base_path('composer.json'), $this->originalComposerContents); - - // $this->artisan('clear-compiled')->assertSuccessful()->execute(); - // $this->artisan('optimize:clear')->assertSuccessful()->execute(); }); it('can optimize discovered domain providers, commands, migrations', function () { diff --git a/tests/TestCase.php b/tests/TestCase.php index c86b26d..4063b5d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -31,6 +31,8 @@ protected function setUp(): void ); DomainCache::clear(); + + config()->set('data.structure_caching.enabled', false); }); $this->beforeApplicationDestroyed(function () { @@ -90,6 +92,7 @@ protected function defineEnvironment($app) ], 'ddd.cache_directory' => 'bootstrap/cache/ddd', 'cache.default' => 'file', + 'data.structure_caching.enabled' => false, ...static::$configValues, ]; } From 4255b321875a74063a9686b20ce5c8d5d0310af9 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 20:17:51 -0500 Subject: [PATCH 159/169] Disable data caching in multiple hooks --- tests/TestCase.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/TestCase.php b/tests/TestCase.php index 4063b5d..eea2819 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -31,7 +31,9 @@ protected function setUp(): void ); DomainCache::clear(); + }); + $this->afterApplicationRefreshed(function () { config()->set('data.structure_caching.enabled', false); }); @@ -40,6 +42,8 @@ protected function setUp(): void }); parent::setUp(); + + config()->set('data.structure_caching.enabled', false); } protected function tearDown(): void @@ -264,6 +268,8 @@ protected function setupTestApplication() DomainCache::clear(); + config()->set('data.structure_caching.enabled', false); + return $this; } From ce6e7e7f6ab8a87caee323f4bbbd6f1c3a397b7d Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 20:18:17 -0500 Subject: [PATCH 160/169] Cont'd --- tests/TestCase.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/TestCase.php b/tests/TestCase.php index eea2819..7c1fe02 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -31,6 +31,8 @@ protected function setUp(): void ); DomainCache::clear(); + + config()->set('data.structure_caching.enabled', false); }); $this->afterApplicationRefreshed(function () { From 3eed572dc86e9fd166b61b990e4608be30403695 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 20:21:37 -0500 Subject: [PATCH 161/169] Update optimize hooks --- tests/Command/OptimizeTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index 49ab16e..3ac6379 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -89,14 +89,11 @@ describe('laravel optimize', function () { beforeEach(function () { - $this->artisan('clear-compiled')->assertSuccessful()->execute(); $this->artisan('optimize:clear')->assertSuccessful()->execute(); }); afterEach(function () { - $this->artisan('clear-compiled')->assertSuccessful()->execute(); $this->artisan('optimize:clear')->assertSuccessful()->execute(); - $this->reloadApplication(); }); test('optimize will include ddd:optimize', function () { From 60e93a4bbdf7c3b387bbef95a0d7a61d32ad8470 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 20:27:02 -0500 Subject: [PATCH 162/169] reset data config --- tests/Command/OptimizeTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index 3ac6379..862f781 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -90,6 +90,7 @@ describe('laravel optimize', function () { beforeEach(function () { $this->artisan('optimize:clear')->assertSuccessful()->execute(); + config()->set('data.structure_caching.enabled', false); }); afterEach(function () { From cdf0b9561caa20661a6bcba6cdba1b3968e1e51e Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 20:30:54 -0500 Subject: [PATCH 163/169] try again --- tests/Command/OptimizeTest.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index 862f781..b1e4b5e 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -88,20 +88,12 @@ }); describe('laravel optimize', function () { - beforeEach(function () { - $this->artisan('optimize:clear')->assertSuccessful()->execute(); - config()->set('data.structure_caching.enabled', false); - }); - - afterEach(function () { - $this->artisan('optimize:clear')->assertSuccessful()->execute(); - }); - test('optimize will include ddd:optimize', function () { expect(DomainCache::get('domain-providers'))->toBeNull(); expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); + config()->set('data.structure_caching.enabled', false); $this->artisan('optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); @@ -112,6 +104,7 @@ }); test('optimize:clear will clear ddd cache', function () { + config()->set('data.structure_caching.enabled', false); $this->artisan('ddd:optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); From 3b98b16796f01d5f1688a7288bc312efb210ecd0 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 20:39:10 -0500 Subject: [PATCH 164/169] update --- tests/Command/OptimizeTest.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index b1e4b5e..00ef1a4 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -18,6 +18,8 @@ DomainCache::clear(); file_put_contents(base_path('composer.json'), $this->originalComposerContents); + + config()->set('data.structure_caching.enabled', false); }); it('can optimize discovered domain providers, commands, migrations', function () { @@ -45,6 +47,8 @@ }); it('can clear the cache', function () { + config()->set('data.structure_caching.enabled', false); + $this->artisan('ddd:optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); @@ -62,6 +66,8 @@ }); it('will not be cleared by laravel cache clearing', function () { + config()->set('data.structure_caching.enabled', false); + expect(DomainCache::get('domain-providers'))->toBeNull(); expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); @@ -89,11 +95,12 @@ describe('laravel optimize', function () { test('optimize will include ddd:optimize', function () { + config()->set('data.structure_caching.enabled', false); + expect(DomainCache::get('domain-providers'))->toBeNull(); expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); - config()->set('data.structure_caching.enabled', false); $this->artisan('optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); @@ -105,6 +112,7 @@ test('optimize:clear will clear ddd cache', function () { config()->set('data.structure_caching.enabled', false); + $this->artisan('ddd:optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); From a60389a3d13ce2e58ba11cae6d8f07ca06f39483 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 20:45:45 -0500 Subject: [PATCH 165/169] try again --- tests/Command/OptimizeTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index 00ef1a4..4f06700 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -20,6 +20,7 @@ file_put_contents(base_path('composer.json'), $this->originalComposerContents); config()->set('data.structure_caching.enabled', false); + $this->artisan('optimize:clear')->assertSuccessful()->execute(); }); it('can optimize discovered domain providers, commands, migrations', function () { From 69eebea4cfb0397ac01c7a6e4b8c2f360c24f6bd Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 20:48:00 -0500 Subject: [PATCH 166/169] Try adding data config in test setup --- tests/TestCase.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/TestCase.php b/tests/TestCase.php index 7c1fe02..ef67324 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -111,6 +111,8 @@ protected function defineEnvironment($app) foreach ($this->appConfig as $key => $value) { $config->set($key, $value); } + + $config->set('data.structure_caching.enabled', false); }); } From 66db9ca9a6d893ca7e590bd203f0925e47181119 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 21:02:12 -0500 Subject: [PATCH 167/169] Try creating mock command --- tests/TestCase.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index ef67324..77ea059 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Config\Repository; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\File; use Lunarstorm\LaravelDDD\LaravelDDDServiceProvider; use Lunarstorm\LaravelDDD\Support\DomainCache; @@ -33,6 +34,12 @@ protected function setUp(): void DomainCache::clear(); config()->set('data.structure_caching.enabled', false); + + Artisan::command('data:cache-structures', function () { + // do nothing + dd('do nothing'); + + }); }); $this->afterApplicationRefreshed(function () { @@ -44,8 +51,6 @@ protected function setUp(): void }); parent::setUp(); - - config()->set('data.structure_caching.enabled', false); } protected function tearDown(): void From c4a011f05192cf9b6e1012b4ab677fb41ad96f31 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 21:05:46 -0500 Subject: [PATCH 168/169] Remove dump. --- tests/TestCase.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 77ea059..ce3a5d4 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -37,7 +37,6 @@ protected function setUp(): void Artisan::command('data:cache-structures', function () { // do nothing - dd('do nothing'); }); }); From d9e812baf1007b56debd435eb6fbaa35ed44a3e8 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Sun, 17 Nov 2024 21:14:19 -0500 Subject: [PATCH 169/169] Cleanup --- src/Support/AutoloadManager.php | 15 --------------- tests/Command/OptimizeTest.php | 9 --------- tests/TestCase.php | 5 +---- 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/src/Support/AutoloadManager.php b/src/Support/AutoloadManager.php index 58ec892..90bf8cb 100644 --- a/src/Support/AutoloadManager.php +++ b/src/Support/AutoloadManager.php @@ -49,8 +49,6 @@ class AutoloadManager protected bool $ran = false; - protected static ?Closure $registeringProviderCallback = null; - public function __construct(protected ?Container $container = null) { $this->container = $container ?? Container::getInstance(); @@ -133,11 +131,6 @@ protected function getCustomLayerPaths(): array ])->map(fn ($path) => Path::normalize($this->app->basePath($path)))->toArray(); } - public static function registeringProvider(Closure $callback) - { - static::$registeringProviderCallback = $callback; - } - protected function handleProviders() { $providers = DomainCache::has('domain-providers') @@ -166,19 +159,11 @@ protected function handleCommands() public function run() { - // if ($this->hasRun()) { - // return $this; - // } - if (! $this->isBooted()) { $this->boot(); } foreach (static::$registeredProviders as $provider) { - if (static::$registeringProviderCallback) { - call_user_func(static::$registeringProviderCallback, $provider); - } - $this->app->register($provider); } diff --git a/tests/Command/OptimizeTest.php b/tests/Command/OptimizeTest.php index 4f06700..f2db8a4 100644 --- a/tests/Command/OptimizeTest.php +++ b/tests/Command/OptimizeTest.php @@ -19,7 +19,6 @@ file_put_contents(base_path('composer.json'), $this->originalComposerContents); - config()->set('data.structure_caching.enabled', false); $this->artisan('optimize:clear')->assertSuccessful()->execute(); }); @@ -48,8 +47,6 @@ }); it('can clear the cache', function () { - config()->set('data.structure_caching.enabled', false); - $this->artisan('ddd:optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); @@ -67,8 +64,6 @@ }); it('will not be cleared by laravel cache clearing', function () { - config()->set('data.structure_caching.enabled', false); - expect(DomainCache::get('domain-providers'))->toBeNull(); expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); @@ -96,8 +91,6 @@ describe('laravel optimize', function () { test('optimize will include ddd:optimize', function () { - config()->set('data.structure_caching.enabled', false); - expect(DomainCache::get('domain-providers'))->toBeNull(); expect(DomainCache::get('domain-commands'))->toBeNull(); expect(DomainCache::get('domain-migration-paths'))->toBeNull(); @@ -112,8 +105,6 @@ }); test('optimize:clear will clear ddd cache', function () { - config()->set('data.structure_caching.enabled', false); - $this->artisan('ddd:optimize')->assertSuccessful()->execute(); expect(DomainCache::get('domain-providers'))->not->toBeNull(); diff --git a/tests/TestCase.php b/tests/TestCase.php index ce3a5d4..a7ba0a9 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -35,10 +35,7 @@ protected function setUp(): void config()->set('data.structure_caching.enabled', false); - Artisan::command('data:cache-structures', function () { - // do nothing - - }); + Artisan::command('data:cache-structures', function () {}); }); $this->afterApplicationRefreshed(function () {