|
| 1 | +# AGENTS.md -- Berlioz Framework |
| 2 | + |
| 3 | +> This file provides architectural context for AI coding assistants (Claude Code, Copilot, Cursor, etc.) |
| 4 | +> and human contributors. It describes the project structure, conventions, and key patterns to avoid |
| 5 | +> unnecessary codebase exploration on every interaction. |
| 6 | +
|
| 7 | +## Overview |
| 8 | + |
| 9 | +**Berlioz Framework** is a slim PHP framework for web applications, APIs, and CLI tools. |
| 10 | +This repository is a **monorepo** containing all official Berlioz packages. Sub-repositories |
| 11 | +(e.g., `berlioz/core`, `berlioz/router`) are **read-only mirrors** synchronized automatically |
| 12 | +via subsplit. All development, issues, and pull requests happen here. |
| 13 | + |
| 14 | +- **Website:** https://getberlioz.com |
| 15 | +- **PHP:** >= 8.2 |
| 16 | +- **License:** MIT |
| 17 | +- **PSR compliance:** PSR-4, PSR-7, PSR-11, PSR-12, PSR-14, PSR-16, PSR-17, PSR-18 |
| 18 | + |
| 19 | +## Monorepo Structure |
| 20 | + |
| 21 | +``` |
| 22 | +. |
| 23 | +├── src/ # Source code -- one subdirectory per package |
| 24 | +│ ├── Cli/Core/ # berlioz/cli-core -- CLI application core |
| 25 | +│ ├── Config/ # berlioz/config -- Configuration management |
| 26 | +│ ├── Core/ # berlioz/core -- Framework kernel |
| 27 | +│ ├── EventManager/ # berlioz/event-manager -- Event dispatcher (PSR-14) |
| 28 | +│ ├── FlashBag/ # berlioz/flash-bag -- Flash messages |
| 29 | +│ ├── Form/ # berlioz/form -- HTML form handling |
| 30 | +│ ├── HtmlSelector/ # berlioz/html-selector -- CSS selector queries on HTML |
| 31 | +│ ├── Http/ |
| 32 | +│ │ ├── Client/ # berlioz/http-client -- HTTP client (PSR-18) |
| 33 | +│ │ ├── Core/ # berlioz/http-core -- HTTP application core |
| 34 | +│ │ └── Message/ # berlioz/http-message -- HTTP messages (PSR-7/PSR-17) |
| 35 | +│ ├── Mailer/ # berlioz/mailer -- Email sending (SMTP/mail) |
| 36 | +│ ├── Package/ |
| 37 | +│ │ ├── Hector/ # berlioz/hector-package -- Hector ORM integration |
| 38 | +│ │ ├── QueueManager/ # berlioz/queue-manager-package -- Queue integration |
| 39 | +│ │ └── Twig/ # berlioz/twig-package -- Twig template integration |
| 40 | +│ ├── QueueManager/ # berlioz/queue-manager -- Background job processing |
| 41 | +│ ├── Router/ # berlioz/router -- HTTP routing |
| 42 | +│ └── ServiceContainer/ # berlioz/service-container -- DI container (PSR-11) |
| 43 | +├── tests/ # Test suites -- mirrors src/ structure |
| 44 | +├── bin/ # Release tooling scripts |
| 45 | +├── composer.json # Root composer -- defines all packages |
| 46 | +├── phpunit.xml.dist # PHPUnit configuration (17 test suites) |
| 47 | +├── rector.php # Rector automated refactoring config |
| 48 | +└── config.subsplit-publish.json # Subsplit configuration for read-only mirrors |
| 49 | +``` |
| 50 | + |
| 51 | +Each package under `src/` contains its own `CHANGELOG.md`, `composer.json`, `LICENSE`, and `README.md`. |
| 52 | + |
| 53 | +## Package Architecture |
| 54 | + |
| 55 | +### Dependency Layers |
| 56 | + |
| 57 | +``` |
| 58 | + ┌─────────────┐ ┌────────────┐ |
| 59 | + │ Http/Core │ │ Cli/Core │ ← Application layer |
| 60 | + └──────┬──────┘ └──────┬─────┘ |
| 61 | + │ │ |
| 62 | + ┌────────────┼─────────────────┤ |
| 63 | + │ │ │ |
| 64 | + ┌─────┴──────┐ ┌──┴───┐ ┌──────────┴────────────┐ |
| 65 | + │ Router │ │ Core │ │ Package/* (Twig, │ ← Framework layer |
| 66 | + └────────────┘ └──┬───┘ │ Hector, QueueManager) │ |
| 67 | + │ └───────────────────────┘ |
| 68 | + ┌─────────────┼───────────────┐ |
| 69 | + │ │ │ |
| 70 | + ┌──────┴──────┐ ┌────┴──────┐ ┌──────┴───────┐ |
| 71 | + │ Config │ │ Service │ │ EventManager │ ← Foundation layer |
| 72 | + └─────────────┘ │ Container │ └──────────────┘ |
| 73 | + └───────────┘ |
| 74 | +
|
| 75 | + ┌────────────────┐ ┌──────────────┐ ┌─────────┐ ┌──────────────┐ |
| 76 | + │ Http/Message │ │ Http/Client │ │ Mailer │ │ QueueManager │ ← Standalone |
| 77 | + │ Form, FlashBag │ │ HtmlSelector │ └─────────┘ └──────────────┘ components |
| 78 | + └────────────────┘ └──────────────┘ |
| 79 | +``` |
| 80 | + |
| 81 | +### Core (`src/Core/`) |
| 82 | + |
| 83 | +The framework kernel. The `Core` class holds all components and orchestrates the boot sequence: |
| 84 | + |
| 85 | +**Boot sequence:** Core constructor → DebugHandler → Filesystem → CoreCacheFactory (Composer, Config, Packages) → |
| 86 | +ContainerBuilder → locale → packages register → packages boot → ready. |
| 87 | + |
| 88 | +Key entry points: |
| 89 | + |
| 90 | +- `Core` -- central kernel, holds config, container, packages, debug, filesystem, cache |
| 91 | +- `App\AbstractApp` -- base application class (extended by `HttpApp` and `CliApp`) |
| 92 | +- `Package\PackageInterface` -- package lifecycle: `config()` → `register(Container)` → `boot(Core)` |
| 93 | +- `Factory\CoreFactory` / `CoreCacheFactory` -- builds and caches Composer, Config, PackageSet |
| 94 | + |
| 95 | +**Default config** (`src/Core/resources/config.default.json`): |
| 96 | + |
| 97 | +```json |
| 98 | +{ |
| 99 | + "berlioz": { |
| 100 | + "environment": "prod", |
| 101 | + "locale": null, |
| 102 | + "debug": { |
| 103 | + "enable": false, |
| 104 | + "ip": [] |
| 105 | + }, |
| 106 | + "directories": { |
| 107 | + "app": "{var: berlioz.directories.app}", |
| 108 | + "cache": "{var: berlioz.directories.cache}", |
| 109 | + "config": "{var: berlioz.directories.config}", |
| 110 | + "debug": "{var: berlioz.directories.debug}", |
| 111 | + "log": "{var: berlioz.directories.log}", |
| 112 | + "tmp": "{config: berlioz.directories.var}/tmp", |
| 113 | + "var": "{var: berlioz.directories.var}", |
| 114 | + "vendor": "{var: berlioz.directories.vendor}", |
| 115 | + "working": "{var: berlioz.directories.working}" |
| 116 | + }, |
| 117 | + "assets": { |
| 118 | + "manifest": null, |
| 119 | + "entrypoints": null, |
| 120 | + "entrypoints_key": null |
| 121 | + } |
| 122 | + }, |
| 123 | + "events": { |
| 124 | + "listeners": {}, |
| 125 | + "subscribers": [] |
| 126 | + }, |
| 127 | + "container": { |
| 128 | + "services": {}, |
| 129 | + "providers": [] |
| 130 | + } |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +### Config (`src/Config/`) |
| 135 | + |
| 136 | +Multi-source configuration with priority-based merging. Pluggable adapters (JSON, YAML, INI, Array) sorted by priority. |
| 137 | + |
| 138 | +**Config Functions** -- dynamic value interpolation in strings: |
| 139 | + |
| 140 | +- `{config: berlioz.environment}` -- references another config key |
| 141 | +- `{var: berlioz.directories.app}` -- resolves a runtime variable |
| 142 | +- `{env: APP_ENV}` -- reads an environment variable |
| 143 | +- `{constant: PHP_INT_MAX}` -- resolves a PHP constant |
| 144 | +- `{file: /path/to/file}` -- reads file contents |
| 145 | + |
| 146 | +Dot-notation key access: `$config->get('berlioz.debug.enable')` |
| 147 | + |
| 148 | +### Service Container (`src/ServiceContainer/`) |
| 149 | + |
| 150 | +PSR-11 dependency injection container with auto-wiring. `Container` delegates to a chain: DefaultContainer → |
| 151 | +ProviderContainer → AutoWiringContainer. `Instantiator` provides reflection-based auto-wiring. `Inflector` auto-calls |
| 152 | +setter methods when a service implements a given interface (the "Aware" pattern). |
| 153 | + |
| 154 | +### Event Manager (`src/EventManager/`) |
| 155 | + |
| 156 | +PSR-14 event dispatcher. Dispatches through a provider chain (SubscriberProvider → ListenerProvider). Supports lazy |
| 157 | +subscribers, named stoppable events, and delegate dispatchers. |
| 158 | + |
| 159 | +### Router (`src/Router/`) |
| 160 | + |
| 161 | +`Router` and `Route` both implement `RouteSetInterface` (composite pattern: routes can be groups). Path syntax: `{id}`, |
| 162 | +`{id:regex}`, `{id::int}`, `{id::uuid4}`, `{id::slug}`. Optional segments: `[/page/{page}]`. Routes compile to regex, |
| 163 | +cached via serialization. |
| 164 | + |
| 165 | +### HTTP Core (`src/Http/Core/`) |
| 166 | + |
| 167 | +HTTP application framework. `HttpApp` extends `AbstractApp` and implements PSR-15 `RequestHandlerInterface`. Middleware |
| 168 | +pipeline (Russian doll) wraps `ControllerHandler`. Routes are built from `#[Route]`/`#[RouteGroup]` PHP 8 attributes by |
| 169 | +`RouterBuilder`. |
| 170 | + |
| 171 | +**Controller return type handling:** `ResponseInterface` → pass through, `null`/empty → 204, scalar → 200, other → JSON. |
| 172 | + |
| 173 | +### HTTP Message (`src/Http/Message/`) |
| 174 | + |
| 175 | +Pure PSR-7/PSR-17 implementation with no framework dependencies. `HttpFactory` implements all 6 PSR-17 factory |
| 176 | +interfaces via traits. Includes pluggable body parsers and specialized streams. |
| 177 | + |
| 178 | +### HTTP Client (`src/Http/Client/`) |
| 179 | + |
| 180 | +PSR-18 HTTP client. Adapter pattern for transport (cURL, stream, HAR replay). `Session` provides stateful cookies and |
| 181 | +history. Follow-redirect (method-aware for 307/308), retry with backoff. |
| 182 | + |
| 183 | +### CLI Core (`src/Cli/Core/`) |
| 184 | + |
| 185 | +CLI application framework, structurally mirrors HTTP Core. `CliApp` extends `AbstractApp`. Commands use `#[Argument]` |
| 186 | +attributes for argument declaration. `Console` extends League CLImate. Entry point: `src/Cli/Core/berlioz`. |
| 187 | + |
| 188 | +### Standalone Components |
| 189 | + |
| 190 | +- **Mailer** -- strategy pattern for transports (SMTP, PHP mail) |
| 191 | +- **Form** -- composite tree of elements with collectors, hydrators, transformers, validators; bidirectional object |
| 192 | + mapping, PSR-7 integration |
| 193 | +- **FlashBag** -- single class, session-backed, consume-on-read |
| 194 | +- **HtmlSelector** -- CSS→XPath translation, jQuery-like query API |
| 195 | +- **QueueManager** -- multiple queue backends (AMQP, Redis, SQS, DB, Memory, Null); `Worker` with rate limiting, |
| 196 | + backoff, signal handling |
| 197 | + |
| 198 | +### Integration Packages (`src/Package/`) |
| 199 | + |
| 200 | +All follow the same pattern: `BerliozPackage` extending `AbstractPackage` + `ServiceProvider` + optional Debug section, |
| 201 | +middleware, CLI commands. |
| 202 | + |
| 203 | +- **Twig** -- Twig template engine integration with extensions and `TwigAwareInterface` |
| 204 | +- **Hector** -- Hector ORM integration with middleware, event subscriber, CLI commands |
| 205 | +- **QueueManager** -- QueueManager integration with queue factories, CLI commands, built-in job handlers |
| 206 | + |
| 207 | +## Coding Conventions |
| 208 | + |
| 209 | +### Formatting |
| 210 | + |
| 211 | +| Rule | Value | |
| 212 | +|---------------------|--------------------------------------------| |
| 213 | +| Coding standard | **PSR-12** | |
| 214 | +| Indent | **4 spaces** (PHP), 2 spaces (other files) | |
| 215 | +| Max line length | **120 characters** | |
| 216 | +| End of line | **LF** | |
| 217 | +| Charset | **UTF-8** | |
| 218 | +| Final newline | **Yes** | |
| 219 | +| Trailing whitespace | **Trim** (except Markdown) | |
| 220 | + |
| 221 | +### PHP File Template |
| 222 | + |
| 223 | +Every PHP file must follow this structure: |
| 224 | + |
| 225 | +```php |
| 226 | +<?php |
| 227 | +/* |
| 228 | + * This file is part of Berlioz framework. |
| 229 | + * |
| 230 | + * @license https://opensource.org/licenses/MIT MIT License |
| 231 | + * @copyright 2026 Ronan GIRON |
| 232 | + * @author Ronan GIRON <https://github.com/ElGigi> |
| 233 | + * |
| 234 | + * For the full copyright and license information, please view the LICENSE |
| 235 | + * file that was distributed with this source code, to the root. |
| 236 | + */ |
| 237 | + |
| 238 | +declare(strict_types=1); |
| 239 | + |
| 240 | +namespace Berlioz\PackageName; |
| 241 | + |
| 242 | +use Some\Dependency; |
| 243 | + |
| 244 | +class ClassName |
| 245 | +{ |
| 246 | +} |
| 247 | +``` |
| 248 | + |
| 249 | +**Non-negotiable rules:** |
| 250 | + |
| 251 | +- `declare(strict_types=1)` in **every** PHP file |
| 252 | +- License block comment header (not PHPDoc) |
| 253 | +- All imports via `use` statements -- never inline fully qualified names |
| 254 | +- One class/interface/trait/enum per file |
| 255 | + |
| 256 | +### Naming Conventions |
| 257 | + |
| 258 | +| Element | Convention | Example | |
| 259 | +|----------------|--------------------------------------------|------------------------------------------| |
| 260 | +| Interface | Suffix `Interface` | `RouteInterface`, `ConfigInterface` | |
| 261 | +| Trait | Suffix `Trait` | `RouteSetTrait`, `ContainerAwareTrait` | |
| 262 | +| Abstract class | Prefix `Abstract` | `AbstractApp`, `AbstractController` | |
| 263 | +| Exception | Suffix `Exception`, in `Exception/` subdir | `RoutingException`, `ContainerException` | |
| 264 | +| Test class | Suffix `Test` | `RouterTest`, `ConfigTest` | |
| 265 | + |
| 266 | +### Key Patterns |
| 267 | + |
| 268 | +**Aware Pattern** -- pervasive dependency injection via interface + trait + inflector: |
| 269 | + |
| 270 | +``` |
| 271 | +{Xxx}AwareInterface → get{Xxx}(): ?{Xxx}, set{Xxx}({Xxx}): static |
| 272 | +{Xxx}AwareTrait → implements the interface |
| 273 | +Inflector → auto-calls set{Xxx}() when a service implements the interface |
| 274 | +``` |
| 275 | + |
| 276 | +**Exception Design** -- each package has its own `Exception/` directory with a root exception extending `\Exception` and |
| 277 | +specialized sub-exceptions. Named static constructors are common: `NotFoundException::notFound($id)`. |
| 278 | + |
| 279 | +**Serialization** -- custom `__serialize()` / `__unserialize()` methods (not the `Serializable` interface). |
| 280 | + |
| 281 | +**Package Lifecycle** -- `config()` → `register(Container)` → `boot(Core)`. |
| 282 | + |
| 283 | +### PHP 8.x Features in Use |
| 284 | + |
| 285 | +- Constructor property promotion (preferred way to declare properties) |
| 286 | +- Union types, `match` expressions, nullsafe `?->`, named arguments -- all used **heavily** |
| 287 | +- Enums, `readonly class`, `readonly` properties -- used where appropriate |
| 288 | +- Trailing commas everywhere |
| 289 | +- PHP 8 attributes for routing (`#[Route]`, `#[RouteGroup]`), CLI (`#[Argument]`), tests (`#[DataProvider]`) |
| 290 | +- `static` return type for fluent chainable setters |
| 291 | + |
| 292 | +### PHPDoc |
| 293 | + |
| 294 | +- **Interfaces/abstract classes:** Full PHPDoc on every method (`@param`, `@return`, `@throws`) |
| 295 | +- **Concrete implementations:** `/** @inheritDoc */` when overriding |
| 296 | +- **Class docblock:** `Class ClassName.` or `Interface InterfaceName.` |
| 297 | +- **Complex types:** PHPDoc for arrays of objects (`@var Attribute[]`), generics (`@template T`) |
| 298 | +- **Simple types:** Native type declarations preferred, PHPDoc only when adding information |
| 299 | + |
| 300 | +## Testing |
| 301 | + |
| 302 | +- **Framework:** PHPUnit 11 with **attributes** (not annotations) |
| 303 | +- **Config:** `phpunit.xml.dist` -- 17 named test suites, one per package |
| 304 | +- **Strict:** `failOnDeprecation`, `failOnRisky`, `failOnWarning` all enabled |
| 305 | +- **Test suite names** match `phpunit.xml.dist` exactly (e.g., `Berlioz Router test suite`) |
| 306 | + |
| 307 | +```bash |
| 308 | +vendor/bin/phpunit # All tests |
| 309 | +vendor/bin/phpunit --testsuite="Berlioz Router test suite" # Single package |
| 310 | +``` |
| 311 | + |
| 312 | +### Test Conventions |
| 313 | + |
| 314 | +- Test files: `*Test.php`, namespace `Berlioz\{Package}\Tests\` mirroring `src/` |
| 315 | +- Data providers: `public static` methods, named `provide*`, referenced with `#[DataProvider('...')]` |
| 316 | +- Mocking: PHPUnit built-in `createMock()` / `getMockBuilder()` only (no Mockery/Prophecy) |
| 317 | +- Method naming: `testMethodName()`, variants with underscore: `testGenerate_withPrefix()` |
| 318 | +- Fake/helper classes live alongside tests (e.g., `tests/Form/Fake/`) |
| 319 | + |
| 320 | +### `tests_env/` Directories |
| 321 | + |
| 322 | +Some packages have `tests_env/` directories containing **fake Berlioz project environments** for integration tests ( |
| 323 | +config, fake services, stub vendor). Found in: `tests/Core/`, `tests/Cli/Core/`, `tests/Http/Core/`, `tests/Package/*/`. |
| 324 | +Autoloaded under `TestProject` namespaces. |
| 325 | + |
| 326 | +## Changelogs & Releases |
| 327 | + |
| 328 | +Each package has its own `CHANGELOG.md` at the root of its directory (e.g., `src/Router/CHANGELOG.md`). The monorepo |
| 329 | +root also has a `CHANGELOG.md` that aggregates all package changes per version. |
| 330 | + |
| 331 | +**Format:** [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) with categories: Added, Changed, Deprecated, |
| 332 | +Removed, Fixed, Security, Docs. |
| 333 | + |
| 334 | +### Workflow for contributors |
| 335 | + |
| 336 | +When you modify a package, **add your changes to the `## [Unreleased]` section** of that package's `CHANGELOG.md`. |
| 337 | +Do **not** touch the root `CHANGELOG.md` or assign a version number -- the release tooling handles that. |
| 338 | + |
| 339 | +Example -- after adding a feature to the Router: |
| 340 | + |
| 341 | +```markdown |
| 342 | +## [Unreleased] |
| 343 | + |
| 344 | +### Added |
| 345 | + |
| 346 | +- Support for wildcard route parameters |
| 347 | +``` |
| 348 | + |
| 349 | +### Release process |
| 350 | + |
| 351 | +`bin/prepare-release.php` automates versioning: |
| 352 | + |
| 353 | +```bash |
| 354 | +php bin/prepare-release.php <version> <date> [package-names...] [--dry-run] |
| 355 | + |
| 356 | +# Examples: |
| 357 | +php bin/prepare-release.php 3.1.0 2026-03-15 # All packages |
| 358 | +php bin/prepare-release.php 3.1.0 2026-03-15 router core # Specific packages |
| 359 | +php bin/prepare-release.php 3.1.0 2026-03-15 --dry-run # Preview only |
| 360 | +``` |
| 361 | + |
| 362 | +What it does: |
| 363 | +1. For each package: renames `[Unreleased]` → `[version] - date` and creates a fresh empty `[Unreleased]` section |
| 364 | +2. Aggregates all package items into the root `CHANGELOG.md` under the same version, prefixed with the package name |
| 365 | + (e.g., `**berlioz/router**: Support for wildcard route parameters`) |
| 366 | +3. Merges intelligently if the version block already exists (idempotent) |
| 367 | + |
| 368 | +## Tooling |
| 369 | + |
| 370 | +| Tool | Command | Purpose | |
| 371 | +|--------------|-------------------------------|------------------------------| |
| 372 | +| PHPUnit | `vendor/bin/phpunit` | Test suite | |
| 373 | +| Rector | `vendor/bin/rector --dry-run` | Code quality check | |
| 374 | +| Rector (fix) | `vendor/bin/rector` | Auto-fix code quality issues | |
| 375 | +| PHPStan | `vendor/bin/phpstan` | Static analysis | |
| 376 | + |
| 377 | +## CI/CD |
| 378 | + |
| 379 | +- **Tests:** GitHub Actions on push to `main`/`*.x`, PRs, and daily. Matrix: PHP 8.2-8.5 x prefer-lowest/prefer-stable. |
| 380 | + Coverage via xdebug. |
| 381 | +- **Subsplit:** Automatically synchronizes each `src/` subdirectory to its read-only mirror repository. |
0 commit comments