- Created Claude.md with comprehensive project context for AI assistant - Added 7 skills for common development workflows: - docker-setup: Initialize Docker development environment - create-module: Scaffold new DDD modules following framework architecture - run-tests: Execute PHPUnit test suites with coverage - static-analysis: Run PHPStan and Deptrac for code quality - database-operations: Database import, export, and management - troubleshoot-docker: Diagnostic steps for Docker issues - debug-setup: Configure Xdebug for PHPStorm and VS Code Each skill includes step-by-step instructions, verification steps, troubleshooting guides, and quick reference commands.
8.9 KiB
Create Module
Create a new module in the Foundation framework following Domain-Driven Design principles and clean architecture.
When to use this skill
- User asks to "create a new module"
- User asks to "add a module"
- User asks to "scaffold a module"
- User wants to add new business functionality
- User mentions "new feature module"
Steps
-
Create Module Directory Structure
Create the base directory and three-layer architecture:
mkdir -p src/Modules/{ModuleName}/Domain mkdir -p src/Modules/{ModuleName}/Application mkdir -p src/Modules/{ModuleName}/Infrastructure/Api mkdir -p src/Modules/{ModuleName}/Infrastructure/Web mkdir -p src/Modules/{ModuleName}/Infrastructure/Database/Commands mkdir -p src/Modules/{ModuleName}/Infrastructure/Database/Queries -
Create Domain Layer (Pure business logic)
Create domain entities, value objects, and repository interfaces:
// src/Modules/{ModuleName}/Domain/{Entity}.php <?php namespace Foundation\Modules\{ModuleName}\Domain; final class {Entity} { public function __construct( private int $id, // Add properties ) {} public function getId(): int { return $this->id; } // Add getters and business methods }// src/Modules/{ModuleName}/Domain/{Repository}Interface.php <?php namespace Foundation\Modules\{ModuleName}\Domain; interface {Repository}Interface { public function findById(int $id): ?{Entity}; public function save({Entity} $entity): void; } -
Create Application Layer (Use cases)
Create application services that orchestrate domain logic:
// src/Modules/{ModuleName}/Application/{UseCase}/{UseCase}.php <?php namespace Foundation\Modules\{ModuleName}\Application\{UseCase}; use Foundation\Modules\{ModuleName}\Domain\{Repository}Interface; final class {UseCase} { public function __construct( private {Repository}Interface $repository ) {} public function execute(/* parameters */): mixed { // Use case logic } } -
Create Infrastructure Layer (External implementations)
a) Create Repository Implementation:
// src/Modules/{ModuleName}/Infrastructure/Database/{Repository}.php <?php namespace Foundation\Modules\{ModuleName}\Infrastructure\Database; use Foundation\Modules\{ModuleName}\Domain\{Entity}; use Foundation\Modules\{ModuleName}\Domain\{Repository}Interface; final class {Repository} implements {Repository}Interface { public function __construct( private FetchByIdQuery $fetchById, private SaveCommand $save ) {} public function findById(int $id): ?{Entity} { return $this->fetchById->execute($id); } public function save({Entity} $entity): void { $this->save->execute($entity); } }b) Create Database Commands/Queries:
// src/Modules/{ModuleName}/Infrastructure/Database/Queries/FetchByIdQuery.php <?php namespace Foundation\Modules\{ModuleName}\Infrastructure\Database\Queries; use PDO; use Foundation\Modules\{ModuleName}\Domain\{Entity}; final class FetchByIdQuery { public function __construct(private PDO $pdo) {} public function execute(int $id): ?{Entity} { $stmt = $this->pdo->prepare("SELECT * FROM {table} WHERE id = ?"); $stmt->execute([$id]); $data = $stmt->fetch(PDO::FETCH_ASSOC); return $data ? new {Entity}(/* map data */) : null; } }c) Create Controller:
// src/Modules/{ModuleName}/Infrastructure/Api/{Controller}.php <?php namespace Foundation\Modules\{ModuleName}\Infrastructure\Api; use Foundation\Core\Infrastructure\ControllerInterface; use Foundation\Core\Routing\Attributes\Get; use Foundation\Core\Routing\Attributes\Post; use Foundation\Core\Routing\Attributes\Group; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; #[Group('/api/{module-name}')] final class {Controller} implements ControllerInterface { public function __construct( private {UseCase} $useCase ) {} #[Get('')] public function index( ServerRequestInterface $request, ResponseInterface $response ): ResponseInterface { // Controller logic } } -
Create Service Provider
Create the service provider to register all module services:
// src/Modules/{ModuleName}/{ModuleName}ServiceProvider.php <?php namespace Foundation\Modules\{ModuleName}; use Foundation\Core\Application\ServiceProvider\ServiceProvider; use Foundation\Core\DependencyInjection\InflectableContainer; use Foundation\Modules\{ModuleName}\Domain\{Repository}Interface; use Foundation\Modules\{ModuleName}\Infrastructure\Database\{Repository}; use Foundation\Modules\{ModuleName}\Infrastructure\Database\Commands\*; use Foundation\Modules\{ModuleName}\Infrastructure\Database\Queries\*; use Foundation\Modules\{ModuleName}\Application\{UseCase}\*; use Foundation\Modules\{ModuleName}\Infrastructure\Api\{Controller}; final class {ModuleName}ServiceProvider extends ServiceProvider { public function register(InflectableContainer $container): void { // Register queries $container->register(FetchByIdQuery::class, [\PDO::class]); // Register commands $container->register(SaveCommand::class, [\PDO::class]); // Register repository $container->bind( {Repository}Interface::class, {Repository}::class, [FetchByIdQuery::class, SaveCommand::class] ); // Register use cases $container->register( {UseCase}::class, [{Repository}Interface::class] ); // Register controllers (routes auto-discovered via attributes) $container->register( {Controller}::class, [{UseCase}::class] ); } } -
Refresh Autoloader
make dump-autoload
Verification
-
Check Module is Auto-Discovered
The
ModuleLoaderbootstrapper automatically discovers service providers insrc/Modules/*/. No additional registration needed. -
Verify Routes are Registered
Access the controller endpoint to verify routes are working:
curl http://localhost:8000/api/{module-name} -
Check Service Container
Verify services are properly registered by accessing them in a controller or test.
Layer Dependency Rules
IMPORTANT: Always follow these strict dependency rules:
-
Domain Layer: NO dependencies on other layers
- Only pure PHP and domain concepts
- No framework dependencies
- No database or HTTP concerns
-
Application Layer: Can depend on Domain only
- Orchestrates domain logic
- Defines use cases
- No framework dependencies
-
Infrastructure Layer: Can depend on Application and itself
- Implements repository interfaces
- Contains controllers, commands, queries
- Has framework dependencies
Troubleshooting
Module Not Auto-Discovered
Problem: Service provider not loaded automatically.
Solution:
- Verify service provider is in:
src/Modules/{ModuleName}/{ModuleName}ServiceProvider.php - Verify it extends
Foundation\Core\Application\ServiceProvider\ServiceProvider - Check namespace matches directory structure
- Run
make dump-autoload
Routes Not Working
Problem: Controller routes not accessible.
Solution:
- Verify controller implements
Foundation\Core\Infrastructure\ControllerInterface - Check route attributes are properly imported and used
- Verify controller is registered in service provider
- Check for route conflicts with existing routes
Autoloading Issues
Problem: Class not found errors.
Solution:
make dump-autoload
Verify namespace matches directory structure following PSR-4:
Foundation\Modules\{ModuleName}→src/Modules/{ModuleName}
Examples
See existing modules for reference:
src/Modules/WelcomeScreen/- Complete example module
Quick Checklist
- Created Domain layer (entities, interfaces)
- Created Application layer (use cases)
- Created Infrastructure layer (controllers, repositories, commands, queries)
- Created Service Provider
- Service provider extends
ServiceProvider - Service provider in correct location
- Controllers implement
ControllerInterface - Routes use attributes (#[Get], #[Post], etc.)
- Repository interfaces in Domain layer
- Repository implementations in Infrastructure layer
- Followed layer dependency rules
- Ran
make dump-autoload - Verified routes are accessible