{"id":36389542,"url":"https://github.com/ahmed-bhs/hexagonal-maker-bundle","last_synced_at":"2026-01-14T01:31:39.509Z","repository":{"id":331889143,"uuid":"1128540388","full_name":"ahmed-bhs/hexagonal-maker-bundle","owner":"ahmed-bhs","description":"A complete Symfony Maker bundle for generating Hexagonal Architecture (Ports \u0026 Adapters) components with CQRS, pure domain entities, and YAML mapping","archived":false,"fork":false,"pushed_at":"2026-01-09T14:05:13.000Z","size":1155,"stargazers_count":6,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-01-11T17:44:52.404Z","etag":null,"topics":["clean-archit","code-generator","command","cqrs","hexa","hexagonal-architecture","maker-bundle","patterns","php","ports-adapters","pure-domain","query","solid","symfony","symfony-bundle"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ahmed-bhs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG-AUTONOMIE.md","contributing":"docs/contributing/development.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP-AUTONOMIE.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-05T19:42:46.000Z","updated_at":"2026-01-09T13:33:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ahmed-bhs/hexagonal-maker-bundle","commit_stats":null,"previous_names":["ahmed-bhs/hexagonal-maker-bundle"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ahmed-bhs/hexagonal-maker-bundle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahmed-bhs%2Fhexagonal-maker-bundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahmed-bhs%2Fhexagonal-maker-bundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahmed-bhs%2Fhexagonal-maker-bundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahmed-bhs%2Fhexagonal-maker-bundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ahmed-bhs","download_url":"https://codeload.github.com/ahmed-bhs/hexagonal-maker-bundle/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahmed-bhs%2Fhexagonal-maker-bundle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408327,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T00:40:43.272Z","status":"ssl_error","status_checked_at":"2026-01-14T00:40:42.636Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clean-archit","code-generator","command","cqrs","hexa","hexagonal-architecture","maker-bundle","patterns","php","ports-adapters","pure-domain","query","solid","symfony","symfony-bundle"],"created_at":"2026-01-11T15:50:08.510Z","updated_at":"2026-01-14T01:31:39.490Z","avatar_url":"https://github.com/ahmed-bhs.png","language":"PHP","funding_links":["https://buymeacoffee.com/w6ZhBSGX2"],"categories":[],"sub_categories":[],"readme":"# Hexagonal Architecture Maker Bundle for Symfony\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/images/hexagonal-architecture.jpg\" alt=\"Hexagonal Architecture\" width=\"400\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eA complete Symfony Maker bundle for generating Hexagonal Architecture (Ports \u0026 Adapters) components\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://packagist.org/packages/ahmed-bhs/hexagonal-maker-bundle\"\u003e\u003cimg src=\"https://img.shields.io/packagist/v/ahmed-bhs/hexagonal-maker-bundle.svg\" alt=\"Latest Version\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/ahmed-bhs/hexagonal-maker-bundle/actions\"\u003e\u003cimg src=\"https://github.com/ahmed-bhs/hexagonal-maker-bundle/workflows/CI/badge.svg\" alt=\"CI Status\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\" alt=\"License\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.php.net/\"\u003e\u003cimg src=\"https://img.shields.io/badge/php-%3E%3D8.1-blue.svg\" alt=\"PHP Version\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://symfony.com/\"\u003e\u003cimg src=\"https://img.shields.io/badge/symfony-6.4%20%7C%207.x-blue.svg\" alt=\"Symfony\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n### ☕ Support This Project\n\nIf this project helped you or saved you time, consider buying me a coffee!\n\n[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Support-FFDD00?style=for-the-badge\u0026logo=buy-me-a-coffee\u0026logoColor=black)](https://buymeacoffee.com/w6ZhBSGX2)\n\n*Your support helps maintain this project and create more learning resources!* ❤️\n\n\u003c/div\u003e\n\n\n\u003cp align=\"center\"\u003e\n  ✨ \u003cstrong\u003e19 maker commands\u003c/strong\u003e | 💎 \u003cstrong\u003ePure Domain\u003c/strong\u003e | 🎯 \u003cstrong\u003eCQRS Pattern\u003c/strong\u003e | 🏗️ \u003cstrong\u003eFull Layer Coverage\u003c/strong\u003e | 🔄 \u003cstrong\u003eAsync/Queue Support\u003c/strong\u003e\n\u003c/p\u003e\n\n---\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [1. Features](#1-features)\n- [2. Why Hexagonal Architecture](#2-why-hexagonal-architecture) → [📚 Complete Guide](WHY-HEXAGONAL.md)\n- [3. Installation](#3-installation)\n- [4. Complete Architecture Generation](#4-complete-architecture-generation)\n- [5. Available Makers (18 Commands)](#5-available-makers)\n- [6. Configuration](#6-configuration)\n- [7. Best Practices](#7-best-practices)\n- [8. Additional Resources](#8-additional-resources)\n- [9. License](#9-license)\n\n---\n\n## Quick Start\n\n```bash\n# 1. Install\ncomposer require ahmedbhs/hexagonal-maker-bundle --dev\n\n# 2. Generate a complete module (User Registration example)\nbin/console make:hexagonal:entity user/account User\nbin/console make:hexagonal:exception user/account InvalidEmailException\nbin/console make:hexagonal:value-object user/account Email\nbin/console make:hexagonal:repository user/account User\nbin/console make:hexagonal:command user/account register --factory\nbin/console make:hexagonal:controller user/account CreateUser /users/register\nbin/console make:hexagonal:form user/account User\n\n# 3. Configure Doctrine ORM mapping (see section 7.3)\n# 4. Start coding your business logic!\n```\n\n**Result:** Complete hexagonal architecture with pure domain, separated layers, and ready-to-use components! 🚀\n\n---\n\n## 1. Features\n\n### 1.1 Core CQRS Components\n- **Commands** - Write operations that modify state (e.g., `CreateUserCommand`) with their handlers (e.g., `CreateUserCommandHandler`) decorated with `#[AsMessageHandler]` for business logic execution\n- **Queries** - Read operations that retrieve data (e.g., `FindUserQuery`) with their handlers (e.g., `FindUserQueryHandler`) decorated with `#[AsMessageHandler]` and response DTOs (e.g., `FindUserResponse`)\n\n### 1.2 Complete Maker Commands Summary\n\n**18 makers covering all hexagonal layers + tests + events + rapid CRUD:**\n\n| Layer | Maker Command | What it generates |\n|-------|--------------|-------------------|\n| **Domain** | `make:hexagonal:entity` | Domain entities + YAML mapping |\n| **Domain** | `make:hexagonal:value-object` | Immutable value objects |\n| **Domain** | `make:hexagonal:exception` | Business rule exceptions |\n| **Domain** | `make:hexagonal:domain-event` | Domain events |\n| **Application** | `make:hexagonal:command` | CQRS commands + handlers |\n| **Application** | `make:hexagonal:query` | CQRS queries + handlers + responses |\n| **Application** | `make:hexagonal:repository` | Repository port + Doctrine adapter |\n| **Application** | `make:hexagonal:input` | Input DTOs with validation |\n| **Application** | `make:hexagonal:use-case` | Use cases |\n| **Application/Infrastructure** | `make:hexagonal:event-subscriber` | Event subscribers |\n| **Infrastructure** | `make:hexagonal:message-handler` | Async message handlers |\n| **UI** | `make:hexagonal:controller` | Web controllers |\n| **UI** | `make:hexagonal:form` | Symfony forms |\n| **UI** | `make:hexagonal:cli-command` | Console commands |\n| **Tests** | `make:hexagonal:use-case-test` | Use case tests (KernelTestCase) |\n| **Tests** | `make:hexagonal:controller-test` | Controller tests (WebTestCase) |\n| **Tests** | `make:hexagonal:cli-command-test` | CLI tests (CommandTester) |\n| **Config** | `make:hexagonal:test-config` | Test configuration setup |\n| **Rapid Dev** | `make:hexagonal:crud` | Complete CRUD (Entity + 5 UseCases + Controllers + Forms + Tests) |\n\n---\n\n## 2. Why Hexagonal Architecture\n\n\u003e **📚 [Read the complete guide: WHY-HEXAGONAL.md](WHY-HEXAGONAL.md)**\n\n### 2.1 What the Founders Say\n\n#### Alistair Cockburn - Creator of Hexagonal Architecture (2005)\n\n\u003e *\"Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.\"*\n\u003e\n\u003e — Alistair Cockburn, [Hexagonal Architecture](https://alistair.cockburn.us/hexagonal-architecture/)\n\n**On the core principle:**\n\n\u003e *\"The hexagon is intended to visually highlight the following:*\n\u003e - *(a) There is an inside and an outside to the application*\n\u003e - *(b) The number of ports is not two, but many (and variable)*\n\u003e - *(c) The number of adapters for any particular port is not one, but many (and variable)*\"*\n\n**On dependencies:**\n\n\u003e *\"Create your application to work without either a UI or a database so you can run automated regression-tests against the application, work when the database becomes unavailable, and link applications together without any user involvement.\"*\n\n#### Robert C. Martin (Uncle Bob) - Creator of Clean Architecture (2012)\n\n**On the business logic:**\n\n\u003e *\"The business rules are the heart of the software. They carry the code that makes, or saves, money. They are the family jewels. We want to protect them from all forms of complexity and change.\"*\n\u003e\n\u003e — Robert C. Martin, [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)\n\n**On frameworks:**\n\n\u003e *\"Frameworks are tools to be used, not architectures to be conformed to. If your architecture is based on frameworks, then it cannot be based on your use cases.\"*\n\n**On the dependency rule:**\n\n\u003e *\"Source code dependencies must point only inward, toward higher-level policies. Nothing in an inner circle can know anything at all about something in an outer circle.\"*\n\n**On volatility:**\n\n\u003e *\"The less volatile things are, the more they should be depended upon. Business rules change less frequently than technical details, so technical details should depend on business rules, not the other way around.\"*\n\n#### Eric Evans - Domain-Driven Design (2003)\n\n**On isolating the domain:**\n\n\u003e *\"The heart of software is its ability to solve domain-related problems for its user. All other features, vital though they may be, support this basic purpose.\"*\n\u003e\n\u003e — Eric Evans, [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://www.domainlanguage.com/ddd/)\n\n**On the domain model:**\n\n\u003e *\"When a significant process or transformation in the domain is not a natural responsibility of an ENTITY or VALUE OBJECT, add an operation to the model as a standalone interface declared as a SERVICE.\"*\n\n#### Jeffrey Palermo - Onion Architecture (2008)\n\n**On dependency direction:**\n\n\u003e *\"The fundamental rule is that all code can depend on layers more central, but code cannot depend on layers further out from the core. In other words, all coupling is toward the center.\"*\n\u003e\n\u003e — Jeffrey Palermo, [The Onion Architecture](https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/)\n\n**On persistence ignorance:**\n\n\u003e *\"The application core doesn't know anything about how data is persisted or where data comes from. It defines interfaces for these concerns, and the outer layers implement these interfaces.\"*\n\n### 2.2 Key Principles from the Masters\n\n| Principle | Author | Meaning |\n|-----------|--------|---------|\n| **Dependency Inversion** | Uncle Bob | High-level modules should not depend on low-level modules. Both should depend on abstractions. |\n| **Ports \u0026 Adapters** | Alistair Cockburn | The core defines ports (interfaces), the outside world provides adapters (implementations). |\n| **Screaming Architecture** | Uncle Bob | Your architecture should scream what the application does, not what framework it uses. |\n| **Ubiquitous Language** | Eric Evans | The code should speak the language of the domain experts, not technical jargon. |\n| **Isolation** | All | Business logic must be isolated from technical concerns (UI, DB, frameworks). |\n\n### Quick Summary\n\n**Everything is coupled anyway, so why bother?**\n\nHexagonal architecture isn't about eliminating coupling—that's impossible. It's about **controlling the direction** of coupling.\n\n### The Core Problem with Traditional Architecture\n\n**Traditional layered architecture problems:**\n- ⛓️ Framework Prison: Business logic tightly coupled to Doctrine/Symfony\n- 🐢 Testing Complexity: Every test requires database, 10 min vs 10 sec\n- 🌪️ Lost Business Rules: Rules scattered across 10+ files\n- 🧱 Cannot Evolve: Adding GraphQL/CLI requires code duplication\n- 📈 Cost Predictability: Simple features take 3x longer after 2 years\n\n**Hexagonal architecture solution:**\n- **💎 Pure Domain Isolation:** Your business logic lives in pure PHP, zero framework dependencies. Why? Because frameworks become obsolete, but your business rules don't. Isolated domain = no technical debt accumulation, easier to understand (speaks business language, not technical jargon), and survives all technology changes. The secret: Dependency Inversion - the domain defines interfaces (Ports), infrastructure adapts to them\n- **🎯 Direction Control:** Business logic depends on abstractions, infrastructure depends on business\n- **⚡ Testing Speed:** 1000x faster (in-memory vs database I/O) - 10 min → 10 sec\n- **🔄 Technology Freedom:** Swap MySQL to MongoDB in days not months (10-20x effort saved)\n- **💰 Cost Predictability (The \"5-Day Rule\"):** Features cost consistent time, no technical debt tax\n- **🚀 Reusability:** Same business logic for REST, GraphQL, CLI, gRPC\n- **🏗️ Craftsmanship Practices:** Promotes SOLID principles, DRY (Don't Repeat Yourself), YAGNI (You Aren't Gonna Need It), KISS (Keep It Simple, Stupid), Separation of Concerns (SoC), and design patterns like DTO, Strategy, Factory, Dependency Injection\n\n**The Investment Analogy:**\n- Traditional = Consumer credit: easy at start, debt strangles you later\n- Hexagonal = Investment: pay upfront, every feature costs its real price forever\n\n\u003e **📖 Want to learn more?** [Read the complete guide with examples, analogies, and decision trees →](WHY-HEXAGONAL.md)\n\n---\n\n## 3. Installation\n\n```bash\ncomposer require ahmedbhs/hexagonal-maker-bundle\n```\n\nThe bundle will auto-register if you use Symfony Flex. Otherwise, add it to `config/bundles.php`:\n\n```php\nreturn [\n    // ...\n    AhmedBhs\\HexagonalMakerBundle\\HexagonalMakerBundle::class =\u003e ['dev' =\u003e true],\n];\n```\n\n---\n\n## 4. Complete Architecture Generation\n\nThis section shows exactly how to build a complete hexagonal architecture module step by step, with the exact commands to run for each component.\n\n### 4.1 Scenario: User Account Management Module\n\nLet's build a complete **User Account** module with all layers of hexagonal architecture.\n\n#### 4.1.1 Step-by-Step Architecture Generation\n\n```bash\n# LAYER 1: DOMAIN (Core Business Logic - Pure PHP)\n# ============================================\n\n# 1.1 Create Domain Entity (User aggregate root - PURE, no Doctrine)\nbin/console make:hexagonal:entity user/account User\n\n# 1.2 Create Domain Exceptions (business rule violations)\nbin/console make:hexagonal:exception user/account InvalidEmailException\nbin/console make:hexagonal:exception user/account UserAlreadyExistsException\n\n# 1.3 Create Value Objects (domain concepts)\nbin/console make:hexagonal:value-object user/account UserId\nbin/console make:hexagonal:value-object user/account Email\nbin/console make:hexagonal:value-object user/account Password\n\n# 1.4 Create Repository Port (interface in domain)\nbin/console make:hexagonal:repository user/account User\n\n\n# LAYER 2: APPLICATION (Use Cases \u0026 DTOs)\n# ============================================\n\n# 2.1 Create Input DTOs (with validation)\nbin/console make:hexagonal:input user/account RegisterUserInput\n\n# 2.2 Create Registration Use Case (Command)\nbin/console make:hexagonal:command user/account register --factory\n\n# 2.3 Create Activation Use Case (Command)\nbin/console make:hexagonal:command user/account activate\n\n# 2.4 Create Find User Use Case (Query)\nbin/console make:hexagonal:query user/account find-by-id\n\n# 2.5 Create List Users Use Case (Query)\nbin/console make:hexagonal:query user/account list-all\n\n# 2.6 Alternative: Create Use Case (instead of Command/Query)\nbin/console make:hexagonal:use-case user/account RegisterUser\n\n\n# LAYER 3: UI (Primary Adapters - Driving)\n# ============================================\n\n# 3.1 Create Web Controller\nbin/console make:hexagonal:controller user/account RegisterUser /users/register\n\n# 3.2 Create Symfony Form\nbin/console make:hexagonal:form user/account User\n\n# 3.3 Create CLI Command\nbin/console make:hexagonal:cli-command user/account RegisterUser app:user:register\n\n\n# LAYER 4: INFRASTRUCTURE (Secondary Adapters - Already generated!)\n# ============================================\n# The Repository adapter was auto-generated in step 1.4\n# Located at: Infrastructure/Persistence/Doctrine/DoctrineUserRepository.php\n# Doctrine YAML mapping auto-generated with entity in step 1.1\n# Located at: Infrastructure/Persistence/Doctrine/Orm/Mapping/User.orm.yml\n```\n\n#### 4.1.2 Generated Architecture Structure\n\nAfter running the commands above, here's your **complete hexagonal architecture**:\n\n```\nsrc/User/Account/\n│\n├── Domain/                                           # 💎 CORE BUSINESS LOGIC (Pure PHP, ZERO framework deps)\n│   ├── Model/\n│   │   └── User.php                                 ← make:hexagonal:entity\n│   │\n│   ├── Exception/                                    ← NEW!\n│   │   ├── InvalidEmailException.php                ← make:hexagonal:exception\n│   │   └── UserAlreadyExistsException.php           ← make:hexagonal:exception\n│   │\n│   ├── ValueObject/\n│   │   ├── UserId.php                               ← make:hexagonal:value-object\n│   │   ├── Email.php                                ← make:hexagonal:value-object\n│   │   └── Password.php                             ← make:hexagonal:value-object\n│   │\n│   └── Port/                                         # Interfaces (Ports)\n│       └── UserRepositoryInterface.php               ← make:hexagonal:repository\n│\n├── Application/                                      # ⚙️ USE CASES \u0026 DTOs\n│   ├── Input/                                        ← NEW!\n│   │   └── RegisterUserInput.php                    ← make:hexagonal:input\n│   │\n│   ├── UseCase/                                      ← NEW!\n│   │   └── RegisterUserUseCase.php                  ← make:hexagonal:use-case\n│   │\n│   ├── Register/                                     # CQRS Command\n│   │   ├── RegisterCommand.php                      ← make:hexagonal:command\n│   │   ├── RegisterCommandHandler.php               ← (auto-generated)\n│   │   └── AccountFactory.php                       ← (auto-generated with --factory)\n│   │\n│   ├── Activate/\n│   │   ├── ActivateCommand.php                      ← make:hexagonal:command\n│   │   └── ActivateCommandHandler.php               ← (auto-generated)\n│   │\n│   ├── FindById/                                     # CQRS Query\n│   │   ├── FindByIdQuery.php                        ← make:hexagonal:query\n│   │   ├── FindByIdQueryHandler.php                 ← (auto-generated)\n│   │   └── FindByIdResponse.php                     ← (auto-generated)\n│   │\n│   └── ListAll/\n│       ├── ListAllQuery.php                         ← make:hexagonal:query\n│       ├── ListAllQueryHandler.php                  ← (auto-generated)\n│       └── ListAllResponse.php                      ← (auto-generated)\n│\n├── UI/                                               # 🎮 PRIMARY ADAPTERS (Driving) - NEW!\n│   ├── Http/\n│   │   └── Web/\n│   │       ├── Controller/\n│   │       │   └── RegisterUserController.php       ← make:hexagonal:controller\n│   │       │\n│   │       └── Form/\n│   │           └── UserType.php                     ← make:hexagonal:form\n│   │\n│   └── Cli/\n│       └── RegisterUserCommand.php                  ← make:hexagonal:cli-command\n│\n└── Infrastructure/                                   # 🔌 SECONDARY ADAPTERS (Driven)\n    └── Persistence/\n        └── Doctrine/\n            ├── Orm/\n            │   └── Mapping/\n            │       └── User.orm.yml                  ← Auto-generated with entity (YAML mapping)\n            │\n            └── DoctrineUserRepository.php            ← make:hexagonal:repository (Adapter)\n```\n\n#### 4.1.3 Understanding the Architecture\n\n| Layer | Responsibility | Dependencies | Makers Available |\n|-------|---------------|--------------|------------------|\n| **💎 Domain** | Business logic, rules, invariants | **ZERO** (Pure PHP) | `make:hexagonal:entity`\u003cbr\u003e`make:hexagonal:value-object`\u003cbr\u003e`make:hexagonal:exception`\u003cbr\u003e`make:hexagonal:repository` (Port) |\n| **⚙️ Application** | Use cases, orchestration, DTOs | Domain only | `make:hexagonal:command`\u003cbr\u003e`make:hexagonal:query`\u003cbr\u003e`make:hexagonal:use-case`\u003cbr\u003e`make:hexagonal:input` |\n| **🎮 UI** | HTTP/CLI interfaces (Primary Adapters) | Application + Domain | `make:hexagonal:controller`\u003cbr\u003e`make:hexagonal:form`\u003cbr\u003e`make:hexagonal:cli-command` |\n| **🔌 Infrastructure** | DB/API implementation (Secondary Adapters) | Domain (implements Ports) | `make:hexagonal:repository` (Adapter)\u003cbr\u003eAuto: Doctrine YAML mapping |\n\n### 4.2 Dependency Flow (Hexagonal Rule)\n\n```mermaid\n%%{init: {'theme':'base', 'themeVariables': { 'fontSize':'14px'}}}%%\ngraph TB\n    subgraph UI[\"🎮 UI / Controllers\"]\n        HTTP[\"🌐 HTTP Controllers\"]\n        CLI[\"⌨️ bin/console Commands\"]\n    end\n\n    subgraph APP[\"⚙️ APPLICATION LAYER\"]\n        Commands[\"📨 Commands \u0026 Queries\u003cbr/\u003e\u003csmall\u003eUse Cases\u003c/small\u003e\"]\n        Reg[\"• RegisterCommand\"]\n        Find[\"• FindByIdQuery\"]\n\n        Commands --- Reg\n        Commands --- Find\n    end\n\n    subgraph DOMAIN[\"💎 DOMAIN LAYER - CORE\"]\n        Entities[\"📦 Entities \u0026 Value Objects\"]\n        EntList[\"• User\u003cbr/\u003e• Email, UserId\"]\n        Ports[\"🔗 Ports\u003cbr/\u003e\u003csmall\u003eInterfaces\u003c/small\u003e\"]\n        PortList[\"• UserRepositoryInterface\"]\n\n        Entities --- EntList\n        Ports --- PortList\n    end\n\n    subgraph INFRA[\"🔌 INFRASTRUCTURE LAYER\"]\n        Adapters[\"🔧 Adapters\u003cbr/\u003e\u003csmall\u003eImplementations\u003c/small\u003e\"]\n        AdList[\"• DoctrineUserRepository\"]\n\n        Adapters --- AdList\n    end\n\n    UI ==\u003e|\"uses\"| APP\n    APP ==\u003e|\"depends on\"| DOMAIN\n    INFRA -.-\u003e|\"🎯 implements\"| Ports\n\n    style DOMAIN fill:#C8E6C9,stroke:#2E7D32,stroke-width:4px,color:#000\n    style APP fill:#B3E5FC,stroke:#0277BD,stroke-width:3px,color:#000\n    style INFRA fill:#F8BBD0,stroke:#C2185B,stroke-width:3px,color:#000\n    style UI fill:#E1BEE7,stroke:#6A1B9A,stroke-width:3px,color:#000\n\n    style Commands fill:#E1F5FE,stroke:#01579B,stroke-width:2px,color:#000\n    style Entities fill:#E8F5E9,stroke:#1B5E20,stroke-width:2px,color:#000\n    style Ports fill:#FFF9C4,stroke:#F57F17,stroke-width:2px,color:#000\n    style Adapters fill:#FCE4EC,stroke:#880E4F,stroke-width:2px,color:#000\n```\n\n**Key Points:**\n- `make:hexagonal:command` / `make:hexagonal:query` → Application Layer\n- `make:hexagonal:entity` / `make:hexagonal:value-object` → Domain Layer\n- `make:hexagonal:repository` → Port (Domain) + Adapter (Infrastructure)\n\n### 4.3 Quick Start: 5-Command Complete Module\n\nWant to generate a complete module in just 5 commands? Here's a copy-paste ready script:\n\n```bash\n# Context: Product Catalog Module\nbin/console make:hexagonal:entity product/catalog Product\nbin/console make:hexagonal:value-object product/catalog ProductId\nbin/console make:hexagonal:repository product/catalog Product\nbin/console make:hexagonal:command product/catalog create-product --factory\nbin/console make:hexagonal:query product/catalog find-product\n```\n\n**Result:** Complete Product module with Domain, Application, and Infrastructure layers.\n\n---\n\n## 5. Available Makers\n\n**Quick reference:** 19 makers covering Domain, Application, Infrastructure, UI, and Tests layers.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e📖 Click to expand: Detailed maker commands documentation\u003c/b\u003e\u003c/summary\u003e\n\n### 5.1 Create a Command (Write Operation)\n\nGenerate a CQRS Command for state-changing operations:\n\n```bash\nbin/console make:hexagonal:command user/account register\n```\n\n**Generated files:**\n```\nsrc/User/Account/Application/Register/\n├── RegisterCommand.php         # The command (DTO)\n└── RegisterCommandHandler.php  # The handler (business logic)\n```\n\n**With Factory pattern:**\n\n```bash\nbin/console make:hexagonal:command user/account register --factory\n```\n\n**Generated files:**\n```\nsrc/User/Account/Application/Register/\n├── RegisterCommand.php\n├── RegisterCommandHandler.php  # Uses factory\n└── AccountFactory.php          # Domain entity factory\n```\n\n**With Tests:**\n\n```bash\nbin/console make:hexagonal:command user/account register --with-tests\n```\n\n**Generated files:**\n```\nsrc/User/Account/Application/Register/\n├── RegisterCommand.php\n├── RegisterCommandHandler.php\ntests/Unit/User/Account/Application/Register/\n├── RegisterCommandHandlerTest.php      # Unit test (with mocks)\ntests/Integration/User/Account/Application/Register/\n└── RegisterCommandHandlerTest.php      # Integration test (full stack)\n```\n\n**With Factory and Tests:**\n\n```bash\nbin/console make:hexagonal:command user/account register --factory --with-tests\n```\n\n### 5.2 Create a Query (Read Operation)\n\nGenerate a CQRS Query for data retrieval:\n\n```bash\nbin/console make:hexagonal:query user/account find\n```\n\n**Generated files:**\n```\nsrc/User/Account/Application/Find/\n├── FindQuery.php          # The query (request DTO)\n├── FindQueryHandler.php   # The handler (read logic)\n└── FindResponse.php       # The response (response DTO)\n```\n\n### 5.3 Create a Repository (Port + Adapter)\n\nGenerate a repository interface (Port) and its infrastructure implementation (Adapter):\n\n```bash\nbin/console make:hexagonal:repository user/account User\n```\n\n**Generated files:**\n```\nsrc/User/Account/\n├── Domain/Port/\n│   └── UserRepositoryInterface.php   # Port (interface)\n└── Infrastructure/Persistence/Doctrine/\n    └── DoctrineUserRepository.php    # Adapter (implementation)\n```\n\n### 5.4 Create a Domain Entity\n\nGenerate a domain entity in the core layer:\n\n```bash\nbin/console make:hexagonal:entity user/account User\n```\n\n**Generated files:**\n```\nsrc/User/Account/Domain/Model/\n└── User.php  # Domain entity with business logic\n```\n\n### 5.5 Create a Value Object\n\nGenerate an immutable value object:\n\n```bash\nbin/console make:hexagonal:value-object user/account Email\n```\n\n**Generated files:**\n```\nsrc/User/Account/Domain/ValueObject/\n└── Email.php  # Immutable value object with validation\n```\n\n### 5.6 Create a Domain Exception\n\nGenerate a business exception in the domain layer:\n\n```bash\nbin/console make:hexagonal:exception user/account InvalidEmailException\n```\n\n**Generated files:**\n```\nsrc/User/Account/Domain/Exception/\n└── InvalidEmailException.php  # Domain exception for business rule violations\n```\n\n### 5.7 Create an Input DTO\n\nGenerate an input DTO with validation constraints:\n\n```bash\nbin/console make:hexagonal:input user/account CreateUserInput\n```\n\n**Generated files:**\n```\nsrc/User/Account/Application/Input/\n└── CreateUserInput.php  # Input DTO with Symfony Validator constraints\n```\n\n### 5.8 Create a Use Case\n\nGenerate a use case (application service):\n\n```bash\nbin/console make:hexagonal:use-case user/account CreateUser\n```\n\n**Generated files:**\n```\nsrc/User/Account/Application/UseCase/\n└── CreateUserUseCase.php  # Use case orchestrating domain logic\n```\n\n### 5.9 Create a Web Controller (UI Layer)\n\nGenerate a web controller for HTTP requests:\n\n```bash\nbin/console make:hexagonal:controller user/account CreateUser /users/create\n```\n\n**Generated files:**\n```\nsrc/User/Account/UI/Http/Web/Controller/\n└── CreateUserController.php  # Web controller with routing\n```\n\n### 5.10 Create a Symfony Form\n\nGenerate a Symfony form type:\n\n```bash\nbin/console make:hexagonal:form user/account User\n```\n\n**Generated files:**\n```\nsrc/User/Account/UI/Http/Web/Form/\n└── UserType.php  # Symfony form type for web UI\n```\n\n### 5.11 Create a CLI Command (UI Layer)\n\nGenerate a console command:\n\n```bash\nbin/console make:hexagonal:cli-command user/account CreateUser app:user:create\n```\n\n**Generated files:**\n```\nsrc/User/Account/UI/Cli/\n└── CreateUserCommand.php  # CLI command for console operations\n```\n\n**With UseCase workflow:**\n\n```bash\nbin/console make:hexagonal:cli-command user/account CreateUser app:user:create --with-use-case\n```\n\n**Generated files:**\n```\nsrc/User/Account/UI/Cli/\n└── CreateUserCommand.php\n\nsrc/User/Account/Application/\n├── UseCase/\n│   └── CreateUserUseCase.php\n├── Command/\n│   ├── CreateUserCommand.php\n│   └── CreateUserCommandHandler.php\n└── Input/\n    └── CreateUserInput.php\n```\n\n**Benefits:**\n- Avoids duplication between web and CLI interfaces\n- Both interfaces use the same UseCase\n- Consistent business logic across all entry points\n\n### 5.12 Create a Use Case Test (Tests)\n\nGenerate a test for your use case (Application layer):\n\n```bash\nbin/console make:hexagonal:use-case-test blog/post CreatePost\n```\n\n**Generated files:**\n```\ntests/Blog/Post/Application/CreatePost/\n└── CreatePostTest.php  # KernelTestCase with repository switching\n```\n\n**Key features:**\n- Extends `KernelTestCase` for full container access\n- Includes success and validation test methods\n- Data providers for parameterized testing\n- Helper method to switch between repository implementations (Memory/Doctrine/File)\n\n### 5.13 Create a Controller Test (Tests)\n\nGenerate a test for your web controller (UI layer):\n\n```bash\nbin/console make:hexagonal:controller-test blog/post CreatePost /posts/create\n```\n\n**Generated files:**\n```\ntests/Blog/Post/UI/Http/Web/Controller/\n└── CreatePostControllerTest.php  # WebTestCase with HTTP client\n```\n\n**Key features:**\n- Extends `WebTestCase` for HTTP testing\n- Tests page loading and redirects\n- Form submission testing with field mapping\n- Database state verification\n- Automatic cleanup in `setUp()`\n\n### 5.14 Create a CLI Command Test (Tests)\n\nGenerate a test for your console command (UI layer):\n\n```bash\nbin/console make:hexagonal:cli-command-test blog/post CreatePost app:post:create\n```\n\n**Generated files:**\n```\ntests/Blog/Post/UI/Cli/\n└── CreatePostCommandTest.php  # CommandTester for CLI testing\n```\n\n**Key features:**\n- Extends `KernelTestCase` with `CommandTester`\n- Tests command execution and exit codes\n- Tests arguments and options\n- Output verification\n- Error handling tests\n\n### 5.15 Create a Domain Event (Domain Layer)\n\nGenerate an immutable domain event:\n\n```bash\nbin/console make:hexagonal:domain-event order/payment OrderPlaced\n```\n\n**Generated files:**\n```\nsrc/Order/Payment/Domain/Event/\n└── OrderPlacedEvent.php  # Immutable event representing a business fact\n```\n\n**Key features:**\n- Readonly class for immutability\n- Contains only data (no behavior)\n- Represents a fact that happened in the domain\n- Can be dispatched from entities or use cases\n\n### 5.16 Create an Event Subscriber (Application or Infrastructure)\n\nGenerate an event subscriber with layer choice:\n\n```bash\n# Application Layer (for business workflow orchestration)\nbin/console make:hexagonal:event-subscriber order/payment OrderPlaced --layer=application\n\n# Infrastructure Layer (for technical concerns)\nbin/console make:hexagonal:event-subscriber shared/logging Exception --layer=infrastructure\n```\n\n**Generated files (Application):**\n```\nsrc/Order/Payment/Application/EventSubscriber/\n└── OrderPlacedSubscriber.php  # Orchestrates use cases in response to events\n```\n\n**Generated files (Infrastructure):**\n```\nsrc/Shared/Infrastructure/EventSubscriber/\n└── ExceptionSubscriber.php  # Handles technical concerns (logging, monitoring)\n```\n\n**Key features:**\n- **Application Layer**: Orchestrates business workflows, calls use cases\n- **Infrastructure Layer**: Handles framework events, logging, caching\n- Implements `EventSubscriberInterface`\n- Auto-configured by Symfony\n\n### 5.17 Enhanced Form with Auto-Generated Command/Input\n\nGenerate a form type with optional Command and Input DTO:\n\n```bash\n# Standard form only\nbin/console make:hexagonal:form blog/post Post\n\n# Form + Command + Input DTO in one command!\nbin/console make:hexagonal:form blog/post Post --with-command --action=Create\n```\n\n**Generated files (with --with-command):**\n```\nsrc/Blog/Post/UI/Http/Web/Form/\n└── PostType.php                    # Symfony form type\n\nsrc/Blog/Post/Application/Input/\n└── CreatePostInput.php             # Input DTO with validation\n\nsrc/Blog/Post/Application/Command/\n├── CreatePostCommand.php           # Command object\n└── CreatePostCommandHandler.php    # Command handler\n```\n\n**Benefits:**\n- One command generates complete workflow\n- Form fields map to Command properties\n- Input DTO provides validation layer\n- Saves time and ensures consistency\n\n---\n\n## 5.18 Generate Complete CRUD Module 🚀\n\nThe most powerful command in the bundle - generate an entire CRUD module in seconds:\n\n```bash\nbin/console make:hexagonal:crud blog/post Post --route-prefix=/posts\n```\n\n**This single command generates 20+ files across all layers:**\n\n```\n📦 Domain Layer (3 files):\n  - Post.php (Entity)\n  - PostRepositoryInterface.php (Port)\n\n🔧 Infrastructure Layer (2 files):\n  - DoctrinePostRepository.php (Adapter)\n  - Post.orm.yml (Doctrine mapping)\n\n🎯 Application Layer (15 files):\n  - CreatePostUseCase.php + CreatePostCommand.php + CreatePostInput.php\n  - UpdatePostUseCase.php + UpdatePostCommand.php + UpdatePostInput.php\n  - DeletePostUseCase.php + DeletePostCommand.php + DeletePostInput.php\n  - GetPostUseCase.php + GetPostCommand.php + GetPostInput.php\n  - ListPostUseCase.php + ListPostCommand.php + ListPostInput.php\n\n🌐 UI Web Layer (6 files):\n  - CreatePostController.php\n  - UpdatePostController.php\n  - DeletePostController.php\n  - ShowPostController.php\n  - ListPostController.php\n  - PostType.php (Form)\n```\n\n**With tests:**\n\n```bash\nbin/console make:hexagonal:crud blog/post Post --with-tests\n```\n\n**Generates 30+ files including:**\n- All UseCase tests (5 files)\n- All Controller tests (5 files)\n\n**With ID ValueObject:**\n\n```bash\nbin/console make:hexagonal:crud blog/post Post --with-id-vo\n```\n\n**Additional file generated:**\n- PostId.php (ValueObject for typed IDs)\n\n**Complete example with all options:**\n\n```bash\nbin/console make:hexagonal:crud blog/post Post \\\n  --route-prefix=/posts \\\n  --with-tests \\\n  --with-id-vo\n```\n\n**Generated routes:**\n- `GET /posts` - List all posts\n- `GET /posts/{id}` - Show single post\n- `GET /posts/new` - Create new post form\n- `POST /posts/new` - Submit new post\n- `GET /posts/{id}/edit` - Edit post form\n- `POST /posts/{id}/edit` - Submit edited post\n- `DELETE /posts/{id}/delete` - Delete post\n\n**Next steps after generation:**\n1. Add properties to your Entity\n2. Complete Doctrine ORM mapping\n3. Configure form fields in PostType.php\n4. Implement UseCase business logic\n5. Implement Repository methods\n6. Run tests (if generated)\n\n**Perfect for:**\n- Rapid prototyping\n- Starting new modules\n- Learning hexagonal architecture structure\n- Scaffolding admin interfaces\n\n---\n\n## 5.19 Powerful `--with-*` Options for Rapid Development ⚡\n\nAll makers support powerful options to generate related files automatically, dramatically speeding up development:\n\n### Controller: `--with-workflow`\nGenerate complete web workflow in one command:\n\n```bash\nbin/console make:hexagonal:controller blog/post CreatePost /posts/create --with-workflow\n```\n\n**Generates 6 files:**\n- 🎯 CreatePostController.php (UI)\n- 🎯 PostType.php (Form)\n- 🎯 CreatePostUseCase.php (Application)\n- 🎯 CreatePostCommand.php + Handler (Application)\n- 🎯 CreatePostInput.php (Application)\n\n**Impact:** Creates complete CRUD workflow instantly!\n\n### Entity: `--with-repository` and `--with-id-vo`\nGenerate entity with repository and ID value object:\n\n```bash\nbin/console make:hexagonal:entity blog/post Post --with-repository --with-id-vo\n```\n\n**Generates 5 files:**\n- 🎯 Post.php (Domain Entity)\n- 🎯 Post.orm.yml (Doctrine Mapping)\n- 🎯 PostRepositoryInterface.php (Domain Port)\n- 🎯 DoctrinePostRepository.php (Infrastructure)\n- 🎯 PostId.php (Value Object)\n\n**Impact:** Complete entity setup with persistence!\n\n### UseCase: `--with-test`\nGenerate use case with its test:\n\n```bash\nbin/console make:hexagonal:use-case blog/post CreatePost --with-test\n```\n\n**Generates 2 files:**\n- 🎯 CreatePostUseCase.php (Application)\n- 🎯 CreatePostTest.php (Tests)\n\n**Impact:** Encourages TDD from the start!\n\n### DomainEvent: `--with-subscriber`\nGenerate event with its subscriber:\n\n```bash\nbin/console make:hexagonal:domain-event order/payment OrderPlaced --with-subscriber\n```\n\n**Generates 2 files:**\n- 🎯 OrderPlacedEvent.php (Domain)\n- 🎯 OrderPlacedSubscriber.php (Application)\n\n**Impact:** Event-driven architecture ready to use!\n\n### Form: `--with-command`\nAlready documented in section 5.17\n\n### CLI Command: `--with-use-case`\nGenerate CLI command with UseCase workflow:\n\n```bash\nbin/console make:hexagonal:cli-command blog/post CreatePost app:post:create --with-use-case\n```\n\n**Generates 4 files:**\n- 🎯 CreatePostCommand.php (UI CLI)\n- 🎯 CreatePostUseCase.php (Application)\n- 🎯 CreatePostCommand.php + Handler (Application)\n- 🎯 CreatePostInput.php (Application)\n\n**Impact:** Shares business logic between web and CLI interfaces!\n\n### Summary Table\n\n| Maker | Option | Generates | Use Case |\n|-------|--------|-----------|----------|\n| `make:hexagonal:controller` | `--with-workflow` | Controller + Form + UseCase + Command + Input | Complete web CRUD |\n| `make:hexagonal:cli-command` | `--with-use-case` | CLI + UseCase + Command + Input | CLI with business logic |\n| `make:hexagonal:entity` | `--with-repository` | Entity + Mapping + Port + Adapter | Entity with persistence |\n| `make:hexagonal:entity` | `--with-id-vo` | Entity + ID ValueObject | Typed IDs |\n| `make:hexagonal:use-case` | `--with-test` | UseCase + Test | TDD workflow |\n| `make:hexagonal:domain-event` | `--with-subscriber` | Event + Subscriber | Event-driven |\n| `make:hexagonal:form` | `--with-command` | Form + Command + Input | Form workflow |\n| `make:hexagonal:crud` | `--with-tests` | Complete CRUD + All tests | Full module with tests |\n| `make:hexagonal:crud` | `--with-id-vo` | Complete CRUD + ID VO | CRUD with typed IDs |\n\n**Pro Tip:** Combine options for maximum productivity!\n\n```bash\n# Option 1: Build feature step-by-step (2 commands)\nbin/console make:hexagonal:entity blog/post Post --with-repository --with-id-vo\nbin/console make:hexagonal:controller blog/post CreatePost /posts/create --with-workflow\n\n# Option 2: Generate entire CRUD module instantly (1 command) ⚡\nbin/console make:hexagonal:crud blog/post Post --with-tests --with-id-vo\n\n# Option 3: CLI + Web sharing same business logic\nbin/console make:hexagonal:use-case blog/post CreatePost --with-test\nbin/console make:hexagonal:controller blog/post CreatePost /posts/create\nbin/console make:hexagonal:cli-command blog/post CreatePost app:post:create\n```\n\n\u003c/details\u003e\n\n---\n\n## 6. Configuration\n\nCreate `config/packages/hexagonal_maker.yaml`:\n\n```yaml\nhexagonal_maker:\n    # Directory where custom skeleton templates are stored\n    skeleton_dir: '%kernel.project_dir%/config/skeleton'\n\n    # Root source directory\n    root_dir: 'src'\n\n    # Root namespace\n    root_namespace: 'App'\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e7.1 Customizing Templates\u003c/b\u003e\u003c/summary\u003e\n\n## 7.1 Customizing Templates\n\nYou can override default templates by creating your own in `config/skeleton/`:\n\n```\nconfig/skeleton/\n└── src/Module/\n    ├── Application/\n    │   ├── Command/\n    │   │   ├── Command.tpl.php\n    │   │   ├── CommandHandler.tpl.php\n    │   │   ├── CommandHandlerWithFactory.tpl.php\n    │   │   └── Factory.tpl.php\n    │   └── Query/\n    │       ├── Query.tpl.php\n    │       ├── QueryHandler.tpl.php\n    │       └── Response.tpl.php\n    ├── Domain/\n    │   ├── Model/\n    │   │   └── Entity.tpl.php\n    │   ├── ValueObject/\n    │   │   └── ValueObject.tpl.php\n    │   └── Port/\n    │       └── RepositoryInterface.tpl.php\n    └── Infrastructure/\n        └── Persistence/\n            └── Doctrine/\n                └── DoctrineRepository.tpl.php\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e7.2 Testing Strategy\u003c/b\u003e\u003c/summary\u003e\n\n## 7.2 Testing Strategy\n\nThe bundle generates two types of tests when using `--with-tests`:\n\n### 7.2.1 Unit Tests\n\nLocated in `tests/Unit/`, these tests:\n- Use mocks and stubs for dependencies\n- Test business logic in isolation\n- Run extremely fast (milliseconds)\n- No database, no framework boot\n\n**Example:**\n```php\nfinal class RegisterCommandHandlerTest extends TestCase\n{\n    public function testHandlerExecutesSuccessfully(): void\n    {\n        $repository = $this-\u003ecreateMock(UserRepositoryInterface::class);\n        $repository-\u003eexpects($this-\u003eonce())\n            -\u003emethod('save');\n\n        $handler = new RegisterCommandHandler($repository);\n        $handler(new RegisterCommand('test@example.com', 'password'));\n    }\n}\n```\n\n### 7.2.2 Integration Tests\n\nLocated in `tests/Integration/`, these tests:\n- Use real dependencies (database, services)\n- Test the full stack end-to-end\n- Verify actual behavior in production-like environment\n- Extend `KernelTestCase` for Symfony integration\n\n**Example:**\n```php\nfinal class RegisterCommandHandlerTest extends KernelTestCase\n{\n    public function testCommandIsHandledSuccessfully(): void\n    {\n        self::bootKernel();\n        $commandBus = static::getContainer()-\u003eget(MessageBusInterface::class);\n\n        $command = new RegisterCommand('test@example.com', 'password');\n        $commandBus-\u003edispatch($command);\n\n        // Verify database changes\n        $repository = static::getContainer()-\u003eget(UserRepositoryInterface::class);\n        $user = $repository-\u003efindByEmail('test@example.com');\n        $this-\u003eassertNotNull($user);\n    }\n}\n```\n\n### 7.2.3 InMemory Repositories\n\nThe bundle also generates InMemory repository implementations for faster unit testing:\n\n```php\nfinal class InMemoryUserRepository implements UserRepositoryInterface\n{\n    private array $users = [];\n\n    public function save(User $user): void\n    {\n        $this-\u003eusers[$user-\u003egetId()-\u003evalue] = $user;\n    }\n\n    public function all(): array\n    {\n        return array_values($this-\u003eusers);\n    }\n}\n```\n\n**Benefits:**\n- No database setup required\n- Tests run 1000x faster\n- Easy to verify state changes\n- Perfect for TDD\n\n---\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e7.3 Doctrine ORM Integration\u003c/b\u003e\u003c/summary\u003e\n\n## 7.3 Doctrine ORM Integration\n\n### 7.3.1 Pure Domain Entities + YAML Mapping\n\nIn true **Hexagonal Architecture**, the Domain layer must remain **PURE** - completely independent of infrastructure frameworks.\n\nThis bundle generates:\n1. **Domain Entity** (pure PHP, no Doctrine) - in `Domain/Model/`\n2. **Doctrine YAML Mapping** (infrastructure concern) - in `Infrastructure/Persistence/Doctrine/Orm/Mapping/`\n\nThis approach maintains **strict separation of concerns** and follows DDD best practices.\n\n### 7.3.2 Generated Files Structure\n\nWhen you run:\n```bash\nbin/console make:hexagonal:entity user/account User\n```\n\n**Two files are generated:**\n\n**1. Domain Entity (PURE)**\n```php\n\u003c?php\n// src/User/Account/Domain/Model/User.php\n\ndeclare(strict_types=1);\n\nnamespace App\\User\\Account\\Domain\\Model;\n\n/**\n * 👀 PURE Domain Entity - No framework dependencies\n * Doctrine mapping is in:\n * Infrastructure/Persistence/Doctrine/Orm/Mapping/User.orm.yml\n */\nfinal class User\n{\n    private string $id;\n    private string $email;\n    private \\DateTimeImmutable $createdAt;\n\n    public function __construct(\n        string $id,\n        string $email,\n    ) {\n        $this-\u003eid = $id;\n        $this-\u003eemail = $email;\n        $this-\u003ecreatedAt = new \\DateTimeImmutable();\n    }\n\n    public function getId(): string\n    {\n        return $this-\u003eid;\n    }\n\n    // Business logic methods...\n}\n```\n\n**2. Doctrine ORM Mapping (Infrastructure)**\n```yaml\n# src/User/Account/Infrastructure/Persistence/Doctrine/Orm/Mapping/User.orm.yml\n\nApp\\User\\Account\\Domain\\Model\\User:\n    type: entity\n    repositoryClass: App\\User\\Account\\Infrastructure\\Persistence\\Doctrine\\DoctrineUserRepository\n    table: user\n\n    id:\n        id:\n            type: string\n            length: 36\n\n    fields:\n        email:\n            type: string\n            length: 180\n            unique: true\n        createdAt:\n            type: datetime_immutable\n            column: created_at\n```\n\n### 7.3.3 Why YAML Mapping in Infrastructure Layer?\n\nThis is the **correct approach** for true Hexagonal Architecture and DDD:\n\n**🎯 Advantages:**\n- **Pure Domain** - Zero framework dependencies in domain entities\n- **Easy Testing** - No need to mock Doctrine infrastructure\n- **Technology Independence** - Switch ORMs without touching domain code\n- **True Separation** - Persistence is an infrastructure detail, not a domain concern\n- **Follows DDD Principles** - Domain model independent of persistence mechanism\n\n**Configuration Required:**\n\nIn `config/packages/doctrine.yaml`:\n```yaml\ndoctrine:\n    dbal:\n        url: '%env(resolve:DATABASE_URL)%'\n\n    orm:\n        auto_generate_proxy_classes: true\n        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware\n        auto_mapping: true\n        mappings:\n            # Add one mapping per module\n            UserAccount:\n                is_bundle: false\n                type: yml\n                dir: '%kernel.project_dir%/src/User/Account/Infrastructure/Persistence/Doctrine/Orm/Mapping'\n                prefix: 'App\\User\\Account\\Domain\\Model'\n                alias: UserAccount\n\n            # Add more modules as needed:\n            # Product:\n            #     is_bundle: false\n            #     type: yml\n            #     dir: '%kernel.project_dir%/src/Catalog/Product/Infrastructure/Persistence/Doctrine/Orm/Mapping'\n            #     prefix: 'App\\Catalog\\Product\\Domain\\Model'\n            #     alias: Product\n```\n\n### 7.3.4 YAML Mapping Examples\n\nHere are common YAML mapping patterns you'll use:\n\n**Basic Field Types:**\n```yaml\nfields:\n    # String\n    name:\n        type: string\n        length: 255\n\n    # Text (unlimited)\n    description:\n        type: text\n\n    # Numbers\n    age:\n        type: integer\n    price:\n        type: decimal\n        precision: 10\n        scale: 2\n\n    # Boolean\n    isActive:\n        type: boolean\n\n    # Dates\n    createdAt:\n        type: datetime_immutable\n    birthDate:\n        type: date_immutable\n\n    # JSON\n    metadata:\n        type: json\n\n    # Nullable\n    middleName:\n        type: string\n        length: 255\n        nullable: true\n```\n\n**Unique Constraints:**\n```yaml\nfields:\n    email:\n        type: string\n        length: 180\n        unique: true\n```\n\n### 7.3.5 Entity Identity Strategies\n\n**Option 1: UUID (Recommended for DDD)**\n```yaml\nid:\n    id:\n        type: uuid\n        # Doctrine will automatically use UUID type\n```\n\n**Option 2: ULID (Sortable UUID)**\n```yaml\nid:\n    id:\n        type: ulid\n        # Doctrine will automatically use ULID type\n```\n\n**Option 3: String-based UUID**\n```yaml\nid:\n    id:\n        type: string\n        length: 36\n        # Generate UUID in entity constructor\n```\n\n**Option 4: Auto-increment**\n```yaml\nid:\n    id:\n        type: integer\n        generator:\n            strategy: AUTO\n```\n\n### 7.3.6 Associations (Relationships)\n\n**One-to-Many:**\n```yaml\noneToMany:\n    orders:\n        targetEntity: App\\Domain\\Order\\Order\n        mappedBy: user\n        cascade: ['persist', 'remove']\n```\n\n**Many-to-One:**\n```yaml\nmanyToOne:\n    category:\n        targetEntity: App\\Domain\\Category\\Category\n        inversedBy: products\n        joinColumn:\n            name: category_id\n            referencedColumnName: id\n            nullable: false\n```\n\n**Many-to-Many:**\n```yaml\nmanyToMany:\n    tags:\n        targetEntity: App\\Domain\\Tag\\Tag\n        inversedBy: products\n        joinTable:\n            name: product_tag\n            joinColumns:\n                product_id:\n                    referencedColumnName: id\n            inverseJoinColumns:\n                tag_id:\n                    referencedColumnName: id\n```\n\n### 7.3.7 Embedded Value Objects\n\n**Address.orm.yml** (Value Object):\n```yaml\nApp\\Domain\\ValueObject\\Address:\n    type: embeddable\n    fields:\n        street:\n            type: string\n            length: 255\n        city:\n            type: string\n            length: 100\n        zipCode:\n            type: string\n            length: 10\n```\n\n**User.orm.yml** (Entity using embedded):\n```yaml\nApp\\Domain\\Model\\User:\n    type: entity\n    table: user\n    # ... other fields ...\n    embedded:\n        address:\n            class: App\\Domain\\ValueObject\\Address\n            columnPrefix: address_\n```\n\n### 7.3.8 Database Schema Generation\n\nAfter creating/modifying YAML mapping files:\n\n```bash\n# 1. Validate mapping files\nbin/console doctrine:schema:validate\n\n# 2. Generate migration from mapping changes\nbin/console doctrine:migrations:diff\n\n# 3. Review the generated migration in migrations/\n#    Then execute it:\nbin/console doctrine:migrations:migrate\n\n# For development only - direct schema update (skip migrations)\nbin/console doctrine:schema:update --force\n```\n\n### 7.3.9 Complete Reference\n\nFor complete YAML mapping reference, see:\n- [Doctrine YAML Mapping Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/yaml-mapping.html)\n- Generated mapping file template in: `Infrastructure/Persistence/Doctrine/Orm/Mapping/`\n- Configuration guide: `Infrastructure/Persistence/Doctrine/Orm/Mapping/DOCTRINE_CONFIGURATION.md`\n\n---\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e7.4 Doctrine Extensions (Gedmo) - Keep Domain Pure 🎯\u003c/b\u003e\u003c/summary\u003e\n\n## 7.4 Doctrine Extensions (Gedmo) - Keep Domain Pure 🎯\n\nThis bundle generates pure domain entities with YAML mapping, making it **100% compatible** with Doctrine Extensions (Gedmo) **without polluting your domain layer**.\n\n### 7.4.1 Why YAML Mapping for Extensions?\n\n**🌪️ Traditional approach (breaks hexagonal architecture):**\n```php\nuse Gedmo\\Mapping\\Annotation as Gedmo;\n\nclass Post\n{\n    #[Gedmo\\Slug(fields: ['title'])]      // 🌪️ Domain depends on Gedmo!\n    private string $slug;\n\n    #[Gedmo\\Timestampable(on: 'create')]  // 🌪️ Infrastructure concern in Domain!\n    private \\DateTimeInterface $createdAt;\n}\n```\n\n**🎯 Hexagonal approach (domain stays pure):**\n```php\n// Domain entity - PURE PHP\nclass Post\n{\n    private string $slug;           // 🎯 No Gedmo dependency\n    private \\DateTimeInterface $createdAt;\n\n    public function __construct(string $title)\n    {\n        $this-\u003etitle = $title;\n        // slug and createdAt managed automatically by Gedmo via YAML\n    }\n}\n```\n\n```yaml\n# Infrastructure YAML mapping - Configuration separated\nfields:\n    slug:\n        type: string\n        gedmo:\n            slug:\n                fields: [title]\n\n    createdAt:\n        type: datetime_immutable\n        gedmo:\n            timestampable:\n                on: create\n```\n\n### 7.4.2 Installation\n\n```bash\ncomposer require stof/doctrine-extensions-bundle\n```\n\n### 7.4.3 Configuration\n\n**Enable extensions in `config/packages/stof_doctrine_extensions.yaml`:**\n\n```yaml\nstof_doctrine_extensions:\n    default_locale: en_US\n\n    orm:\n        default:\n            sluggable: true           # Auto-generate slugs\n            timestampable: true       # Auto-manage created/updated dates\n            softdeleteable: true      # Soft delete (logical deletion)\n            blameable: true           # Track who created/updated\n            loggable: true            # Entity change history\n            translatable: true        # Multi-language content\n            tree: true                # Nested tree structures\n```\n\n### 7.4.4 Available Extensions with YAML Examples\n\n#### 1️⃣ **Sluggable** - Auto-generate URL-friendly slugs\n\n**Domain Entity:**\n```php\nfinal class Post\n{\n    private string $title;\n    private string $slug;  // Managed by Gedmo\n\n    public function __construct(string $title)\n    {\n        $this-\u003etitle = $title;\n        // No need to manually set slug!\n    }\n\n    public function updateTitle(string $title): void\n    {\n        $this-\u003etitle = $title;\n        // Slug auto-updates when title changes\n    }\n}\n```\n\n**YAML Mapping:**\n```yaml\nApp\\Blog\\Post\\Domain\\Model\\Post:\n    type: entity\n    fields:\n        title:\n            type: string\n            length: 255\n\n        slug:\n            type: string\n            length: 128\n            unique: true\n            gedmo:\n                slug:\n                    fields: [title]         # Generate from title\n                    updatable: true         # Update when title changes\n                    separator: '-'          # Use hyphens\n                    unique: true            # Ensure uniqueness\n```\n\n#### 2️⃣ **Timestampable** - Auto-manage created/updated dates\n\n**Domain Entity:**\n```php\nfinal class Post\n{\n    private \\DateTimeImmutable $createdAt;  // Set automatically\n    private \\DateTimeImmutable $updatedAt;  // Updated automatically\n\n    public function getCreatedAt(): \\DateTimeImmutable\n    {\n        return $this-\u003ecreatedAt;\n    }\n}\n```\n\n**YAML Mapping:**\n```yaml\nfields:\n    createdAt:\n        type: datetime_immutable\n        column: created_at\n        gedmo:\n            timestampable:\n                on: create          # Set when entity is created\n\n    updatedAt:\n        type: datetime_immutable\n        column: updated_at\n        gedmo:\n            timestampable:\n                on: update          # Update on every change\n\n    publishedAt:\n        type: datetime_immutable\n        column: published_at\n        nullable: true\n        gedmo:\n            timestampable:\n                on: change          # Set when specific field changes\n                field: status\n                value: published    # When status becomes 'published'\n```\n\n#### 3️⃣ **SoftDeleteable** - Logical deletion (keep data)\n\n**Domain Entity:**\n```php\nfinal class Post\n{\n    private ?\\DateTimeImmutable $deletedAt;  // Managed by Gedmo\n\n    public function isDeleted(): bool\n    {\n        return $this-\u003edeletedAt !== null;\n    }\n}\n```\n\n**YAML Mapping:**\n```yaml\nApp\\Blog\\Post\\Domain\\Model\\Post:\n    type: entity\n    gedmo:\n        soft_deleteable:\n            field_name: deletedAt    # Field to mark deletion\n            time_aware: false        # Set to true to filter by date\n\n    fields:\n        deletedAt:\n            type: datetime_immutable\n            column: deleted_at\n            nullable: true\n```\n\n**Usage:**\n```php\n// Soft delete (sets deletedAt, doesn't remove from DB)\n$entityManager-\u003eremove($post);\n$entityManager-\u003eflush();\n\n// Soft-deleted entities are automatically excluded from queries\n$posts = $repository-\u003efindAll();  // Excludes deleted posts\n\n// To include deleted entities\n$repository-\u003ecreateQueryBuilder('p')\n    -\u003egetQuery()\n    -\u003esetHint(\n        \\Gedmo\\SoftDeleteable\\Query\\TreeWalker\\SoftDeleteableWalker::HINT_SOFT_DELETED,\n        true\n    );\n```\n\n#### 4️⃣ **Blameable** - Track who created/updated\n\n**Domain Entity:**\n```php\nfinal class Post\n{\n    private string $createdBy;  // User who created\n    private string $updatedBy;  // Last user who updated\n}\n```\n\n**YAML Mapping:**\n```yaml\nfields:\n    createdBy:\n        type: string\n        length: 255\n        column: created_by\n        gedmo:\n            blameable:\n                on: create\n\n    updatedBy:\n        type: string\n        length: 255\n        column: updated_by\n        gedmo:\n            blameable:\n                on: update\n\n    publishedBy:\n        type: string\n        length: 255\n        column: published_by\n        nullable: true\n        gedmo:\n            blameable:\n                on: change\n                field: status\n                value: published\n```\n\n**Configure Blameable Listener:**\n```yaml\n# config/services.yaml\nservices:\n    Gedmo\\Blameable\\BlameableListener:\n        tags:\n            - { name: doctrine.event_subscriber, connection: default }\n        calls:\n            - [ setUserValue, [ '@security.token_storage' ] ]\n```\n\n#### 5️⃣ **Translatable** - Multi-language content\n\n**Domain Entity:**\n```php\nfinal class Post\n{\n    private string $title;      // Translatable\n    private string $content;    // Translatable\n    private string $locale;     // Current locale\n\n    public function setTranslatableLocale(string $locale): void\n    {\n        $this-\u003elocale = $locale;\n    }\n}\n```\n\n**YAML Mapping:**\n```yaml\nApp\\Blog\\Post\\Domain\\Model\\Post:\n    type: entity\n    gedmo:\n        translation:\n            entity: Gedmo\\Translatable\\Entity\\Translation\n            locale: locale\n\n    fields:\n        title:\n            type: string\n            length: 255\n            gedmo:\n                translatable: ~     # This field is translatable\n\n        content:\n            type: text\n            gedmo:\n                translatable: ~\n\n        locale:\n            type: string\n            length: 5\n            gedmo:\n                locale: ~           # Stores current locale\n```\n\n**Usage:**\n```php\n// Create post in English\n$post = new Post('Hello World', 'Content in English');\n$entityManager-\u003epersist($post);\n$entityManager-\u003eflush();\n\n// Add French translation\n$post-\u003esetTranslatableLocale('fr');\n$post-\u003esetTitle('Bonjour le monde');\n$post-\u003esetContent('Contenu en français');\n$entityManager-\u003epersist($post);\n$entityManager-\u003eflush();\n\n// Retrieve in specific language\n$repository-\u003efindTranslationsByLocale($post, 'fr');\n```\n\n#### 6️⃣ **Tree (Nested Set)** - Hierarchical structures\n\n**Domain Entity:**\n```php\nfinal class Category\n{\n    private int $lft;           // Left value\n    private int $lvl;           // Level\n    private int $rgt;           // Right value\n    private ?int $root;         // Root id\n    private ?self $parent;      // Parent category\n    private Collection $children;  // Child categories\n}\n```\n\n**YAML Mapping:**\n```yaml\nApp\\Category\\Domain\\Model\\Category:\n    type: entity\n    gedmo:\n        tree:\n            type: nested         # Use Nested Set algorithm\n\n    fields:\n        name:\n            type: string\n            length: 255\n\n        lft:\n            type: integer\n            gedmo:\n                tree_left: ~\n\n        lvl:\n            type: integer\n            gedmo:\n                tree_level: ~\n\n        rgt:\n            type: integer\n            gedmo:\n                tree_right: ~\n\n        root:\n            type: integer\n            nullable: true\n            gedmo:\n                tree_root: ~\n\n    manyToOne:\n        parent:\n            targetEntity: App\\Category\\Domain\\Model\\Category\n            inversedBy: children\n            joinColumn:\n                name: parent_id\n                referencedColumnName: id\n                onDelete: CASCADE\n            gedmo:\n                tree_parent: ~\n\n    oneToMany:\n        children:\n            targetEntity: App\\Category\\Domain\\Model\\Category\n            mappedBy: parent\n```\n\n**Usage:**\n```php\n// Create tree structure\n$electronics = new Category('Electronics');\n$computers = new Category('Computers');\n$laptops = new Category('Laptops');\n\n$computers-\u003esetParent($electronics);\n$laptops-\u003esetParent($computers);\n\n// Query tree\n$repository-\u003echildrenHierarchy();  // Get full tree\n$repository-\u003egetChildren($electronics);  // Get direct children\n$repository-\u003egetPath($laptops);  // Get path from root\n```\n\n#### 7️⃣ **Loggable** - Entity change history\n\n**Domain Entity:**\n```php\nfinal class Post\n{\n    private string $title;     // Versioned\n    private string $content;   // Versioned\n    // Changes will be logged automatically\n}\n```\n\n**YAML Mapping:**\n```yaml\nApp\\Blog\\Post\\Domain\\Model\\Post:\n    type: entity\n    gedmo:\n        loggable: ~           # Enable logging for this entity\n\n    fields:\n        title:\n            type: string\n            length: 255\n            gedmo:\n                versioned: ~  # Track changes to this field\n\n        content:\n            type: text\n            gedmo:\n                versioned: ~\n```\n\n**Usage:**\n```php\n// Changes are logged automatically\n$post-\u003esetTitle('New Title');\n$entityManager-\u003eflush();\n\n// Retrieve change history\n$logEntries = $entityManager\n    -\u003egetRepository(Gedmo\\Loggable\\Entity\\LogEntry::class)\n    -\u003egetLogEntries($post);\n\nforeach ($logEntries as $log) {\n    echo $log-\u003egetAction();     // create, update, remove\n    echo $log-\u003egetUsername();   // who made the change\n    echo $log-\u003egetLoggedAt();   // when\n    echo $log-\u003egetData();       // what changed\n}\n```\n\n### 7.4.5 Complete Example: Blog Post with Multiple Extensions\n\n**Domain Entity (100% Pure):**\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Blog\\Post\\Domain\\Model;\n\nfinal class Post\n{\n    private string $id;\n    private string $title;\n    private string $slug;                      // Gedmo Sluggable\n    private string $content;\n    private string $status = 'draft';\n    private \\DateTimeImmutable $createdAt;     // Gedmo Timestampable\n    private \\DateTimeImmutable $updatedAt;     // Gedmo Timestampable\n    private ?\\DateTimeImmutable $publishedAt = null;  // Gedmo Timestampable\n    private ?\\DateTimeImmutable $deletedAt = null;    // Gedmo SoftDeleteable\n    private string $createdBy;                 // Gedmo Blameable\n    private string $updatedBy;                 // Gedmo Blameable\n\n    public function __construct(string $id, string $title, string $content)\n    {\n        $this-\u003eid = $id;\n        $this-\u003etitle = $title;\n        $this-\u003econtent = $content;\n        // All Gedmo fields are managed automatically!\n    }\n\n    public function publish(): void\n    {\n        $this-\u003estatus = 'published';\n        // publishedAt will be set automatically by Gedmo\n    }\n\n    public function updateContent(string $title, string $content): void\n    {\n        $this-\u003etitle = $title;\n        $this-\u003econtent = $content;\n        // slug and updatedAt will be updated automatically\n    }\n\n    // Getters only - no setters for Gedmo-managed fields\n    public function getSlug(): string { return $this-\u003eslug; }\n    public function getCreatedAt(): \\DateTimeImmutable { return $this-\u003ecreatedAt; }\n    public function isDeleted(): bool { return $this-\u003edeletedAt !== null; }\n}\n```\n\n**YAML Mapping (Infrastructure Configuration):**\n```yaml\n# src/Blog/Post/Infrastructure/Persistence/Doctrine/Orm/Mapping/Post.orm.yml\n\nApp\\Blog\\Post\\Domain\\Model\\Post:\n    type: entity\n    repositoryClass: App\\Blog\\Post\\Infrastructure\\Persistence\\Doctrine\\DoctrinePostRepository\n    table: post\n\n    gedmo:\n        soft_deleteable:\n            field_name: deletedAt\n        loggable: ~\n\n    id:\n        id:\n            type: string\n            length: 36\n\n    fields:\n        title:\n            type: string\n            length: 255\n            gedmo:\n                versioned: ~\n\n        slug:\n            type: string\n            length: 128\n            unique: true\n            gedmo:\n                slug:\n                    fields: [title]\n                    updatable: true\n                    unique: true\n\n        content:\n            type: text\n            gedmo:\n                versioned: ~\n\n        status:\n            type: string\n            length: 20\n\n        createdAt:\n            type: datetime_immutable\n            column: created_at\n            gedmo:\n                timestampable:\n                    on: create\n\n        updatedAt:\n            type: datetime_immutable\n            column: updated_at\n            gedmo:\n                timestampable:\n                    on: update\n\n        publishedAt:\n            type: datetime_immutable\n            column: published_at\n            nullable: true\n            gedmo:\n                timestampable:\n                    on: change\n                    field: status\n                    value: published\n\n        deletedAt:\n            type: datetime_immutable\n            column: deleted_at\n            nullable: true\n\n        createdBy:\n            type: string\n            length: 255\n            column: created_by\n            gedmo:\n                blameable:\n                    on: create\n\n        updatedBy:\n            type: string\n            length: 255\n            column: updated_by\n            gedmo:\n                blameable:\n                    on: update\n```\n\n### 7.4.6 Benefits of YAML-based Extensions\n\n| Benefit | Description |\n|---------|-------------|\n| 🎯 **Pure Domain** | Zero framework/library dependencies in domain entities |\n| 🎯 **Technology Independence** | Easy to switch from Gedmo to another solution |\n| 🎯 **Easy Testing** | Domain entities remain simple POPOs (Plain Old PHP Objects) |\n| 🎯 **Clear Separation** | Infrastructure concerns stay in Infrastructure layer |\n| 🎯 **True Hexagonal** | Respects dependency inversion principle |\n| 🎯 **All Extensions Work** | Full compatibility with all Gedmo extensions |\n\n### 7.4.7 References\n\n- [StofDoctrineExtensionsBundle Documentation](https://github.com/stof/StofDoctrineExtensionsBundle)\n- [Doctrine Extensions (Gedmo) Documentation](https://github.com/doctrine-extensions/DoctrineExtensions/tree/main/doc)\n- [YAML Mapping Examples](https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/yaml_mapping.md)\n\n---\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e7.5 Infrastructure Organization 🏗️\u003c/b\u003e\u003c/summary\u003e\n\n## 7.5 Infrastructure Organization 🏗️\n\nThe Infrastructure layer contains **Secondary Adapters** - technical implementations of ports (interfaces) defined in the Domain.\n\n### 7.5.1 Recommended Structure\n\n```\nInfrastructure/\n├── Persistence/              ← Database adapters\n│   ├── Doctrine/            ← Doctrine ORM implementation\n│   │   ├── DoctrineUserRepository.php\n│   │   └── Orm/\n│   │       └── Mapping/\n│   │           └── User.orm.yml\n│   └── InMemory/            ← In-memory for testing (optional)\n│       └── InMemoryUserRepository.php\n├── Messaging/               ← Async/Queue adapters\n│   ├── Handler/             ← Message handlers (Symfony Messenger)\n│   │   └── SendWelcomeEmailHandler.php\n│   └── Publisher/           ← Event publishers\n│       └── DomainEventPublisher.php\n├── Email/                   ← Email service adapters\n│   ├── SymfonyMailerService.php\n│   └── SendGridService.php\n├── Http/                    ← HTTP client adapters (external APIs)\n│   ├── StripePaymentClient.php\n│   └── GoogleMapsClient.php\n├── Cache/                   ← Cache adapters\n│   └── RedisCacheAdapter.php\n├── FileStorage/             ← File storage adapters\n│   ├── LocalFilesystemStorage.php\n│   └── S3Storage.php\n└── EventSubscriber/         ← Infrastructure event subscribers\n    └── LoggingSubscriber.php\n```\n\n### 7.5.2 Persistence Layer (Doctrine)\n\n**Generated automatically by:** `make:hexagonal:repository`\n\n```bash\nbin/console make:hexagonal:repository user/account User\n```\n\n**Generates:**\n- 🎯 Port: `Domain/Port/UserRepositoryInterface.php`\n- 🎯 Adapter: `Infrastructure/Persistence/Doctrine/DoctrineUserRepository.php`\n- 🎯 Mapping: `Infrastructure/Persistence/Doctrine/Orm/Mapping/User.orm.yml`\n\n**Example - Domain Port:**\n```php\n// src/Module/User/Account/Domain/Port/UserRepositoryInterface.php\nnamespace App\\Module\\User\\Account\\Domain\\Port;\n\ninterface UserRepositoryInterface\n{\n    public function save(User $user): void;\n    public function findById(string $id): ?User;\n}\n```\n\n**Example - Infrastructure Adapter:**\n```php\n// src/Module/User/Account/Infrastructure/Persistence/Doctrine/DoctrineUserRepository.php\nnamespace App\\Module\\User\\Account\\Infrastructure\\Persistence\\Doctrine;\n\nuse Doctrine\\ORM\\EntityManagerInterface;\n\nfinal class DoctrineUserRepository implements UserRepositoryInterface\n{\n    public function __construct(private EntityManagerInterface $em) {}\n\n    public function save(User $user): void\n    {\n        $this-\u003eem-\u003epersist($user);\n        $this-\u003eem-\u003eflush();\n    }\n}\n```\n\n### 7.5.3 Messaging Layer (Async/Queue) ⚡\n\n**NEW in this bundle!** Generate async message handlers for background processing.\n\n**Generated by:** `make:hexagonal:message-handler`\n\n```bash\n# Generate message handler only\nbin/console make:hexagonal:message-handler user/account SendWelcomeEmail\n\n# Generate handler + message class\nbin/console make:hexagonal:message-handler user/account SendWelcomeEmail --with-message\n```\n\n**Generates:**\n- 🎯 Handler: `Infrastructure/Messaging/Handler/SendWelcomeEmailHandler.php`\n- 🎯 Message: `Application/Message/SendWelcomeEmailMessage.php` (with `--with-message`)\n\n**Example - Message (DTO):**\n```php\n// src/Module/User/Account/Application/Message/SendWelcomeEmailMessage.php\nnamespace App\\Module\\User\\Account\\Application\\Message;\n\nfinal readonly class SendWelcomeEmailMessage\n{\n    public function __construct(\n        public string $userId,\n        public string $email,\n        public string $name,\n    ) {\n    }\n}\n```\n\n**Example - Message Handler:**\n```php\n// src/Module/User/Account/Infrastructure/Messaging/Handler/SendWelcomeEmailHandler.php\nnamespace App\\Module\\User\\Account\\Infrastructure\\Messaging\\Handler;\n\nuse Symfony\\Component\\Messenger\\Attribute\\AsMessageHandler;\n\n#[AsMessageHandler]\nfinal readonly class SendWelcomeEmailHandler\n{\n    public function __construct(\n        private EmailServiceInterface $emailService,\n        private LoggerInterface $logger,\n    ) {\n    }\n\n    public function __invoke(SendWelcomeEmailMessage $message): void\n    {\n        $this-\u003eemailService-\u003esendWelcomeEmail(\n            to: $message-\u003eemail,\n            name: $message-\u003ename\n        );\n\n        $this-\u003elogger-\u003einfo('Welcome email sent', [\n            'user_id' =\u003e $message-\u003euserId,\n        ]);\n    }\n}\n```\n\n**Dispatch message from UseCase:**\n```php\n// src/Module/User/Account/Application/UseCase/CreateUserUseCase.php\nuse Symfony\\Component\\Messenger\\MessageBusInterface;\n\nfinal readonly class CreateUserUseCase\n{\n    public function __construct(\n        private UserRepositoryInterface $repository,\n        private MessageBusInterface $messageBus,  // Inject message bus\n    ) {\n    }\n\n    public function execute(CreateUserCommand $command): void\n    {\n        $user = new User(...);\n        $this-\u003erepository-\u003esave($user);\n\n        // Dispatch async message\n        $this-\u003emessageBus-\u003edispatch(new SendWelcomeEmailMessage(\n            userId: $user-\u003egetId(),\n            email: $user-\u003egetEmail(),\n            name: $user-\u003egetName(),\n        ));\n    }\n}\n```\n\n**Configure Messenger (config/packages/messenger.yaml):**\n```yaml\nframework:\n    messenger:\n        transports:\n            async: '%env(MESSENGER_TRANSPORT_DSN)%'\n\n        routing:\n            # Route all messages to async transport\n            'App\\Module\\User\\Account\\Application\\Message\\SendWelcomeEmailMessage': async\n```\n\n**Start worker:**\n```bash\nbin/console messenger:consume async\n```\n\n### 7.5.4 Email/Http/Cache/FileStorage Adapters\n\nThese adapters are **too specific** to auto-generate. Create them manually following the Port \u0026 Adapter pattern.\n\n**Example - Email Adapter:**\n\n**1. Define Port (Domain):**\n```php\n// src/Module/User/Account/Domain/Port/EmailServiceInterface.php\nnamespace App\\Module\\User\\Account\\Domain\\Port;\n\ninterface EmailServiceInterface\n{\n    public function sendWelcomeEmail(string $to, string $name): void;\n}\n```\n\n**2. Implement Adapter (Infrastructure):**\n```php\n// src/Module/User/Account/Infrastructure/Email/SymfonyMailerService.php\nnamespace App\\Module\\User\\Account\\Infrastructure\\Email;\n\nuse Symfony\\Component\\Mailer\\MailerInterface;\nuse Symfony\\Component\\Mime\\Email;\n\nfinal readonly class SymfonyMailerService implements EmailServiceInterface\n{\n    public function __construct(private MailerInterface $mailer) {}\n\n    public function sendWelcomeEmail(string $to, string $name): void\n    {\n        $email = (new Email())\n            -\u003eto($to)\n            -\u003esubject('Welcome!')\n            -\u003ehtml(\"\u003ch1\u003eWelcome $name!\u003c/h1\u003e\");\n\n        $this-\u003emailer-\u003esend($email);\n    }\n}\n```\n\n**3. Configure Service:**\n```yaml\n# config/services.yaml\nservices:\n    App\\Module\\User\\Account\\Domain\\Port\\EmailServiceInterface:\n        class: App\\Module\\User\\Account\\Infrastructure\\Email\\SymfonyMailerService\n```\n\n**Benefits:**\n- 🎯 Easy to switch from SymfonyMailer to SendGrid (just change config)\n- 🎯 Easy to mock in tests\n- 🎯 Domain doesn't know about Symfony\n\n---\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e7.6 Shared Kernel Structure 🔄\u003c/b\u003e\u003c/summary\u003e\n\n## 7.6 Shared Kernel Structure 🔄\n\nThe **Shared Kernel** contains code reused across multiple modules (bounded contexts).\n\n### 7.6.1 Recommended Structure\n\n```\nsrc/\n├── Module/                   ← Modular architecture (bounded contexts)\n│   ├── User/\n│   ├── Blog/\n│   └── Order/\n└── Shared/                   ← Shared across modules\n    ├── Domain/\n    │   ├── ValueObject/      ← Shared value objects\n    │   │   ├── Uuid.php\n    │   │   ├── Email.php\n    │   │   ├── DateRange.php\n    │   │   └── Money.php\n    │   ├── Exception/        ← Shared domain exceptions\n    │   │   ├── NotFoundException.php\n    │   │   ├── ValidationException.php\n    │   │   └── DomainException.php\n    │   └── Event/            ← Shared domain events (optional)\n    │       └── DomainEventInterface.php\n    ├── Application/\n    │   ├── Bus/              ← Bus abstractions\n    │   │   ├── CommandBusInterface.php\n    │   │   ├── QueryBusInterface.php\n    │   │   └── EventBusInterface.php\n    │   └── UseCase/          ← Shared use case traits\n    │       └── TransactionalTrait.php\n    └── Infrastructure/\n        ├── Persistence/\n        │   └── Migrations/   ← Doctrine migrations (centralized)\n        │       ├── Version20250106120000.php\n        │       └── Version20250106130000.php\n        ├── Bus/\n        │   ├── SymfonyCommandBus.php\n        │   ├── SymfonyQueryBus.php\n        │   └── SymfonyEventBus.php\n        └── Doctrine/\n            └── Types/        ← Custom Doctrine types\n                ├── UuidType.php\n                └── MoneyType.php\n```\n\n### 7.6.2 Shared Value Objects\n\n**Example - Shared Email Value Object:**\n\n```php\n// src/Shared/Domain/ValueObject/Email.php\nnamespace App\\Shared\\Domain\\ValueObject;\n\nfinal readonly class Email\n{\n    private string $value;\n\n    public function __construct(string $value)\n    {\n        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {\n            throw new \\InvalidArgumentException(\"Invalid email: $value\");\n        }\n\n        $this-\u003evalue = strtolower($value);\n    }\n\n    public function getValue(): string\n    {\n        return $this-\u003evalue;\n    }\n\n    public function __toString(): string\n    {\n        return $this-\u003evalue;\n    }\n}\n```\n\n**Usage in modules:**\n```php\n// Module User\nuse App\\Shared\\Domain\\ValueObject\\Email;\n\nfinal class User\n{\n    public function __construct(\n        private Email $email,  // Reuse shared Email VO\n    ) {\n    }\n}\n\n// Module Newsletter\nuse App\\Shared\\Domain\\ValueObject\\Email;\n\nfinal class Subscriber\n{\n    public function __construct(\n        private Email $email,  // Same Email VO!\n    ) {\n    }\n}\n```\n\n### 7.6.3 Shared Exceptions\n\n```php\n// src/Shared/Domain/Exception/NotFoundException.php\nnamespace App\\Shared\\Domain\\Exception;\n\nclass NotFoundException extends \\DomainException\n{\n    public static function forResource(string $resource, string $id): self\n    {\n        return new self(\"$resource with ID '$id' not found\");\n    }\n}\n\n// Usage:\nthrow NotFoundException::forResource('User', $userId);\nthrow NotFoundException::forResource('Post', $postId);\n```\n\n### 7.6.4 Doctrine Migrations (Centralized)\n\n**Configure Doctrine Migrations in Shared:**\n\n```yaml\n# config/packages/doctrine_migrations.yaml\ndoctrine_migrations:\n    migrations_paths:\n        'App\\Shared\\Infrastructure\\Persistence\\Migrations': 'src/Shared/Infrastructure/Persistence/Migrations'\n\n    organize_migrations: false\n    all_or_nothing: true\n```\n\n**Generate migrations:**\n```bash\nbin/console make:migration\n```\n\n**Generated in:**\n```\nsrc/Shared/Infrastructure/Persistence/Migrations/Version20250106120000.php\n```\n\n**Why centralized migrations?**\n- 🎯 Single source of truth for database schema\n- 🎯 Migrations execute in order (no conflicts between modules)\n- 🎯 Easier to track schema evolution\n- 🌪️ Modules are slightly coupled through DB schema (acceptable trade-off)\n\n### 7.6.5 When to Use Shared vs Module\n\n| Component | Shared | Module | Reasoning |\n|-----------|--------|--------|-----------|\n| **Email VO** | 🎯 | 🌪️ | Same validation everywhere |\n| **Money VO** | 🎯 | 🌪️ | Same currency logic everywhere |\n| **Uuid VO** | 🎯 | 🌪️ | Generic identifier |\n| **UserException** | 🌪️ | 🎯 | Specific to User module |\n| **User Entity** | 🌪️ | 🎯 | Bounded context specific |\n| **NotFoundException** | 🎯 | 🌪️ | Generic exception |\n| **Migrations** | 🎯 | 🌪️ | Database-wide changes |\n| **Bus Interfaces** | 🎯 | 🌪️ | Application-wide infrastructure |\n\n**Golden Rule:**\n\u003e If 3+ modules need the same code → Move to Shared\n\u003e If only 1-2 modules need it → Keep in Module\n\n### 7.6.6 Benefits of Shared Kernel\n\n| Benefit | Description |\n|---------|-------------|\n| 🎯 **DRY Principle** | Avoid duplicating Email, Uuid, Money across modules |\n| 🎯 **Consistency** | Same validation logic everywhere |\n| 🎯 **Maintainability** | Fix once, applies everywhere |\n| 👀 **Coupling** | Modules depend on Shared (acceptable trade-off) |\n\n### 7.6.7 References\n\n- [Shared Kernel Pattern (DDD)](https://martinfowler.com/bliki/BoundedContext.html)\n- [Symfony Doctrine Migrations](https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html)\n\n---\n\n\u003c/details\u003e\n\n## 7. Best Practices\n\n### 7.1 Best Practices \u0026 Design Principles\n\n**📚 Complete Documentation:**\n- [**Best Practices Guide**](https://ahmed-bhs.github.io/hexagonal-maker-bundle/advanced/) - Architecture patterns and implementation guidelines\n- [**SOLID Principles**](https://ahmed-bhs.github.io/hexagonal-maker-bundle/SOLID.md) - How hexagonal architecture enforces SOLID principles\n- [**Domain vs Application Logic**](https://ahmed-bhs.github.io/hexagonal-maker-bundle/advanced/domain-vs-application.html) - Decision guide for business logic placement\n- [**Error Handling Strategy**](https://ahmed-bhs.github.io/hexagonal-maker-bundle/advanced/error-handling-strategy.html) - Exception handling best practices\n- [**Anti-Patterns to Avoid**](https://ahmed-bhs.github.io/hexagonal-maker-bundle/advanced/anti-patterns-pitfalls.html) - Common mistakes and how to avoid them\n\n**Quick summary:**\n- Keep Domain pure (zero framework dependencies)\n- Use Value Objects (immutable with `readonly`)\n- CQRS separation (Commands change state, Queries read data)\n- Port/Adapter pattern (interfaces in domain, implementations in infrastructure)\n- Factories for complex creation\n\nSee [ARCHITECTURE-EN.md - Best Practices](ARCHITECTURE-EN.md#7-best-practices) | [ARCHITECTURE.md - Bonnes pratiques (FR)](ARCHITECTURE.md#7-bonnes-pratiques) for detailed best practices with code examples.\n\n---\n\n## 8. Additional Resources\n\n### Documentation\n- [Complete Architecture Guide](ARCHITECTURE-EN.md) | [Guide Complet d'Architecture (FR)](ARCHITECTURE.md) - Detailed explanation of hexagonal architecture concepts with diagrams\n- [SOLID Principles Guide](SOLID-EN.md) | [Guide des Principes SOLID (FR)](SOLID.md) - How hexagonal architecture respects SOLID principles\n- [Practical Examples](EXAMPLES-EN.md) | [Exemples Pratiques (FR)](EXAMPLES.md) - Complete real-world examples with full code\n\n### Learn More\n- [Doctrine YAML Mapping Reference](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/yaml-mapping.html)\n- [Hexagonal Architecture (Alistair Cockburn)](https://alistair.cockburn.us/hexagonal-architecture/)\n- [Domain-Driven Design (Eric Evans)](https://www.domainlanguage.com/ddd/)\n\n---\n\n## 9. License\n\nThis software is published under the [MIT License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahmed-bhs%2Fhexagonal-maker-bundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fahmed-bhs%2Fhexagonal-maker-bundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahmed-bhs%2Fhexagonal-maker-bundle/lists"}