Skip to content

Commit

Permalink
Experimental: custom layers
Browse files Browse the repository at this point in the history
  • Loading branch information
jaspertey committed Oct 16, 2024
1 parent 7daf96e commit 42054d4
Show file tree
Hide file tree
Showing 16 changed files with 249 additions and 41 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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' => [
Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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' => [
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -326,7 +341,7 @@ return [
| Configure domain objects in the application layer.
|
*/
'application_layer' => [
'application' => [
'path' => 'app/Modules',
'namespace' => 'App\Modules',
'objects' => [
Expand Down
27 changes: 23 additions & 4 deletions config/ddd.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
| Configure domain objects in the application layer.
|
*/
'application_layer' => [
'application' => [
'path' => 'app/Modules',
'namespace' => 'App\Modules',
'objects' => [
Expand All @@ -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\*
Expand Down
4 changes: 2 additions & 2 deletions src/Commands/DomainListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
49 changes: 31 additions & 18 deletions src/Support/Domain.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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'));
}
Expand All @@ -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
Expand Down Expand Up @@ -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, '')
Expand All @@ -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
);
}
Expand Down
33 changes: 28 additions & 5 deletions src/Support/DomainResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ public static function domainRootNamespace(): ?string
*/
public static function applicationLayerPath(): ?string
{
return config('ddd.application_layer.path');
return config('ddd.application.path');
}

/**
* Get the current configured root application layer namespace.
*/
public static function applicationLayerRootNamespace(): ?string
{
return config('ddd.application_layer.namespace');
return config('ddd.application.namespace');
}

/**
Expand All @@ -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);
};
Expand All @@ -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.
*
Expand All @@ -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('\\');

Expand Down
67 changes: 67 additions & 0 deletions src/Support/Layer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace Lunarstorm\LaravelDDD\Support;

class Layer
{
public readonly ?string $namespace;

public readonly ?string $path;

public function __construct(
?string $namespace,
?string $path,
) {
$this->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()
);
}
}
8 changes: 8 additions & 0 deletions src/Support/Path.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
3 changes: 2 additions & 1 deletion tests/.skeleton/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"tests/TestCase.php"
],
"psr-4": {
"App\\": "app/"
"App\\": "app/",
"Support\\": "src/Support"
}
},
"extra": {
Expand Down
2 changes: 1 addition & 1 deletion tests/Generator/ControllerMakeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
3 changes: 2 additions & 1 deletion tests/Generator/Model/MakeWithOptionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
}

$command = [
'ddd:model', [
'ddd:model',
[
'name' => $modelName,
'--domain' => $domain->dotName,
...$options,
Expand Down
2 changes: 1 addition & 1 deletion tests/Generator/RequestMakeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
Loading

0 comments on commit 42054d4

Please sign in to comment.