ahmed-bhs/hexagonal-maker-bundle
Quick answers to common questions about Hexagonal Maker Bundle.
composer require ahmed-bhs/hexagonal-maker-bundle --dev
The bundle auto-registers with Symfony Flex.
Yes! You must configure Doctrine YAML mappings for each module. See Installation Guide.
bin/console make:hexagonal:entity user/account User
bin/console make:hexagonal:repository user/account User
bin/console make:hexagonal:command user/account create
Yes! Use the CRUD maker:
bin/console make:hexagonal:crud blog/post Post --with-tests
This generates 30+ files including all layers, use cases, controllers, and tests.
19 specialized makers covering all hexagonal layers:
Benefits:
They're very similar:
Both achieve the same goal: domain independence.
Pure domain principle:
Annotations pollute domain entities with infrastructure concerns (Doctrine). YAML keeps domain 100% pure PHP.
// ✅ Pure domain - no dependencies
final class User
{
private string $id;
private string $email;
}
vs
// ❌ Domain depends on Doctrine
#[ORM\Entity]
final class User
{
#[ORM\Id]
private string $id;
}
Command Query Responsibility Segregation
Benefits:
Commands (CQRS):
UseCases:
Both are valid! Choose based on your needs.
Commands are dispatched via Symfony Messenger:
// 1. Define command (DTO)
final readonly class CreateUserCommand
{
public function __construct(
public string $email,
public string $password,
) {}
}
// 2. Handle with #[AsMessageHandler]
#[AsMessageHandler]
final readonly class CreateUserCommandHandler
{
public function __invoke(CreateUserCommand $command): void
{
// Business logic here
}
}
// 3. Dispatch from controller
$this->messageBus->dispatch(new CreateUserCommand($email, $password));
In config/packages/doctrine.yaml:
doctrine:
orm:
mappings:
UserAccount:
type: yml
dir: '%kernel.project_dir%/src/User/Account/Infrastructure/Persistence/Doctrine/Orm/Mapping'
prefix: 'App\User\Account\Domain\Model'
Add one mapping per module.
Technically yes, but not recommended. It breaks the pure domain principle.
Use embedded in YAML mapping:
# Email.orm.yml
App\Domain\ValueObject\Email:
type: embeddable
fields:
value:
type: string
length: 180
# User.orm.yml
App\Domain\Model\User:
embedded:
email:
class: App\Domain\ValueObject\Email
columnPrefix: email_
Yes! Configure them in YAML to keep domain pure:
fields:
createdAt:
type: datetime_immutable
gedmo:
timestampable:
on: create
Generate tests with --with-tests:
bin/console make:hexagonal:command blog/post create --with-tests
Or use the test maker:
bin/console make:hexagonal:use-case-test blog/post CreatePost
Unit Tests:
Integration Tests:
Both are generated!
# All tests
vendor/bin/phpunit
# Specific test
vendor/bin/phpunit tests/Blog/Post/Application/CreatePost/CreatePostTest.php
# With coverage
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html coverage
Yes! Create custom templates in config/skeleton/:
# config/packages/hexagonal_maker.yaml
hexagonal_maker:
skeleton_dir: '%kernel.project_dir%/config/skeleton'
Yes, in configuration:
hexagonal_maker:
root_namespace: 'MyApp'
Yes! Use the module path parameter:
# Default: src/Blog/Post/
bin/console make:hexagonal:entity blog/post Post
# Custom: src/Module/Blog/Post/
bin/console make:hexagonal:entity module/blog/post Post
# Flat: src/BlogPost/
bin/console make:hexagonal:entity blog-post Post
# Generate event + subscriber
bin/console make:hexagonal:domain-event order/payment OrderPlaced --with-subscriber
Dispatch from entity:
class Order
{
private array $events = [];
public function place(): void
{
$this->status = 'placed';
$this->events[] = new OrderPlacedEvent($this->id);
}
public function pullDomainEvents(): array
{
$events = $this->events;
$this->events = [];
return $events;
}
}
Yes! Generate message handlers:
bin/console make:hexagonal:message-handler user/account SendWelcomeEmail --with-message
Configure in messenger.yaml:
framework:
messenger:
routing:
'App\User\Account\Application\Message\*': async
Use Shared Kernel for tenant-aware repositories:
// Shared/Domain/ValueObject/TenantId.php
final readonly class TenantId
{
public function __construct(public string $value) {}
}
// Each repository filters by tenant
interface UserRepositoryInterface
{
public function findByTenant(TenantId $tenantId): array;
}
| Feature | Symfony Maker | Hexagonal Maker |
|---|---|---|
| Architecture | Traditional layers | Hexagonal/DDD |
| Domain Purity | ❌ Coupled to Doctrine | ✅ Pure PHP |
| CQRS | ❌ No | ✅ Yes |
| Modular | ❌ Single namespace | ✅ Modules/Contexts |
| Testing | Basic | ✅ Unit + Integration |
Use both! Hexagonal Maker extends Symfony Maker.
| Aspect | Manual | Hexagonal Maker |
|---|---|---|
| Speed | Hours per module | Minutes |
| Consistency | Varies by developer | Enforced patterns |
| Best Practices | Requires expertise | Built-in |
| Learning Curve | Steep | Guided |
Hexagonal Maker = Manual quality at automated speed
Solution: Check configuration in hexagonal_maker.yaml:
hexagonal_maker:
root_namespace: 'App' # Must match your composer.json
Error: Class "X" is not a valid entity or mapped super class
Solution: Add YAML mapping in doctrine.yaml for your module.
Error: Cannot autowire... no such service exists
Solution: Add interface binding in services.yaml:
services:
App\Blog\Post\Domain\Port\PostRepositoryInterface:
class: App\Blog\Post\Infrastructure\Persistence\Doctrine\DoctrinePostRepository
Solution: Regenerate autoload:
composer dump-autoload
See Contributing Guide. Ways to help:
Yes! MIT License - free for commercial use.
Yes! Generated code is yours. No restrictions.
Still have questions? Open a discussion
How can I help you explore Laravel packages today?