foundation/.claude/skills/foundation/create-module.md
Claude 5ae612227e
Add Claude.md and Foundation skills
- 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.
2025-11-04 21:41:27 +00:00

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

  1. 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
    
  2. 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;
    }
    
  3. 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
        }
    }
    
  4. 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
        }
    }
    
  5. 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]
            );
        }
    }
    
  6. Refresh Autoloader

    make dump-autoload
    

Verification

  1. Check Module is Auto-Discovered

    The ModuleLoader bootstrapper automatically discovers service providers in src/Modules/*/. No additional registration needed.

  2. Verify Routes are Registered

    Access the controller endpoint to verify routes are working:

    curl http://localhost:8000/api/{module-name}
    
  3. 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:

  1. Verify service provider is in: src/Modules/{ModuleName}/{ModuleName}ServiceProvider.php
  2. Verify it extends Foundation\Core\Application\ServiceProvider\ServiceProvider
  3. Check namespace matches directory structure
  4. Run make dump-autoload

Routes Not Working

Problem: Controller routes not accessible.

Solution:

  1. Verify controller implements Foundation\Core\Infrastructure\ControllerInterface
  2. Check route attributes are properly imported and used
  3. Verify controller is registered in service provider
  4. 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