Skip to content

Commit

Permalink
Merge pull request #45 from lunarstorm/proxy-generators
Browse files Browse the repository at this point in the history
[1.x] Refactor and expand generator commands
  • Loading branch information
jaspertey authored Mar 24, 2024
2 parents 905d2ee + 306896f commit 43fa48d
Show file tree
Hide file tree
Showing 56 changed files with 959 additions and 446 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/phpstan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
php-version: '8.2'
coverage: none

- name: Install composer dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: [main]
pull_request:
branches: [main]
branches: [main, next]

jobs:
test:
Expand Down
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,35 @@

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

## [Unversioned]
### Added
- `ddd:list` to show a summary of current domains in the domain folder.
- Additional generator commands that extend Laravel's generators and funnel the generated objects into the domain layer:
- `ddd:cast {domain}:{name}`
- `ddd:channel {domain}:{name}`
- `ddd:command {domain}:{name}`
- `ddd:enum {domain}:{name}` (Laravel 11 only)
- `ddd:event {domain}:{name}`
- `ddd:exception {domain}:{name}`
- `ddd:job {domain}:{name}`
- `ddd:listener {domain}:{name}`
- `ddd:mail {domain}:{name}`
- `ddd:notification {domain}:{name}`
- `ddd:observer {domain}:{name}`
- `ddd:policy {domain}:{name}`
- `ddd:provider {domain}:{name}`
- `ddd:resource {domain}:{name}`
- `ddd:rule {domain}:{name}`
- `ddd:scope {domain}:{name}`

### Changed
- BREAKING: `ddd:*` commands no longer receive a dedicated domain argument (e.g., `ddd:action Invoicing CreateInvoice`). The domain is instead specified in two ways:
- As an option: `ddd:action CreateInvoice --domain=Invoicing` (this will take precedence).
- Shorthand prefix within the name: `ddd:action Invoicing:CreateInvoice`.

### Chore
- Dropped Laravel 9 support.

