# Modules and configuration
Almost every framework class you will reach for — `Application`, `Configuration`, `PackageManager`,
`I18n`, `Theme`, and every controller — is a **module**: a singleton, configured from json, reached
through `::Shared()`. Never `new` one.
```php
$theme = \MMO\client\Theme::Shared();
```
A module declares its own defaults in `DefaultConfig()`, and the application's configuration file
overrides them by class name. This is the demo's, in `demo/configs/config.json`:
```json
{
"MMO\\core\\Application": {
"routes": {
"MMO\\client\\controllers\\PageController": {},
"MMO\\client\\controllers\\ThemeController": {}
}
},
"MMO\\client\\Theme": {
"stylesheet": "{basepath}/styles/demo.scs",
"pages": ["{basepath}/contents/pages"]
}
}
```
A few things that are easy to get wrong:
- **The key is the class, written with escaped backslashes** — it is json, so `MMO\\client\\Theme`.
- `{basepath}`, `{root}`, `{assets}`, `{bin}` are **path aliases**, resolved by `\MMO\filename()` and
`\MMO\url()`. They are not the same folder: a package served through its own `.htaccess` rewrite moves
`{root}` under `demo/`, which is why anything pointing at a package's files should be anchored on the
package alias (`{MMO\client}` is `libs/client/src`) rather than on `{root}`.
- `"$"` **selects the class to build**, so a configuration entry can name a different implementation:
`"$": "MMO\\core\\entities\\JsonRepository"`. The same marker appears in serialized data — see
[Serialization](dataStructure?guide=20-serialization.md).
- The demo's config file allows `//` comments and trailing commas; it is read by the framework's own json
parser, not by `json_decode`.
> **Broken include** `@include {MMO\client}/../../core/docs/Modules.md` — no such file (`phar:///homepages/38/d520689115/htdocs/mmoframework.fr/www/releases/3.1.4/packages/mmo.client.phar/src/../../core/docs/Modules.md`).
# Controllers and routing
A controller is a module whose public methods are routes. You do not register urls by hand: you tag a
method with `#[APIRoute]`, and `Application` finds it by reflection when the class is connected.
```php
namespace MMO\client\controllers;
use MMO\core\attributes\APIRoute;
use MMO\core\classes\aController;
use MMO\core\server\{Response, Route};
class DemoController extends aController {
#[APIRoute('GET|demo.json', 'a greeting', mediaTypes: ['json'])]
public function routeDemo(Route $route): array {
$route->setResponse(Response::Json());
return ['pizza' => '4 fromages'];
}
}
```
Connect it in the application's configuration, and the route answers at `demo.json`
**relative to the package root** — the folder its `.htaccess` serves, which is the root this page is
served from, whatever you called the folder you cloned into:
```json
"MMO\\core\\Application": {
"routes": {
"MMO\\client\\controllers\\DemoController": {}
}
}
```
## Picking a base class
| base | what its `onRoute()` does for you | use it for |
|---|---|---|
| `aController` | claims the route, and nothing else — **no response is created** | handlers returning an `iResponse`, or setting one up themselves |
| `aDataController` | also calls `$route->setResponse(Response::Json())` | json endpoints that just return an array |
| `aShellController` | CLI: `#[Command]`, `#[InteractiveCommand]`, built-in `help` | command line tools |
This is the one that bites. A handler returning a plain array needs
**a response already set on the route**, because the middleware loop merges the array into the current
response rather than inventing one.
A bare `aController` subclass returning an array fails with *"route has no response set"*. Either extend
`aDataController`, or do what `ThemeController::routeConfig()` does and call `setResponse()` first — the
example above is that second shape.
## The route pattern
```
METHOD|path/with/{param}.{extension}?query={q}&optional={?limit}
```
- `{name}` in the path becomes a route parameter, read with `$route->get('name')`;
- `{?name}` marks a query parameter as optional;
- `order:` decides which route is tried first when several match — lower first. `PageController` claims
every html url with `#[APIRoute('**', …, order: 1000)]`, a deliberately high number so that every other
controller gets the first look.
- `mediaTypes:` lists what the route can answer, and is what lets one handler serve `json`, `csv` and
`xlsx` from the same url.
Real ones to read in this repository: `src/controllers/ThemeController.php` (a small json api, four
verbs on one path), `src/controllers/PageController.php` (html pages, the catch-all), and
`MMO\core\controllers\RepositoryController` (a whole entity api, generated).
> **Broken include** `@include {MMO\client}/../../core/docs/Controller.md` — no such file (`phar:///homepages/38/d520689115/htdocs/mmoframework.fr/www/releases/3.1.4/packages/mmo.client.phar/src/../../core/docs/Controller.md`).
---
> **Broken include** `@include {MMO\client}/../../core/docs/Router.md` — no such file (`phar:///homepages/38/d520689115/htdocs/mmoframework.fr/www/releases/3.1.4/packages/mmo.client.phar/src/../../core/docs/Router.md`).
# Entities
An entity is one record: a class whose properties carry the `#[ArrayProperty]` attribute, so the framework
knows what belongs to the data, how to coerce it, how to validate it and what to send to the client. Each
entity class has a **collection**, each collection belongs to a **repository**, and the repository is the
bridge to wherever the data really lives — a json file, in the demo's case.
```php
use MMO\core\attributes\ArrayProperty;
use MMO\core\entities\Entity;
/**
* @unique firstname,lastname
* @title {genre}. {lastname} {firstname}
*/
class Personne extends Entity {
/**
* @title First name
* @icon circle-user
*/
#[ArrayProperty()]
protected string $firstname;
/**
* @title Email
* @format email
* @readonly true
*/
#[ArrayProperty()]
protected string $email;
}
```
That is `demo/www/entities.php`, shortened. Three habits it shows:
- **Properties are protected and typed.** Public would bypass the coercion and change-tracking that the
array access gives you; the php type is what the generated schema is built from, so there is no
`@type` to write for an ordinary property.
- **The docblock is the schema.** `@title`, `@format`, `@icon`, `@readonly` and any JSON Schema constraint
end up in the class's schema and therefore in every form the client builds from it. See
[Describing and validating data](dataStructure?guide=10-schemas.md).
- `@unique` **on the class is what makes** `getOrCreateEntity()` **able to find anything.** Without it, it
creates a new entity every time it is called.
Read and write through the array operator, never the property:
```php
$personne['firstname']; // coerced on the way in and out
$personne->set('firstname', 'Marc'); // same thing, explicitly
\MMO\commit($this); // nothing is persisted before this
```
> **Broken include** `@include {MMO\client}/../../core/docs/Entities.md` — no such file (`phar:///homepages/38/d520689115/htdocs/mmoframework.fr/www/releases/3.1.4/packages/mmo.client.phar/src/../../core/docs/Entities.md`).
# Events
An event is an object that is sent, and that anything may listen to. The framework uses them for its own
wiring, which is why so much of it can be extended without subclassing anything: `Theme` builds the site's
menu by listening to `PageContents`, and adds its stylesheets by listening to `Response`.
```php
use MMO\core\attributes\Listen;
#[Listen(PageContents::class)]
public function onPageContents(PageContents $contents): void { … }
```
A **signal** is an event with a return value, so a listener can change the outcome of an operation rather
than just observe it.
> **Broken include** `@include {MMO\client}/../../core/docs/Events.md` — no such file (`phar:///homepages/38/d520689115/htdocs/mmoframework.fr/www/releases/3.1.4/packages/mmo.client.phar/src/../../core/docs/Events.md`).