ahmed-bhs/hexagonal-maker-bundle
Install the bundle:
composer require ahmed-bhs/hexagonal-maker-bundle --dev
(Auto-registers with Symfony Flex)
Generate a complete module skeleton (e.g., User module):
bin/console make:hexagonal:crud user/account User
This creates:
User)First use case: Implement business logic in the generated RegisterUserCommandHandler (located in src/User/Account/Application/Command/Handler/).
src/User/Account/Domain/ (pure PHP, no framework dependencies)src/User/Account/Application/ (use cases, commands, queries)src/User/Account/Infrastructure/ (adapters, Doctrine implementations)tests/User/Account/ (pre-generated test classes)Modify the generated RegisterUserCommandHandler to add business rules:
// src/User/Account/Application/Command/Handler/RegisterUserCommandHandler.php
public function __invoke(RegisterUserCommand $command): UserId
{
$email = new Email($command->email);
$user = User::register(
$email,
$command->password,
new UserId(Uuid::v7())
);
$this->userRepository->save($user);
return $user->id();
}
bin/console make:hexagonal:entity user/account User --aggregate-root
bin/console make:hexagonal:value-object user/account Email
User::register()).bin/console make:hexagonal:command user/account register
bin/console make:hexagonal:query user/account find-by-id
#[AsMessageHandler] attribute.bin/console make:hexagonal:repository user/account User
// src/User/Account/Infrastructure/Persistence/DoctrineUserRepository.php
class DoctrineUserRepository implements UserRepository
{
public function save(User $user): void
{
$this->entityManager->persist($user);
$this->entityManager->flush();
}
}
bin/console make:hexagonal:message-handler user/account UserRegistered
$this->bus->dispatch(
new RegisterUserCommand($email, $password)
);
bin/console make:hexagonal:form user/account User
$form = $this->createForm(UserType::class, $user);
config/packages/doctrine.yaml:
doctrine:
orm:
mappings:
UserAccount:
type: yaml
dir: '%kernel.project_dir%/src/User/Account/Infrastructure/Persistence/Doctrine'
prefix: 'App\User\Account\Infrastructure\Persistence\Doctrine'
is_bundle: false
// tests/User/Account/Domain/UserTest.php
public function testUserRegistration(): void
{
$user = User::register(new Email('test@example.com'), 'password123', new UserId());
$this->assertEquals('test@example.com', $user->email()->value());
}
// tests/User/Account/Application/Command/Handler/RegisterUserCommandHandlerTest.php
public function testRegistration(): void
{
$command = new RegisterUserCommand('test@example.com', 'password123');
$handler = new RegisterUserCommandHandler($this->repository);
$userId = $handler($command);
$this->assertInstanceOf(UserId::class, $userId);
}
bin/console make:hexagonal:domain-event user/account UserRegistered
bin/console make:hexagonal:event-subscriber user/account UserRegisteredSubscriber
Class 'App\User\Account\Domain\User' is not a valid entity or mapped super class.Infrastructure/Persistence/Doctrine/.# config/packages/doctrine.yaml
doctrine:
orm:
mappings:
UserAccount:
dir: '%kernel.project_dir%/src/User/Account/Infrastructure/Persistence/Doctrine'
prefix: 'App\User\Account\Domain' # Correct namespace
#[AsMessageHandler] causes circular dependencies between Command and CommandHandler.CommandBus is properly configured in config/services.yaml:
services:
App\User\Account\Application\Command\Handler\RegisterUserCommandHandler:
arguments:
$bus: '@messenger.bus.default'
Email) fail when serialized in Symfony forms or controllers.__toString() and ensure they are immutable:
// src/User/Account/Domain/ValueObject/Email.php
public function __toString(): string
{
return $this->value;
}
UserRegisteredHandler) are not processed.MESSENGER_TRANSPORT_DSN is configured in .env:
MESSENGER_TRANSPORT_DSN=async://default
And the transport is enabled in config/packages/messenger.yaml:
messenger:
transports:
async: '%env(MESSENGER_TRANSPORT_DSN)%'
UserRepository is not injected into the handler.// src/User/Account/Infrastructure/Persistence/DoctrineUserRepository.php
#[AutoconfigureTag('messenger.message_handler')]
class DoctrineUserRepository implements UserRepository
{
public function __construct(private EntityManagerInterface $entityManager) {}
}
hexagonal_maker namespace in config/packages/hexagonal_maker.yaml:
hexagonal_maker:
namespace:
domain: 'App\User\Account\Domain'
application: 'App\User\Account\Application'
infrastructure: 'App\User\Account\Infrastructure'
config/packages/hexagonal_maker.yaml:
hexagonal_maker:
templates:
command: 'path/to/custom/command.stub'
query: 'path/to/custom/query.stub'
DomainEvent class to add metadata:
// src/User/Account/Domain/Event/DomainEvent.php
abstract class DomainEvent
{
public function __construct(
public readonly UserId $userId,
public readonly DateTimeImmutable $occurredOn
) {}
}
How can I help you explore Laravel packages today?