Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.x] Refactor and expand generator commands #45

Merged
merged 19 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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