|
| 1 | +# Structure of the Core Submodule |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +Maps what lives inside the Core submodule — its five modules and the kind of code each one holds — and shows where the boundary falls between Core and the applications that include it. |
| 6 | + |
| 7 | +## Details |
| 8 | + |
| 9 | +[Core](introduction.md) is not a single directory of loose classes. |
| 10 | +It is organized into five modules, each a PSR-4 namespace mapped under `src/Core/src/`, so the shared codebase is grouped by domain rather than by file type. |
| 11 | + |
| 12 | +Every application that includes the submodule gets all five, and every application sees exactly the same definitions. |
| 13 | + |
| 14 | +```text |
| 15 | + ┌───────────┐ ┌───────────┐ ┌───────────┐ |
| 16 | + │ API │ │ Admin │ │ Queue │ |
| 17 | + │ handlers │ │ templates │ │ consumers │ |
| 18 | + │ HAL, HTTP │ │ forms, UI │ │ workers │ |
| 19 | + └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ |
| 20 | + │ │ │ |
| 21 | + └───────────────┼───────────────┘ |
| 22 | + │ each includes Core |
| 23 | + ▼ |
| 24 | + ┌─────────────────────┐ |
| 25 | + │ Core │ |
| 26 | + │ entities · repos │ |
| 27 | + │ enums · DBAL types │ |
| 28 | + └──────────┬──────────┘ |
| 29 | + ▼ |
| 30 | + ┌─────────────────┐ |
| 31 | + │ database │ |
| 32 | + └─────────────────┘ |
| 33 | +``` |
| 34 | + |
| 35 | +Note that every arrow points one way. |
| 36 | +The applications know about Core; Core knows nothing about any application. |
| 37 | +Core contains no handler, no template, no route and no middleware — which is what makes it safe to include everywhere. |
| 38 | + |
| 39 | +### The five modules |
| 40 | + |
| 41 | +| Module | Namespace | Holds | |
| 42 | +| --- | --- | --- | |
| 43 | +| App | `Core\App` | The base entity and its traits, the abstract repository, DBAL enum and UUID types, table-prefix and entity-listener wiring, data fixtures, shared services (`MailService`, `IpService`), the paginator helper and shared messages | |
| 44 | +| Admin | `Core\Admin` | The `Admin`, `AdminRole`, `AdminLogin` and `AdminIdentity` entities with their repositories, plus the admin role and status enums | |
| 45 | +| User | `Core\User` | The `User`, `UserDetail`, `UserAvatar`, `UserRole` and `UserResetPassword` entities with their repositories, the user status and role enums, and the avatar event listener | |
| 46 | +| Security | `Core\Security` | OAuth2 persistence — the access token, refresh token, auth code, client and scope entities, plus the repositories the authorization server requires | |
| 47 | +| Setting | `Core\Setting` | The `Setting` entity, its identifier enum and repository, for platform-wide configuration kept in the database | |
| 48 | + |
| 49 | +`Core\App` is the one module that is not a domain in its own right. |
| 50 | +It holds the pieces the other four build on — the abstract entity every entity extends, the abstract repository every repository extends, and the Doctrine infrastructure that makes UUID identifiers and enum columns work. |
| 51 | + |
| 52 | +### How the modules are registered |
| 53 | + |
| 54 | +Each module ships its own [ConfigProvider](../config-provider/introduction.md), and every application registers all five in `config/config.php`: |
| 55 | + |
| 56 | +```php |
| 57 | +// Dotkernel modules |
| 58 | +Core\Admin\ConfigProvider::class, |
| 59 | +Core\App\ConfigProvider::class, |
| 60 | +Core\Security\ConfigProvider::class, |
| 61 | +Core\Setting\ConfigProvider::class, |
| 62 | +Core\User\ConfigProvider::class, |
| 63 | +``` |
| 64 | + |
| 65 | +This is why including the submodule is enough to get the whole domain layer: the entities, repositories and DBAL types register themselves, exactly as they do in every other application on the platform. |
| 66 | + |
| 67 | +The PSR-4 map in `composer.json` points at the submodule path, so it needs no change when a plain `src/Core` folder becomes a submodule mounted at the same location: |
| 68 | + |
| 69 | +```json |
| 70 | +"autoload": { |
| 71 | + "psr-4": { |
| 72 | + "Core\\Admin\\": "src/Core/src/Admin/src/", |
| 73 | + "Core\\App\\": "src/Core/src/App/src/", |
| 74 | + "Core\\Security\\": "src/Core/src/Security/src/", |
| 75 | + "Core\\Setting\\": "src/Core/src/Setting/src/", |
| 76 | + "Core\\User\\": "src/Core/src/User/src/" |
| 77 | + } |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +### Where the boundary falls |
| 82 | + |
| 83 | +The question that comes up on almost every feature is whether the new code belongs in Core or in the application. |
| 84 | +The dividing line is delivery: Core describes what the data *is*, the application describes how it is *delivered*. |
| 85 | + |
| 86 | +```text |
| 87 | + application (API / Admin / Queue) |
| 88 | + ┌──────────────────────────────────────────────────┐ |
| 89 | + │ Handler / Consumer HTTP or message entry │ |
| 90 | + │ InputFilter / Form validates this interface │ |
| 91 | + │ Service this application's flow │ |
| 92 | + └────────────────────────┬─────────────────────────┘ |
| 93 | + │ depends on |
| 94 | + Core ▼ |
| 95 | + ┌──────────────────────────────────────────────────┐ |
| 96 | + │ Service (shared) MailService, IpService │ |
| 97 | + │ Repository querying and persistence │ |
| 98 | + │ Entity + Enum the model itself │ |
| 99 | + └──────────────────────────────────────────────────┘ |
| 100 | +``` |
| 101 | + |
| 102 | +Use this as a checklist: |
| 103 | + |
| 104 | +- **Belongs in Core** — entities and the state changes intrinsic to them, repositories, enums, DBAL types, data fixtures, Doctrine listeners and factories, and any service that behaves identically no matter which application calls it. |
| 105 | +- **Belongs in the application** — handlers and consumers, middleware, routes, input filters and forms, templates, HAL resources, OpenAPI annotations, and the services that express that application's own workflows. |
| 106 | +- **Never in Core** — anything that imports a PSR-7 message, a template renderer, a session, a HAL type or a Messenger envelope. |
| 107 | +If Core would need to know how the request arrived, the code is on the wrong side of the line. |
| 108 | + |
| 109 | +The service layer is the case worth understanding properly, because it sits on both sides: shared services such as `MailService` live in Core, while application-specific ones do not. |
| 110 | +API and Admin each have their own `UserService`, both operating on the same `Core\User\Entity\User` through the same `Core\User\Repository\UserRepository`. |
| 111 | +That is deliberate rather than duplication — the two applications present the same operation very differently while the model underneath stays single. |
| 112 | +[The Service Layer](../services.md) covers the reasoning in full. |
| 113 | + |
| 114 | +### Core owns the schema |
| 115 | + |
| 116 | +Because the entities live in Core, Doctrine generates migrations from Core's mappings. |
| 117 | +That makes the schema a shared asset, and it needs a single owner. |
| 118 | + |
| 119 | +Decide once which application is responsible for generating and running migrations — normally the API — and keep it there. |
| 120 | +Two applications generating migrations independently against the same mappings will produce conflicting histories, and reconciling them after the fact is considerably harder than agreeing the convention up front. |
| 121 | + |
| 122 | +## FAQ |
| 123 | + |
| 124 | +**Q: How many modules does Core contain?** |
| 125 | + |
| 126 | +A: Five — `Core\App`, `Core\Admin`, `Core\User`, `Core\Security` and `Core\Setting`, each mapped under `src/Core/src/`. |
| 127 | + |
| 128 | +**Q: What is `Core\App` for, if it isn't a domain?** |
| 129 | + |
| 130 | +A: It holds the foundations the other modules build on: the abstract entity and repository, the DBAL enum and UUID types, the Doctrine wiring, data fixtures and the shared services. |
| 131 | + |
| 132 | +**Q: Do I have to register each Core module separately?** |
| 133 | + |
| 134 | +A: Yes. All five ConfigProviders are listed in each application's `config/config.php`, which is what makes the entities and DBAL types available. |
| 135 | + |
| 136 | +**Q: How do I decide whether new code goes in Core or in my application?** |
| 137 | + |
| 138 | +A: Ask whether it describes the data or the delivery. Entities, repositories, enums and DBAL types describe the data and belong in Core; handlers, templates, input filters and application-specific services describe delivery and stay in the application. |
| 139 | + |
| 140 | +**Q: Why do API and Admin each have their own `UserService` if Core is meant to prevent duplication?** |
| 141 | + |
| 142 | +A: Because they are different workflows over the same model. Both use `Core\User\Entity\User` and `Core\User\Repository\UserRepository`, so the data has one definition even though the two applications present it differently. |
| 143 | + |
| 144 | +**Q: Which application should run the Doctrine migrations?** |
| 145 | + |
| 146 | +A: One of them, chosen deliberately — usually the API. Because Core owns the entities, migrations generated independently by two applications will conflict. |
| 147 | + |
| 148 | +**Q: Does the `composer.json` autoload section change when Core becomes a submodule?** |
| 149 | + |
| 150 | +A: No. The submodule is mounted at the same `src/Core` path, so the PSR-4 map continues to resolve. Run `composer dump-autoload` after the switch. |
| 151 | + |
| 152 | +## See also |
| 153 | + |
| 154 | +- [Introduction](introduction.md) |
| 155 | +- [Creating a Core Submodule](creation.md) |
| 156 | +- [Using the Core Submodule](usage.md) |
| 157 | +- [The Service Layer](../services.md) |
| 158 | +- [Architecture Overview](../architecture.md) |
0 commit comments