Skip to content

Commit

Permalink
Refactor internals:
Browse files Browse the repository at this point in the history
- implement abstract DomainFactory (extends Factory).
- DomainFactory::resolveFactoryName() implements custom domain factory resolution.
- Ensure factory resolution respects customizations made in ddd config.
- Simplify base-model stub's newFactory method to use DomainFactory::factoryForModel().
- Refactor the API of the internal Domain support class.
- Add more test coverage.
  • Loading branch information
JasperTey committed Oct 22, 2023
1 parent 1d057d9 commit f9b6742
Show file tree
Hide file tree
Showing 17 changed files with 446 additions and 105 deletions.
8 changes: 6 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,19 @@
},
"autoload-dev": {
"psr-4": {
"Lunarstorm\\LaravelDDD\\Tests\\": "tests"
"Lunarstorm\\LaravelDDD\\Tests\\": "tests",
"App\\": "vendor/orchestra/testbench-core/laravel/app/",
"Database\\Factories\\": "vendor/orchestra/testbench-core/laravel/database/factories/",
"Domain\\": "vendor/orchestra/testbench-core/laravel/src/Domain/"
}
},
"scripts": {
"post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi",
"analyse": "vendor/bin/phpstan analyse",
"test": "vendor/bin/pest",
"test-coverage": "vendor/bin/pest --coverage",
"format": "vendor/bin/pint"
"format": "vendor/bin/pint",
"lint": "vendor/bin/pint"
},
"config": {
"sort-packages": true,
Expand Down
1 change: 1 addition & 0 deletions src/Commands/DomainGeneratorCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ protected function getDomain()
{
return str($this->getDomainInput())
->trim()
->replace(['.', '/'], '\\')
->studly()
->toString();
}
Expand Down
15 changes: 9 additions & 6 deletions src/Commands/MakeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,17 @@ protected function preparePlaceholders(): array

$name = $this->getNameInput();

$namespacedModel = $this->option('model')
? $domain->namespacedModel($this->option('model'))
: $domain->namespacedModel($this->guessModelName($name));
$modelName = $this->option('model') ?: $this->guessModelName($name);

$domainModel = $domain->model($modelName);

$domainFactory = $domain->factory($name);

return [
'namespacedModel' => $namespacedModel,
'model' => class_basename($namespacedModel),
'namespacedModel' => $domainModel->fqn,
'model' => class_basename($domainModel->fqn),
'factory' => $this->getFactoryName(),
'namespace' => $domainFactory->namespace,
];
}

Expand All @@ -109,6 +112,6 @@ protected function guessModelName($name)
$name = substr($name, 0, -7);
}

return (new Domain($this->getDomain()))->namespacedModel($name);
return (new Domain($this->getDomain()))->model($name)->name;
}
}
53 changes: 53 additions & 0 deletions src/Factories/DomainFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Lunarstorm\LaravelDDD\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

abstract class DomainFactory extends Factory
{
/**
* Get the domain namespace.
*
* @return string
*/
protected static function domainNamespace()
{
return basename(config('ddd.paths.domains')).'\\';
}

/**
* Get the factory name for the given domain model name.
*
* @param class-string<\Illuminate\Database\Eloquent\Model> $modelName
* @return null|class-string<\Illuminate\Database\Eloquent\Factories\Factory>
*/
public static function resolveFactoryName(string $modelName)
{
$resolver = function (string $modelName) {
$domainNamespace = static::domainNamespace();
$modelNamespace = config('ddd.namespaces.models');

// Expected domain model FQN:
// {DomainNamespace}\{Domain}\{ModelNamespace}\{Model}

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

$domain = str($modelName)
->after($domainNamespace)
->beforeLast($modelNamespace)
->trim('\\')
->toString();

$modelBaseName = class_basename($modelName);

return static::$namespace."{$domain}\\{$modelBaseName}Factory";
};

return $resolver($modelName);
}
}
18 changes: 18 additions & 0 deletions src/Models/DomainModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Lunarstorm\LaravelDDD\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Lunarstorm\LaravelDDD\Factories\DomainFactory;

abstract class DomainModel
{
use HasFactory;

protected $guarded = [];

protected static function newFactory()
{
return DomainFactory::factoryForModel(get_called_class());
}
}
164 changes: 146 additions & 18 deletions src/Support/Domain.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,164 @@

namespace Lunarstorm\LaravelDDD\Support;

use Lunarstorm\LaravelDDD\ValueObjects\DomainNamespaces;
use Lunarstorm\LaravelDDD\ValueObjects\DomainObject;

