Skip to content

Commit

Permalink
54 additional laravel 11 generators (#55)
Browse files Browse the repository at this point in the history
* New commands roughed in.

* Cleanup readme.

* Refine wording.

* Document the additional commands.

* Refine internals and fix tests.

* Nested object test coverage.

* Update readme.

* Update changelog snippet.

* Update examples.
  • Loading branch information
jaspertey authored Apr 7, 2024
1 parent 13112ac commit 9872408
Show file tree
Hide file tree
Showing 20 changed files with 450 additions and 94 deletions.
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

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

## [Unversioned]
### Added
- Add `ddd:class` generator extending Laravel's `make:class` (Laravel 11 only).
- Add `ddd:interface` generator extending Laravel's `make:interface` (Laravel 11 only).
- Add `ddd:trait` generator extending Laravel's `make:trait` (Laravel 11 only).
- Allow overriding configured namespaces at runtime by specifying an absolute name starting with /:
```bash
# The usual: generate a provider in the configured provider namespace
php artisan ddd:provider Invoicing:InvoiceServiceProvider
# -> Domain\Invoicing\Providers\InvoiceServiceProvider

# Override the configured namespace at runtime
php artisan ddd:provider Invoicing:/InvoiceServiceProvider
# -> Domain\Invoicing\InvoiceServiceProvider

# Generate an event inside the Models namespace (hypothetical)
php artisan ddd:event Invoicing:/Models/EventDoesNotBelongHere
# -> Domain\Invoicing\Models\EventDoesNotBelongHere

# Deep nesting is supported
php artisan ddd:exception Invoicing:/Models/Exceptions/InvoiceNotFoundException
# -> Domain\Invoicing\Models\Exceptions\InvoiceNotFoundException
```

### Fixed
- Internals: Handle a variety of additional edge cases when generating base models and base view models.

## [1.0.0] - 2024-03-31
### Added
- `ddd:list` to show a summary of current domains in the domain folder.
Expand Down
109 changes: 85 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,28 @@ You can install the package via composer:
composer require lunarstorm/laravel-ddd
```

You may then initialize the package using the `ddd:install` artisan command. This command will publish the config file, register the domain path in your project's composer.json psr-4 autoload configuration on your behalf, and allow you to publish generator stubs for customization if needed.
You may initialize the package using the `ddd:install` artisan command. This will publish the config file, register the domain path in your project's composer.json psr-4 autoload configuration on your behalf, and allow you to publish generator stubs for customization if needed.
```bash
php artisan ddd:install
```

### Version Compatibility
Laravel | LaravelDDD
:---------------|:-----------
9.x - 10.24.x | 0.x
10.25.x | 1.x
11.x | 1.x
### Deployment
In production, run `ddd:cache` during the deployment process to [optimize autoloading](#autoloading-in-production).
```bash
php artisan ddd:cache
```

> For 0.x usage, please refer to the **[0.x README](https://github.com/lunarstorm/laravel-ddd/blob/v0.10.0/README.md)**.
>
### Version Compatibility
Laravel | LaravelDDD | |
:---------------|:-----------|:-------------------------------------------------------------------------------------|
9.x - 10.24.x | 0.x | **[0.x README](https://github.com/lunarstorm/laravel-ddd/blob/v0.10.0/README.md)** |
10.25.x | 1.x |
11.x | 1.x |

### Upgrading from 0.x
Things to be aware of when upgrading from 0.x:
- If the config file was published, it should be removed, re-published, and re-configured according to the latest format. A helper command `ddd:upgrade` is available to assist with this.
- If stubs were published, they should also be re-published and inspected to ensure everything is up-to-date.
- In production, `ddd:cache` should be run during the deployment process to optimize autoloading. See the [Autoloading in Production](#autoloading-in-production) section for more details.
- Update config to the [latest format](#config-file). A helper command `ddd:upgrade` is available to assist with this.
- Remove and re-publish stubs if applicable.
- In production, `ddd:cache` should be run during the deployment process. See the [Autoloading in Production](#autoloading-in-production) section for more details.

## Usage
### Syntax
Expand Down Expand Up @@ -82,7 +84,6 @@ php artisan ddd:action Invoicing:SendInvoiceToCustomer
php artisan ddd:cast Invoicing:MoneyCast
php artisan ddd:channel Invoicing:InvoiceChannel
php artisan ddd:command Invoicing:InvoiceDeliver
php artisan ddd:enum Customer:CustomerType # Laravel 11+ only
php artisan ddd:event Invoicing:PaymentWasReceived
php artisan ddd:exception Invoicing:InvoiceNotFoundException
php artisan ddd:job Invoicing:GenerateInvoicePdf
Expand All @@ -95,6 +96,12 @@ php artisan ddd:provider Invoicing:InvoiceServiceProvider
php artisan ddd:resource Invoicing:InvoiceResource
php artisan ddd:rule Invoicing:ValidPaymentMethod
php artisan ddd:scope Invoicing:ArchivedInvoicesScope

# Laravel 11+ only
php artisan ddd:class Invoicing:Support/InvoiceBuilder
php artisan ddd:enum Customer:CustomerType
php artisan ddd:interface Customer:Contracts/Invoiceable
php artisan ddd:trait Customer:Concerns/HasInvoices
```
Generated objects will be placed in the appropriate domain namespace as specified by `ddd.namespaces.*` in the configuration file.

Expand All @@ -110,6 +117,54 @@ php artisan ddd:cache
php artisan ddd:clear
```

## Advanced Usage
### Nested Objects
When specifying object names for any `ddd:*` generator command, nested objects can be specified with forward slashes.
```bash
php artisan ddd:model Invoicing:Payment/Transaction
# -> Domain\Invoicing\Models\Payment\Transaction

php artisan ddd:action Invoicing:Payment/ProcessTransaction
# -> Domain\Invoicing\Actions\Payment\ProcessTransaction

php artisan ddd:exception Invoicing:Payment/PaymentFailedException
# -> Domain\Invoicing\Exceptions\Payment\PaymentFailedException
```
This is essential for objects without a fixed namespace such as `class`, `interface`, `trait`,
each of which have a blank namespace by default. In other words, these objects originate
from the root of the domain.
```bash
php artisan ddd:class Invoicing:Support/InvoiceBuilder
# -> Domain\Invoicing\Support\InvoiceBuilder

php artisan ddd:interface Invoicing:Contracts/PayableByCreditCard
# -> Domain\Invoicing\Contracts\PayableByCreditCard

php artisan ddd:interface Invoicing:Models/Concerns/HasLineItems
# -> Domain\Invoicing\Models\Concerns\HasLineItems
```

### Overriding Configured Namespaces at Runtime
If for some reason you need to generate a domain object under a namespace different to what is configured in `ddd.namespaces.*`,
you may do so using an absolute name starting with `/`. This will generate the object from the root of the domain.
```bash
# The usual: generate a provider in the configured provider namespace
php artisan ddd:provider Invoicing:InvoiceServiceProvider
# -> Domain\Invoicing\Providers\InvoiceServiceProvider

# Override the configured namespace at runtime
php artisan ddd:provider Invoicing:/InvoiceServiceProvider
# -> Domain\Invoicing\InvoiceServiceProvider

# Generate an event inside the Models namespace (hypothetical)
php artisan ddd:event Invoicing:/Models/EventDoesNotBelongHere
# -> Domain\Invoicing\Models\EventDoesNotBelongHere

# Deep nesting is supported
php artisan ddd:exception Invoicing:/Models/Exceptions/InvoiceNotFoundException
# -> Domain\Invoicing\Models\Exceptions\InvoiceNotFoundException
```

### Subdomains (nested domains)
Subdomains can be specified with dot notation wherever a domain option is accepted.
```bash
Expand All @@ -122,7 +177,7 @@ php artisan ddd:view-model Reporting.Customer:MonthlyInvoicesReportViewModel
# (supported by all commands where a domain option is accepted)
```

### Customization
## Customization
This package ships with opinionated (but sensible) configuration defaults. You may customize by publishing the config file and generator stubs as needed:

```bash
Expand Down Expand Up @@ -175,7 +230,6 @@ In production, you should cache the autoload manifests using the `ddd:cache` com
This is the content of the published config file (`ddd.php`):

```php

return [

/*
Expand Down Expand Up @@ -221,12 +275,14 @@ return [
'value_object' => 'ValueObjects',
'action' => 'Actions',
'cast' => 'Casts',
'class' => '',
'channel' => 'Channels',
'command' => 'Commands',
'enum' => 'Enums',
'event' => 'Events',
'exception' => 'Exceptions',
'factory' => 'Database\Factories',
'interface' => '',
'job' => 'Jobs',
'listener' => 'Listeners',
'mail' => 'Mail',
Expand All @@ -237,6 +293,7 @@ return [
'resource' => 'Resources',
'rule' => 'Rules',
'scope' => 'Scopes',
'trait' => '',
],

/*
Expand Down Expand Up @@ -299,26 +356,30 @@ return [
*/
'autoload' => [
/**
* When enabled, any class within the domain layer extending `Illuminate\Support\ServiceProvider`
* will be auto-registered as a service provider
* When enabled, any class in the domain layer extending
* `Illuminate\Support\ServiceProvider` will be
* auto-registered as a service provider
*/
'providers' => true,

/**
* When enabled, any class within the domain layer extending `Illuminate\Console\Command`
* will be auto-registered as a command when running in console.
* When enabled, any class in the domain layer extending
* `Illuminate\Console\Command` will be auto-registered
* as a command when running in console.
*/
'commands' => true,

/**
* When enabled, the package will register a custom policy discovery callback to resolve policy names
* for domain models, and fallback to Laravel's default for all other cases.
* When enabled, a custom policy discovery callback will be
* registered to resolve policy names for domain models,
* or fallback to Laravel's default otherwise.
*/
'policies' => true,

/**
* When enabled, the package will register a custom factory discovery callback to resolve factory names
* for domain models, and fallback to Laravel's default for all other cases.
* When enabled, a custom policy discovery callback will be
* registered to resolve factory names for domain models,
* or fallback to Laravel's default otherwise.
*/
'factories' => true,
],
Expand Down
3 changes: 3 additions & 0 deletions config/ddd.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@
'value_object' => 'ValueObjects',
'action' => 'Actions',
'cast' => 'Casts',
'class' => '',
'channel' => 'Channels',
'command' => 'Commands',
'enum' => 'Enums',
'event' => 'Events',
'exception' => 'Exceptions',
'factory' => 'Database\Factories',
'interface' => '',
'job' => 'Jobs',
'listener' => 'Listeners',
'mail' => 'Mail',
Expand All @@ -61,6 +63,7 @@
'resource' => 'Resources',
'rule' => 'Rules',
'scope' => 'Scopes',
'trait' => '',
],

/*
Expand Down
26 changes: 23 additions & 3 deletions src/Commands/Concerns/ResolvesDomainFromInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ trait ResolvesDomainFromInput
{
use CanPromptForDomain;

protected $nameIsAbsolute = false;

protected ?Domain $domain = null;

protected function getOptions()
Expand Down Expand Up @@ -41,7 +43,9 @@ protected function guessObjectType(): string
protected function getDefaultNamespace($rootNamespace)
{
if ($this->domain) {
return $this->domain->namespaceFor($this->guessObjectType());
return $this->nameIsAbsolute
? $this->domain->namespace->root
: $this->domain->namespaceFor($this->guessObjectType());
}

return parent::getDefaultNamespace($rootNamespace);
Expand All @@ -51,7 +55,11 @@ protected function getPath($name)
{
if ($this->domain) {
return Path::normalize($this->laravel->basePath(
$this->domain->object($this->guessObjectType(), class_basename($name))->path
$this->domain->object(
type: $this->guessObjectType(),
name: $name,
absolute: $this->nameIsAbsolute
)->path
));
}

Expand All @@ -68,7 +76,7 @@ public function handle()

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

$this->domain = match (true) {
Expand All @@ -86,6 +94,18 @@ public function handle()
$this->domain = new Domain($this->promptForDomainName());
}

// Now that the domain part is handled,
// we will deal with the name portion.

// Normalize slash and dot separators
$nameInput = Str::replace(['.', '\\', '/'], '/', $nameInput);

if ($this->nameIsAbsolute = Str::startsWith($nameInput, ['/'])) {
// $nameInput = Str::after($nameInput, '/');
}

$this->input->setArgument('name', $nameInput);

parent::handle();
}
}
13 changes: 13 additions & 0 deletions src/Commands/DomainClassMakeCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Lunarstorm\LaravelDDD\Commands;

use Illuminate\Foundation\Console\ClassMakeCommand;
use Lunarstorm\LaravelDDD\Commands\Concerns\ResolvesDomainFromInput;

class DomainClassMakeCommand extends ClassMakeCommand
{
use ResolvesDomainFromInput;

protected $name = 'ddd:class';
}
13 changes: 13 additions & 0 deletions src/Commands/DomainInterfaceMakeCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Lunarstorm\LaravelDDD\Commands;

use Illuminate\Foundation\Console\InterfaceMakeCommand;
use Lunarstorm\LaravelDDD\Commands\Concerns\ResolvesDomainFromInput;

class DomainInterfaceMakeCommand extends InterfaceMakeCommand
{
use ResolvesDomainFromInput;

protected $name = 'ddd:interface';
}
Loading

0 comments on commit 9872408

Please sign in to comment.