Skip to content

Commit

Permalink
Tighten up the generator blueprint and add more test coverage.
Browse files Browse the repository at this point in the history
  • Loading branch information
JasperTey committed Nov 16, 2024
1 parent 18ee64d commit 36af4eb
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 52 deletions.
35 changes: 0 additions & 35 deletions src/Commands/Concerns/CanPromptForDomain.php

This file was deleted.

34 changes: 31 additions & 3 deletions src/Commands/Concerns/ResolvesDomainFromInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand Down
43 changes: 29 additions & 14 deletions src/Support/GeneratorBlueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class GeneratorBlueprint
{
public string $nameInput;

public string $normalizedName;

public string $baseName;

public string $domainName;

public ?Domain $domain = null;
Expand All @@ -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) {
Expand Down Expand Up @@ -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),
Expand Down
27 changes: 27 additions & 0 deletions tests/Datasets/GeneratorSchemas.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

dataset('generatorSchemas', [
'ddd:model Invoicing:Invoice' => [
'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',
],
],
]);
123 changes: 123 additions & 0 deletions tests/Support/BlueprintTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

use Lunarstorm\LaravelDDD\Support\GeneratorBlueprint;

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',
'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'],
]);
2 changes: 2 additions & 0 deletions tests/Support/DomainTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
]);

Expand Down

0 comments on commit 36af4eb

Please sign in to comment.