## [0.10.0] - 2024-03-23
### Added
- Add `ddd.domain_path` and `ddd.domain_namespace` to config, to specify the path to the domain layer and root domain namespace more explicitly (replaces the previous `ddd.paths.domains` config).
Expand Down
104 changes: 78 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/lunarstorm/laravel-ddd/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/lunarstorm/laravel-ddd/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain)
[![Total Downloads](https://img.shields.io/packagist/dt/lunarstorm/laravel-ddd.svg?style=flat-square)](https://packagist.org/packages/lunarstorm/laravel-ddd)

Laravel-DDD is a toolkit to support domain driven design (DDD) patterns in Laravel applications. One of the pain points when adopting DDD is the inability to use Laravel's native `make:model` artisan command to properly generate domain models, since domain models are not intended to be stored in the `App/Models/*` namespace. This package aims to fill the gaps by providing an equivalent command, `ddd:model`, plus a few more.

> :warning: **Disclaimer**: This package is subject to frequent design changes as it evolves towards a stable v1.0 release. It is currently being tested and fine tuned within Lunarstorm's client projects.
Laravel-DDD is a toolkit to support domain driven design (DDD) patterns in Laravel applications. One of the pain points when adopting DDD is the inability to use Laravel's native `make:model` artisan command to properly generate domain models, since domain models are not intended to be stored in the `App/Models/*` namespace. This package aims to fill the gaps by providing an equivalent command, `ddd:model`, plus many more.

## Installation

Expand All @@ -24,56 +22,94 @@ php artisan ddd:install

## Usage

The following generator commands are currently available:
Command syntax:
```bash
# Specifying the domain as an option
php artisan ddd:{object} {name} --domain={domain}

# Specifying the domain as part of the name (short-hand syntax)
php artisan ddd:{object} {domain}:{name}

# Not specifying the domain at all, which will then prompt
# you to enter the domain name (with auto-completion)
php artisan ddd:{object} {name}
```

The following generators are currently available, shown using short-hand syntax:
```bash
# Generate a domain model
php artisan ddd:model {domain} {name}
php artisan ddd:model {domain}:{name}

# Generate a domain model with factory
php artisan ddd:model {domain} {name} -f
php artisan ddd:model {domain} {name} --factory
php artisan ddd:model {domain}:{name} -f
php artisan ddd:model {domain}:{name} --factory

# Generate a domain factory
php artisan ddd:factory {domain} {name} [--model={model}]
php artisan ddd:factory {domain}:{name} [--model={model}]

# Generate a data transfer object
php artisan ddd:dto {domain} {name}
php artisan ddd:dto {domain}:{name}

# Generates a value object
php artisan ddd:value {domain} {name}
php artisan ddd:value {domain}:{name}

# Generates a view model
php artisan ddd:view-model {domain} {name}
php artisan ddd:view-model {domain}:{name}

# Generates an action
php artisan ddd:action {domain} {name}
php artisan ddd:action {domain}:{name}

# Extended Commands
# (extends Laravel's make:* generators and funnels the objects into the domain layer)
php artisan ddd:cast {domain}:{name}
php artisan ddd:channel {domain}:{name}
php artisan ddd:command {domain}:{name}
php artisan ddd:enum {domain}:{name} # Requires Laravel 11+
php artisan ddd:event {domain}:{name}
php artisan ddd:exception {domain}:{name}
php artisan ddd:job {domain}:{name}
php artisan ddd:listener {domain}:{name}
php artisan ddd:mail {domain}:{name}
php artisan ddd:notification {domain}:{name}
php artisan ddd:observer {domain}:{name}
php artisan ddd:policy {domain}:{name}
php artisan ddd:provider {domain}:{name}
php artisan ddd:resource {domain}:{name}
php artisan ddd:rule {domain}:{name}
php artisan ddd:scope {domain}:{name}
```

Examples:
```bash
php artisan ddd:model Invoicing LineItem # Domain/Invoicing/Models/LineItem
php artisan ddd:model Invoicing LineItem -f # Domain/Invoicing/Models/LineItem + Database/Factories/Invoicing/LineItemFactory
php artisan ddd:factory Invoicing LineItemFactory # Database/Factories/Invoicing/LineItemFactory
php artisan ddd:dto Invoicing LinePayload # Domain/Invoicing/Data/LinePayload
php artisan ddd:value Shared Percentage # Domain/Shared/ValueObjects/Percentage
php artisan ddd:view-model Invoicing ShowInvoiceViewModel # Domain/Invoicing/ViewModels/ShowInvoiceViewModel
php artisan ddd:action Invoicing SendInvoiceToCustomer # Domain/Invoicing/Actions/SendInvoiceToCustomer
php artisan ddd:model Invoicing:LineItem # Domain/Invoicing/Models/LineItem
php artisan ddd:model Invoicing:LineItem -f # Domain/Invoicing/Models/LineItem + Database/Factories/Invoicing/LineItemFactory
php artisan ddd:factory Invoicing:LineItemFactory # Database/Factories/Invoicing/LineItemFactory
php artisan ddd:dto Invoicing:LinePayload # Domain/Invoicing/Data/LinePayload
php artisan ddd:value Shared:Percentage # Domain/Shared/ValueObjects/Percentage
php artisan ddd:view-model Invoicing:ShowInvoiceViewModel # Domain/Invoicing/ViewModels/ShowInvoiceViewModel
php artisan ddd:action Invoicing:SendInvoiceToCustomer # Domain/Invoicing/Actions/SendInvoiceToCustomer
```

Subdomains (nested domains) can be specified with dot notation:
```bash
php artisan ddd:model Invoicing.Customer CustomerInvoice # Domain/Invoicing/Customer/Models/CustomerInvoice
php artisan ddd:factory Invoicing.Customer CustomerInvoice # Database/Factories/Invoicing/Customer/CustomerInvoiceFactory
php artisan ddd:model Invoicing.Customer:CustomerInvoice # Domain/Invoicing/Customer/Models/CustomerInvoice
php artisan ddd:factory Invoicing.Customer:CustomerInvoice # Database/Factories/Invoicing/Customer/CustomerInvoiceFactory
# (supported by all generator commands)
```

### Other Commands
```bash
# Show a summary of current domains in the domain folder
php artisan ddd:list
```

This package ships with opinionated (but sensible) configuration defaults. If you need to customize, you may do so by publishing the config file and generator stubs as needed:

```bash
php artisan vendor:publish --tag="ddd-config"
php artisan vendor:publish --tag="ddd-stubs"
```
Note that the extended commands do not publish ddd-specific stubs, and inherit the respective application-level stubs published by Laravel.

This is the content of the published config file (`ddd.php`):

Expand Down Expand Up @@ -117,11 +153,27 @@ return [
|
*/
'namespaces' => [
'models' => 'Models',
'data_transfer_objects' => 'Data',
'view_models' => 'ViewModels',
'value_objects' => 'ValueObjects',
'actions' => 'Actions',
'model' => 'Models',
'data_transfer_object' => 'Data',
'view_model' => 'ViewModels',
'value_object' => 'ValueObjects',
'action' => 'Actions',
'cast' => 'Casts',
'channel' => 'Channels',
'command' => 'Commands',
'enum' => 'Enums',
'event' => 'Events',
'exception' => 'Exceptions',
'job' => 'Jobs',
'listener' => 'Listeners',
'mail' => 'Mail',
'notification' => 'Notifications',
'observer' => 'Observers',
'policy' => 'Policies',
'provider' => 'Providers',
'resource' => 'Resources',
'rule' => 'Rules',
'scope' => 'Scopes',
],

/*
Expand Down
26 changes: 21 additions & 5 deletions config/ddd.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,27 @@
|
*/
'namespaces' => [
'models' => 'Models',
'data_transfer_objects' => 'Data',
'view_models' => 'ViewModels',
'value_objects' => 'ValueObjects',
'actions' => 'Actions',
'model' => 'Models',
'data_transfer_object' => 'Data',
'view_model' => 'ViewModels',
'value_object' => 'ValueObjects',
'action' => 'Actions',
'cast' => 'Casts',
'channel' => 'Channels',
'command' => 'Commands',
'enum' => 'Enums',
'event' => 'Events',
'exception' => 'Exceptions',
'job' => 'Jobs',
'listener' => 'Listeners',
'mail' => 'Mail',
'notification' => 'Notifications',
'observer' => 'Observers',
'policy' => 'Policies',
'provider' => 'Providers',
'resource' => 'Resources',
'rule' => 'Rules',
'scope' => 'Scopes',
],

/*
Expand Down
91 changes: 91 additions & 0 deletions src/Commands/Concerns/ResolvesDomainFromInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

namespace Lunarstorm\LaravelDDD\Commands\Concerns;

use Illuminate\Support\Str;
use Lunarstorm\LaravelDDD\Support\Domain;
use Lunarstorm\LaravelDDD\Support\DomainResolver;
use Lunarstorm\LaravelDDD\Support\Path;
use Symfony\Component\Console\Input\InputOption;

trait ResolvesDomainFromInput
{
protected ?Domain $domain = null;

protected function getOptions()
{
return [
...parent::getOptions(),
['domain', null, InputOption::VALUE_OPTIONAL, 'The domain name'],
];
}

protected function rootNamespace()
{
return Str::finish(DomainResolver::domainRootNamespace(), '\\');
}

protected function guessObjectType(): string
{
return match ($this->name) {
'ddd:base-view-model' => 'view_model',
'ddd:base-model' => 'model',
'ddd:value' => 'value_object',
'ddd:dto' => 'data_transfer_object',
default => str($this->name)->after(':')->snake()->toString(),
};
}

protected function getDefaultNamespace($rootNamespace)
{
if ($this->domain) {
return $this->domain->namespaceFor($this->guessObjectType());
}

return parent::getDefaultNamespace($rootNamespace);
}

protected function getPath($name)
{
if ($this->domain) {
return Path::normalize($this->laravel->basePath(
$this->domain->object($this->guessObjectType(), class_basename($name))->path
));
}

return parent::getPath($name);
}

public function handle()
{
$nameInput = $this->getNameInput();

// If the name contains a domain prefix, extract it
// and strip it from the name argument.
$domainExtractedFromName = null;

if (Str::contains($nameInput, ':')) {
$domainExtractedFromName = Str::before($nameInput, ':');
$this->input->setArgument('name', Str::after($nameInput, ':'));
}

$this->domain = match (true) {
// Domain was specified explicitly via option (priority)
filled($this->option('domain')) => new Domain($this->option('domain')),

// Domain was specified as a prefix in the name
filled($domainExtractedFromName) => new Domain($domainExtractedFromName),

default => null,
};

// If the domain is not set, prompt for it
if (! $this->domain) {
$this->domain = new Domain(
$this->anticipate('What is the domain?', DomainResolver::domainChoices())
);
}

parent::handle();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

namespace Lunarstorm\LaravelDDD\Commands;

use Symfony\Component\Console\Input\InputArgument;

class MakeAction extends DomainGeneratorCommand
class DomainActionMakeCommand extends DomainGeneratorCommand
{
protected $name = 'ddd:action';

Expand All @@ -17,29 +15,11 @@ class MakeAction extends DomainGeneratorCommand

protected $type = 'Action';

protected function getArguments()
{
return [
...parent::getArguments(),

new InputArgument(
'name',
InputArgument::REQUIRED,
'The name of the Action',
),
];
}

protected function getStub()
{
return $this->resolveStubPath('action.php.stub');
}

protected function getRelativeDomainNamespace(): string
{
return config('ddd.namespaces.actions', 'Actions');
}

protected function preparePlaceholders(): array
{
$baseClass = config('ddd.base_action');
Expand Down
Loading

0 comments on commit 43fa48d

Please sign in to comment.