Skip to content

Commit

Permalink
Improve the support for base models.
Browse files Browse the repository at this point in the history
  • Loading branch information
JasperTey committed Nov 12, 2023
1 parent f94768c commit ebade11
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 20 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

All notable changes to `laravel-ddd` will be documented in this file.

## [Unversioned]
### Changed
- Implement more robust handling of base models when generating a domain model with `ddd:model`:
- If the configured base model class in `ddd.base_model` exists (evaluated using `class_exists`), base model creation is skipped.
- If the configured base model class does not exist, base model will be created, but only if the class falls under a domain scope.
- Examples of falling under domain scope: `Domain\Shared\Models\CustomDomainBaseModel`, `Domain\Infrastructure\Models\DomainModel`.
- Examples of not falling under domain scope: `App\Models\CustomAppBaseModel`, `Illuminate\Database\Eloquent\NonExistentModel`.

### Fixed
- Resolve long-standing issue where `ddd:model` would not properly detect whether the configured basemodel in `ddd.base_model` already exists, leading to unpredictable results when `ddd.base_model` deviated from the default `Domain\Shared\Models\BaseModel`.

## [0.7.0] - 2023-10-22
### Added
- Formal support for subdomains (nested domains). For example, to generate model `Domain\Reporting\Internal\Models\InvoiceReport`, the domain argument can be specified with dot notation: `ddd:model Reporting.Internal InvoiceReport`. Specifying `Reporting/Internal` or `Reporting\\Internal` will also be accepted and normalized to dot notation internally.
Expand Down
28 changes: 22 additions & 6 deletions src/Commands/MakeModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Lunarstorm\LaravelDDD\Commands;

use Lunarstorm\LaravelDDD\Support\DomainResolver;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

Expand Down Expand Up @@ -75,17 +76,32 @@ protected function createBaseModelIfNeeded($baseModel)
return;
}

$parts = str($baseModel)->explode('\\');
$baseModelName = $parts->last();
$baseModelPath = $this->getPath($baseModel);
$this->warn("Configured base model {$baseModel} doesn't exist.");

// If the base model is out of scope, we won't attempt to create it
// because we don't want to interfere with outside folders.
$allowedRootNamespaces = [
$this->rootNamespace(),
];

if (! str($baseModel)->startsWith($allowedRootNamespaces)) {
return;
}

// base_path(config('ddd.paths.domains') . '/Shared/Models/BaseModel.php')
$domain = DomainResolver::fromClass($baseModel);

if (! $domain) {
return;
}

$baseModelName = class_basename($baseModel);
$baseModelPath = $this->getPath($baseModel);

if (! file_exists($baseModelPath)) {
$this->warn("Base model {$baseModel} doesn't exist, generating...");
$this->info("Generating {$baseModel}...");

$this->call(MakeBaseModel::class, [
'domain' => 'Shared',
'domain' => $domain->domain,
'name' => $baseModelName,
]);
}
Expand Down
17 changes: 16 additions & 1 deletion src/Support/DomainResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,24 @@

namespace Lunarstorm\LaravelDDD\Support;

use Illuminate\Support\Str;

class DomainResolver
{
public static function fromModelClass(string $modelClass)
public static function fromClass(string $class): ?Domain
{
$domainNamespace = basename(config('ddd.paths.domains')).'\\';

if (! Str::startsWith($class, $domainNamespace)) {
// Not a domain model
return null;
}

$domain = str($class)
->after($domainNamespace)
->before('\\')
->toString();

return new Domain($domain);
}
}
43 changes: 30 additions & 13 deletions tests/Generator/MakeModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,12 @@
expect(file_exists($expectedModelPath))->toBeTrue();
})->with('makeModelInputs');

it('generates the base model when possible', function () {
it('generates the base model when possible', function ($baseModelClass, $baseModelPath) {
$modelName = Str::studly(fake()->word());
$domain = Str::studly(fake()->word());

Config::set('ddd.base_model', $baseModelClass);

$expectedModelPath = base_path(implode('/', [
config('ddd.paths.domains'),
$domain,
Expand All @@ -123,23 +125,36 @@

expect(file_exists($expectedModelPath))->toBeFalse();

// This currently only tests for the default base model
$expectedBaseModelPath = base_path(config('ddd.paths.domains').'/Shared/Models/BaseModel.php');
$expectedBaseModelPath = base_path($baseModelPath);

if (file_exists($expectedBaseModelPath)) {
unlink($expectedBaseModelPath);
}

// Todo: should bypass base model creation if
// a custom base model is being used.
// $baseModel = config('ddd.base_model');

expect(file_exists($expectedBaseModelPath))->toBeFalse();
expect(file_exists($expectedBaseModelPath))->toBeFalse("{$baseModelPath} expected not to exist.");

Artisan::call("ddd:model {$domain} {$modelName}");

expect(file_exists($expectedBaseModelPath))->toBeTrue();
});
expect(file_exists($expectedBaseModelPath))->toBeTrue("{$baseModelPath} expected to exist.");
})->with([
['Domain\\Shared\\Models\\BaseModel', 'src/Domain/Shared/Models/BaseModel.php'],
['Domain\\Infrastructure\\Models\\BaseModel', 'src/Domain/Infrastructure/Models/BaseModel.php'],
]);

it('will not create a base model if the configured base model is out of scope', function ($baseModel) {
Config::set('ddd.base_model', $baseModel);

expect(class_exists($baseModel))->toBeFalse();

Artisan::call('ddd:model Fruits Lemon');

expect(Artisan::output())
->toContain("Configured base model {$baseModel} doesn't exist.")
->not->toContain("Generating {$baseModel}");
})->with([
['Illuminate\\Database\\Eloquent\\NonExistentModel'],
['OtherVendor\\OtherPackage\\Models\\OtherModel'],
]);

it('skips base model creation if configured base model already exists', function ($baseModel) {
Config::set('ddd.base_model', $baseModel);
Expand All @@ -148,10 +163,12 @@

Artisan::call('ddd:model Fruits Lemon');

expect(Artisan::output())->not->toContain("Base model {$baseModel} doesn't exist, generating...");
expect(Artisan::output())
->not->toContain("Configured base model {$baseModel} doesn't exist.")
->not->toContain("Generating {$baseModel}");
})->with([
['Illuminate\Database\Eloquent\Model'],
['Lunarstorm\LaravelDDD\Models\DomainModel'],
['Illuminate\\Database\\Eloquent\\Model'],
['Lunarstorm\\LaravelDDD\\Models\\DomainModel'],
]);

it('shows meaningful hints when prompting for missing input', function () {
Expand Down

0 comments on commit ebade11

Please sign in to comment.