samuel-nunes / laravel-ddd-toolkit
A pragmatic tactical DDD toolkit for modular Laravel applications.
Package info
github.com/SamuelNunesDev/laravel-ddd-toolkit
pkg:composer/samuel-nunes/laravel-ddd-toolkit
Requires
- php: ^8.2
- illuminate/console: ^11.0|^12.0|^13.0
- illuminate/filesystem: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- nikic/php-parser: ^5.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpunit/phpunit: ^10.5|^11.0
- vimeo/psalm: ^6.0
This package is not auto-updated.
Last update: 2026-07-23 16:05:33 UTC
README
Laravel DDD Toolkit is a Composer package for building large, modular Laravel applications with vertical modules, hexagonal architecture by default, and pragmatic tactical DDD patterns.
Website: samuelnunesdev.github.io/laravel-ddd-toolkit-site
It organizes the application vertically by business capability, such as Order, Payment, Customer, or Billing, while keeping the Laravel experience familiar: Artisan commands, service providers, routes, Eloquent, jobs, listeners, requests, and controllers still work as expected.
This package is intentionally pragmatic. It is not an academic DDD framework, it does not replace Laravel, and it does not force CQRS, Event Sourcing, repositories, or a rigid architecture on every project.
Summary
- Why This Exists
- Compatibility
- Architecture
- Installation
- Quick Start
- Core Commands
- Generated Structure
- Generated Code Example
- Presets
- Discovery Cache
- Architecture Checks
- Configuration
- Repositories And Eloquent
- Custom Stubs
- Contributing
- Development
- License
Why This Exists
Laravel is productive and expressive, but large applications often become hard to navigate when code is grouped mainly by technical layer:
- controllers grow too large;
- services become generic dumping grounds;
- business rules spread across models, requests, jobs, and listeners;
- related files live far apart from each other;
- module boundaries become hard to see;
- teams struggle to change one business area without touching another.
Laravel DDD Toolkit addresses this by encouraging a modular monolith organized by business capability:
app/
Modules/
Order/
Payment/
Customer/
Shared/
Each module is a feature, business capability, subdomain, or bounded context depending on the size of the application.
Compatibility
- PHP:
^8.2 - Laravel components:
^11.0,^12.0, or^13.0
Architecture
The toolkit combines two complementary ideas:
Vertical architecture organizes the project by module or business capability.
Hexagonal architecture organizes dependencies inside each module through Ports and Adapters.
The default positioning is:
Vertical modules
+ Hexagonal architecture inside each module
+ Pragmatic tactical DDD
Dependency direction is separate from execution flow.
Allowed dependency direction:
Infrastructure -> Application -> Domain
Infrastructure -> Application Ports
Domain does not depend on Infrastructure
Application does not depend on Controllers, Requests, or persistence Models
Typical execution flow:
HTTP
-> Controller
-> Application Use Case
-> Domain
-> Port
-> Adapter
Ports live in Application/Ports/In and Application/Ports/Out. Adapters live in Infrastructure.
An external integration can act as an anti-corruption layer when it protects the domain from external APIs, payloads, SDK models, or vendor-specific concepts. ACL is treated as a role an integration can play, not as the primary command name.
Installation
composer require samuel-nunes/laravel-ddd-toolkit php artisan ddd:install
The install command creates:
app/Modules
app/Shared
app/Providers/ModulesServiceProvider.php
config/ddd.php
AGENTS.md
On Laravel 11 and newer, it also registers App\Providers\ModulesServiceProvider in:
bootstrap/providers.php
If legacy folders such as app/Models, app/Services, or app/Repositories exist, the command may ask if you want to review them. It never removes those folders automatically.
The installer never overwrites an existing AGENTS.md unless --force-agents is explicitly used.
Installer options:
--no-agents
--merge-agents
--force-agents
AI-friendly projects
Laravel DDD Toolkit can publish an AGENTS.md file to help AI coding agents understand the architecture of your Laravel project.
php artisan ddd:install
If your project already has an AGENTS.md, it will not be overwritten.
To merge Laravel DDD Toolkit instructions into an existing file:
php artisan ddd:install --merge-agents
To skip publishing:
php artisan ddd:install --no-agents
To overwrite:
php artisan ddd:install --force-agents
The file guides agents to preserve vertical modules, hexagonal architecture by default, ports in Application, adapters in Infrastructure, a Domain layer without Laravel dependencies, make:module instead of make:domain, and ddd:check before finishing architectural changes.
Quick Start
Create an Order module, define an outbound persistence port, bind it to an Eloquent adapter, and create a use case:
php artisan make:module Order php artisan make:port Order OrderRepository --type=out php artisan make:adapter Order EloquentOrderRepository --port=OrderRepository --type=persistence php artisan make:usecase Order CancelOrder
Then run the architecture checker:
php artisan ddd:check --module=Order
Module AI docs
When creating a module, Laravel DDD Toolkit can generate module-level documentation for humans and AI agents.
php artisan make:module Billing
You can provide context directly:
php artisan make:module Billing --context="Handles invoice issuing, cancellation and invoice queries."
Or from a file:
php artisan make:module Billing --context-file=docs/billing-context.md
To skip AI docs:
php artisan make:module Billing --no-ai-docs
Generated files:
app/Modules/Billing/README.md
app/Modules/Billing/AGENTS.md
These files do not use AI. They are generated from templates using the context provided by the developer.
Core Commands
Create a module:
php artisan make:module Order
Create tactical domain and application classes:
php artisan make:entity Order Order php artisan make:value-object Customer Email php artisan make:event Order OrderCancelled php artisan make:usecase Order CancelOrder
Create Ports and Adapters:
php artisan make:port Order OrderRepository --type=out php artisan make:port Order CancelOrderUseCase --type=in php artisan make:adapter Order EloquentOrderRepository --port=OrderRepository --type=persistence
Create an external integration:
php artisan make:integration Payment Stripe
Run architecture checks and discovery cache commands:
php artisan ddd:check php artisan ddd:cache php artisan ddd:clear
Existing files are not overwritten unless you pass --force.
Generated Structure
By default, make:module creates a vertical module with hexagonal structure:
app/Modules/Order/
├── Domain/
│ ├── Entities/
│ ├── ValueObjects/
│ ├── Events/
│ └── Exceptions/
├── Application/
│ ├── UseCases/
│ ├── DTO/
│ └── Ports/
│ ├── In/
│ └── Out/
└── Infrastructure/
├── Http/
│ ├── Controllers/
│ ├── Requests/
│ └── routes.php
├── Persistence/
│ ├── Models/
│ └── Adapters/
├── Integrations/
└── Providers/
Domain/Contracts is not created by the default preset. Use explicit ports in Application/Ports/In and Application/Ports/Out instead.
Generated Code Example
An outbound Port is generated as a PHP interface in the application layer:
<?php namespace App\Modules\Order\Application\Ports\Out; interface OrderRepository { }
A persistence Adapter can implement that Port from the infrastructure layer:
<?php namespace App\Modules\Order\Infrastructure\Persistence\Adapters; use App\Modules\Order\Application\Ports\Out\OrderRepository; final class EloquentOrderRepository implements OrderRepository { }
When possible, the adapter command also registers the binding in the module provider:
$this->app->bind( \App\Modules\Order\Application\Ports\Out\OrderRepository::class, \App\Modules\Order\Infrastructure\Persistence\Adapters\EloquentOrderRepository::class, );
Presets
Hexagonal is the default:
php artisan make:module Order
This is equivalent to:
php artisan make:module Order --preset=hexagonal
Alternative presets are available when a project needs less or more structure:
minimal: creates onlyDomain,Application, andInfrastructure.tactical: creates tactical DDD directories without explicit Ports and Adapters.full: includes aggregates, ports, adapters, repositories, jobs, listeners, policies, integrations, and providers.
Discovery Cache
The generated ModulesServiceProvider discovers module routes and providers automatically.
It loads route files from:
app/Modules/{Module}/Infrastructure/Http/routes.php
It registers module service providers from:
app/Modules/{Module}/Infrastructure/Providers/*ServiceProvider.php
For production, cache discovery into a manifest:
php artisan ddd:cache
The manifest is written to:
bootstrap/cache/ddd-modules.php
Clear it with:
php artisan ddd:clear
When the cache exists, discovery uses the manifest. When it does not exist, filesystem discovery is used.
Architecture Checks
Run:
php artisan ddd:check
By default, this validates basic hexagonal rules:
Domainmust not import Laravel, HTTP foundation, Guzzle, or module Infrastructure classes.Applicationmust not import controllers, HTTP requests, or persistence models.Infrastructuremay depend onApplication,Application/Ports, andDomain.
Validate a single module:
php artisan ddd:check --module=Order
The command does not alter files and returns a non-zero exit code when violations are found, so it can be used in CI.
Configuration
The published config lives at:
config/ddd.php
Default options:
return [ 'default_preset' => 'hexagonal', 'modules_path' => 'app/Modules', 'shared_path' => 'app/Shared', 'create_repositories' => false, 'create_policies' => false, 'create_jobs' => true, 'create_events' => true, ];
Repositories And Eloquent
Eloquent is supported. Active Record is part of Laravel's productivity story and can coexist with domain-oriented code.
Repositories are disabled by default:
'create_repositories' => false,
Create repositories or outbound ports only when they solve a real problem, such as hiding complex persistence, integrating external storage, or protecting application/domain code from query details.
Force a repository generation:
php artisan make:repository Order OrderRepository --force
For hexagonal persistence boundaries, prefer an outbound port plus an infrastructure adapter:
php artisan make:port Order OrderRepository --type=out php artisan make:adapter Order EloquentOrderRepository --port=OrderRepository --type=persistence
Custom Stubs
Publish the package stubs when you want to customize generated code:
php artisan vendor:publish --tag=ddd-stubs
Published stubs are placed in:
stubs/vendor/laravel-ddd-toolkit
When a custom stub exists there, the generator uses it instead of the package default.
Contributing
Contributions are welcome. Before opening a pull request, run:
composer check
Development
Install dependencies:
composer install
Run tests:
composer test
The test suite uses Orchestra Testbench to validate package behavior against Laravel.
Run static analysis:
composer analyse
Run the full local check:
composer check
When PHP or Composer are not available on the host machine, the same checks can be run with the Psalm Docker image:
docker run --rm --user 1000:1000 -v "$PWD:/app" -w /app --entrypoint /composer/vendor/bin/psalm ghcr.io/danog/psalm:latest --no-cache --show-info=false docker run --rm --user 1000:1000 -v "$PWD:/app" -w /app --entrypoint /composer/vendor/bin/psalm ghcr.io/danog/psalm:latest --no-cache --show-info=false --taint-analysis
License
This project is open-sourced software licensed under the MIT license.