class Domain
{
public string $domainRoot;
public readonly string $dotName;

public readonly string $path;

public readonly string $domain;

public readonly ?string $subdomain;

public function __construct(public string $domain)
public readonly string $domainWithSubdomain;

public readonly DomainNamespaces $namespace;

public function __construct(string $domain, string $subdomain = null)
{
$this->domainRoot = basename(config('ddd.paths.domains'));
if (is_null($subdomain)) {
// If a subdomain isn't explicitly specified, we
// will attempt to parse it from the domain.
$parts = str($domain)
->replace(['\\', '/'], '.')
->explode('.')
->filter();

$domain = $parts->shift();

if ($parts->count() > 0) {
$subdomain = $parts->implode('.');
}
}

$domain = str($domain)->trim('\\/')->toString();

$subdomain = str($subdomain)->trim('\\/')->toString();

$this->domainWithSubdomain = str($domain)
->when($subdomain, fn ($domain) => $domain->append("\\{$subdomain}"))
->toString();

$this->domain = $domain;

$this->subdomain = $subdomain ?: null;

$this->dotName = $this->subdomain
? "{$this->domain}.{$this->subdomain}"
: $this->domain;

$this->namespace = DomainNamespaces::from($this->domain, $this->subdomain);

$this->path = str(app()->joinPaths(config('ddd.paths.domains'), $this->domainWithSubdomain))
->replace(['\\', '/'], DIRECTORY_SEPARATOR)
->toString();
}

public function relativePath(string $path = ''): string
protected function getDomainBasePath()
{
return implode(DIRECTORY_SEPARATOR, [
$this->domain,
$path,
]);
return app()->basePath(config('ddd.paths.domains'));
}

public function namespacedModel(?string $model): string
public function path(string $path = null): string
{
$prefix = implode('\\', [
$this->domainRoot,
$this->domain,
config('ddd.namespaces.models'),
]);
if (is_null($path)) {
return $this->path;
}

$model = str($model)
->replace($prefix, '')
->ltrim('\\')
$path = str($path)
->replace($this->namespace->root, '')
->replace(['\\', '/'], DIRECTORY_SEPARATOR)
->append('.php')
->toString();

return implode('\\', [$prefix, $model]);
return app()->joinPaths($this->path, $path);
}

public function relativePath(string $path = ''): string
{
return collect([$this->domain, $path])->filter()->implode(DIRECTORY_SEPARATOR);
}

public function model(string $name): DomainObject
{
$name = str_replace($this->namespace->models.'\\', '', $name);

return new DomainObject(
name: $name,
namespace: $this->namespace->models,
fqn: $this->namespace->models.'\\'.$name,
path: $this->path($this->namespace->models.'\\'.$name),
);
}

public function factory(string $name): DomainObject
{
$name = str_replace($this->namespace->factories.'\\', '', $name);

return new DomainObject(
name: $name,
namespace: $this->namespace->factories,
fqn: $this->namespace->factories.'\\'.$name,
path: str("database/factories/{$this->domainWithSubdomain}/{$name}.php")
->replace(['\\', '/'], DIRECTORY_SEPARATOR)
->toString()
);
}

public function dataTransferObject(string $name): DomainObject
{
$name = str_replace($this->namespace->dataTransferObjects.'\\', '', $name);

return new DomainObject(
name: $name,
namespace: $this->namespace->dataTransferObjects,
fqn: $this->namespace->dataTransferObjects.'\\'.$name,
path: $this->path($this->namespace->dataTransferObjects.'\\'.$name),
);
}

public function dto(string $name): DomainObject
{
return $this->dataTransferObject($name);
}

public function viewModel(string $name): DomainObject
{
$name = str_replace($this->namespace->viewModels.'\\', '', $name);

return new DomainObject(
name: $name,
namespace: $this->namespace->viewModels,
fqn: $this->namespace->viewModels.'\\'.$name,
path: $this->path($this->namespace->viewModels.'\\'.$name),
);
}

public function valueObject(string $name): DomainObject
{
$name = str_replace($this->namespace->valueObjects.'\\', '', $name);

return new DomainObject(
name: $name,
namespace: $this->namespace->valueObjects,
fqn: $this->namespace->valueObjects.'\\'.$name,
path: $this->path($this->namespace->valueObjects.'\\'.$name),
);
}

public function action(string $name): DomainObject
{
$name = str_replace($this->namespace->actions.'\\', '', $name);

return new DomainObject(
name: $name,
namespace: $this->namespace->actions,
fqn: $this->namespace->actions.'\\'.$name,
path: $this->path($this->namespace->actions.'\\'.$name),
);
}
}
10 changes: 10 additions & 0 deletions src/Support/DomainResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Lunarstorm\LaravelDDD\Support;

class DomainResolver
{
public static function fromModelClass(string $modelClass)
{
}
}
38 changes: 38 additions & 0 deletions src/ValueObjects/DomainNamespaces.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Lunarstorm\LaravelDDD\ValueObjects;

class DomainNamespaces
{
public function __construct(
public readonly string $root,
public readonly string $models,
public readonly string $factories,
public readonly string $dataTransferObjects,
public readonly string $viewModels,
public readonly string $valueObjects,
public readonly string $actions,
) {
}

public static function from(string $domain, string $subdomain = null): self
{
$domainWithSubdomain = str($domain)
->when($subdomain, fn ($domain) => $domain->append("\\{$subdomain}"))
->toString();

$root = basename(config('ddd.paths.domains'));

$domainNamespace = implode('\\', [$root, $domainWithSubdomain]);

return new self(
root: $domainNamespace,
models: "{$domainNamespace}\\".config('ddd.namespaces.models'),
factories: "Database\\Factories\\{$domainWithSubdomain}",
dataTransferObjects: "{$domainNamespace}\\".config('ddd.namespaces.data_transfer_objects'),
viewModels: "{$domainNamespace}\\".config('ddd.namespaces.view_models'),
valueObjects: "{$domainNamespace}\\".config('ddd.namespaces.value_objects'),
actions: "{$domainNamespace}\\".config('ddd.namespaces.actions'),
);
}
}
14 changes: 14 additions & 0 deletions src/ValueObjects/DomainObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Lunarstorm\LaravelDDD\ValueObjects;

class DomainObject
{
public function __construct(
public readonly string $name,
public readonly string $namespace,
public readonly string $fqn,
public readonly string $path,
) {
}
}
Loading

0 comments on commit f9b6742

Please sign in to comment.