Skip to content

Commit 9cddfdc

Browse files
committed
Add AGENTS.md with project architecture and conventions for AI assistants
1 parent 846f3b2 commit 9cddfdc

File tree

1 file changed

+381
-0
lines changed

1 file changed

+381
-0
lines changed

AGENTS.md

Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
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

Comments
 (0)