From 79ea5ad4bff59d0f630c8d31838883448450dec7 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Tue, 15 Oct 2024 17:16:49 -0400 Subject: [PATCH] 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'));