Customization
How to extend the boilerplate with new features
Customization
This guide shows how to extend the boilerplate with new features.
Adding a New Command (Write Operation)
Example: adding an "Update Profile" feature.
1. Create the Feature Folder
src/UserManagement/Application/UpdateProfile/
2. Create the Command DTO
final readonly class UpdateProfileCommand
{
public function __construct(
public string $userId,
public string $name,
) {}
}
3. Create the Handler
#[AsMessageHandler(bus: 'command.bus')]
final readonly class UpdateProfileHandler
{
public function __construct(
private UserRepositoryInterface $userRepository,
) {}
public function __invoke(UpdateProfileCommand $command): void
{
$user = $this->userRepository->get(UserId::fromString($command->userId));
$user->updateName($command->name);
$this->userRepository->save($user);
}
}
4. Create the Controller
Thin controller that parses the request, dispatches the command, and returns a JSON response.
5. Add the Route
api_update_profile:
path: /api/v1/user/profile
methods: [PUT]
controller: App\UserManagement\Infrastructure\Http\UpdateProfileController
6. Write Tests
Create both a unit test (with fakes) and a functional test (with HTTP requests).
Adding a New Query (Read Operation)
- Create the feature folder with Query DTO and ViewModel
- Create a read-model repository interface in Application
- Implement with DBAL (raw SQL, no ORM) in Infrastructure
- Create a handler on the
query.bus - Wire up the controller and route
Adding a New Bounded Context
src/NewContext/
├── Domain/
│ ├── Model/
│ ├── Port/
│ ├── Event/
│ └── Exception/
├── Application/
└── Infrastructure/
├── Http/
├── Persistence/
│ ├── Mapping/
│ └── ReadModel/
└── Console/
Then add ORM mapping in config/packages/doctrine.yaml, register controllers in config/services.yaml, and add routes.
Adding a New Domain Event
- Create the event class in
Domain/Event/ - Dispatch from a handler via
$this->eventBus->dispatch(...) - Create a listener handler on the
event.bus
Adding a New Email Template
- Create the template in Brevo, note the ID
- Add the template ID to
config/packages/brevo.yaml - Add the method to
EmailSenderInterface - Implement in both the Brevo and Symfony Mailer adapters
Adding a New Stripe Plan
- Edit
config/packages/stripe.yamlwith the new plan - Add the plan type enum case to
PlanType.php - Define its features in the
features()method - Sync to Stripe:
docker compose exec php bin/console app:stripe:sync-plans
Switching to Async Events
By default, events are dispatched synchronously. To process them asynchronously via Redis:
- Update the transport DSN in
config/packages/messenger.yaml - Set
MESSENGER_TRANSPORT_DSN=redis://redis:6379/messagesin.env.local - Start a worker:
docker compose exec php bin/console messenger:consume async
In production, run the worker as a supervised process.