3: Runtime-configuratie
data/app.json beschrijft de build, wat getranspiled wordt. Dit hoofdstuk behandelt de runtime, wat er gebeurt bij elke aanvraag. Die configuratie bevindt zich in www/app.php (en, voor stage/release, release/www/app.php) als argumenten voor phlo_app(...).
<?php
require('/opt/phlo/phlo.php');
phlo_app (
id: 'Example',
host: 'dev.example.nl',
auth: true,
build: true,
debug: true,
app: '/var/www/example.nl/',
)
Elke sleutel is een genaamd argument dat ook een PHP constante wordt, overal in je app beschikbaar als een los woord (host, data, composer, enzovoort). Hetzelfde geldt voor je eigen aangepaste sleutels.
3.1: Identity and host
| Key | Default | Purpose |
|---|---|---|
id |
none | Free-form name for the app, used by the Phlo Control Center, the Phlo Dashboard and logs |
host |
null |
Canonical host used by the app for URL generation, credentials and diagnostics. Bind and validate incoming hosts in the web server; Phlo does not reject a request by its Host header |
3.2: Modi: `build`, `debug`, `auth`, `thread`
| Sleutel | Standaard | Effect |
|---|---|---|
build |
false |
Schakelt de build/lint/reflect CLI in en laat Phlo gewijzigde bronnen per verzoek detecteren en opnieuw bouwen |
debug |
false |
Laadt debug.php, activeert debug() / dx() / debug helpers, geeft volledige stack traces in plaats van generieke 500-fouten |
auth |
false |
Site-brede HTTP Basic-authenticatie met inloggegevens in data/auth.ini |
thread |
false |
Worker-modus (zie 3.7), true = onbeperkt, geheel getal = aantal verzoeken per worker |
Combineer niet: build: true en thread: true. Build schrijft bestanden tussen verzoeken; in een langdurige worker is dat onveilig. Phlo gooit een runtime-fout als beide aan staan.
Vereist: auth: true heeft build: true nodig. Phlo geeft een foutmelding bij het opstarten als auth is ingesteld zonder build. (Dit is een implementatiedetail: de site-brede auth-handler wordt meegeleverd in de build-laag. Om een host in een niet-buildfase te beschermen, plaats je HTTP Basic-authenticatie in je webserver voor Phlo.)
Control Center-pad: wanneer build: true en debug: true, wordt het Phlo Control Center automatisch gemonteerd op /phlo, geen configuratie nodig. Gebruik de optionele control: sleutel om het op een ander pad te monteren (control: 'admin' serveert het op /admin) of stel control: false in om het uit te schakelen. Buiten build+debug is het ongeacht uitgeschakeld.
3.3: Paths
Only app is required. The rest falls back to subdirectories of app:
| Key | Default | Intended for |
|---|---|---|
app |
(required) | App root |
data |
<app>/data/ |
Config (app.json, auth.ini), credentials, runtime state |
php |
<app>/php/ |
Generated PHP, only change this when release output lives elsewhere |
www |
<app>/www/ |
Web root |
Your release entrypoint typically sets app: '<app>/release/' and php: '<app>/release/' so release output is served without build mode.
3.3.1 Your own path keys
Every key you pass becomes a constant, so an app declares its own directories the same way:
phlo_app (
app: '/srv/site/release/',
php: '/srv/site/release/',
guide: '/srv/site/guide/',
snippets: '/srv/site/snippets/',
);
guide.'1. intro.md' and snippets.'01-intro.txt' then resolve wherever the entrypoint points them.
Lesson. Content read at runtime needs a key of its own; do not build its path from
app. The assumption "appis my project directory" holds in development and breaks on release, because thereapppoints atrelease/, which contains generated PHP and nothing else. Markdown, code samples and fixtures loaded withfile_get_contents()are then silently missing: the route still answers 200 and the page renders with an empty section, so nothing fails loudly. Give each such directory a key, and the same source is served in both modes.
3.4: Control Center en WebSocket
| Sleutel | Standaard | Effect |
|---|---|---|
control |
'phlo' met build+debug, anders false |
URL-prefix waaronder de control UI zich bevindt. Bijvoorbeeld 'beheer' → /beheer; false = uit |
daemon |
null |
Poort van de Phlo Daemon voor deze app. Wordt de constante daemon, gebruikt door wsCast() en de runtime helpers |
De control UI vereist build: true. Zie de hoofdstukken over Daemon en WebSocket voor de setup van de Phlo Daemon.
3.5: Composer autoload
Wil je PHP-pakketten uit vendor/ gebruiken? Geef het pad op:
phlo_app (
composer: '/var/www/example.nl/',
...
)
Phlo registreert vervolgens een lazy autoloader die alleen <composer>/vendor/autoload.php laadt wanneer een onbekende klasse moet worden opgelost. Geen opstartkosten als je nooit Composer-pakketten aanraakt; volledige Composer autoload zodra je dat wel doet.
Conventie: composer: '<app>/data/' (de composer.json en vendor/ bevinden zich dan in data/, buiten de webroot).
3.6: Trace en CLI
| Sleutel | Standaard | Effect |
|---|---|---|
trace |
false |
Schakelt de trace-modus in, zie het Trace-hoofdstuk |
cli |
'php-zts' (indien ZTS) of 'php' |
Pad naar de PHP-binaire die Phlo gebruikt voor subprocessen (build, taken, websocket). Overschrijf dit als je systeem niet-standaard PHP-binaries heeft |
cliis een string in v4 (een pad), niet een boolean zoals in v1.
3.7: Worker mode rules
thread: true puts Phlo in long-running FrankenPHP worker mode. The runtime stays in memory between requests. Three rules you need to know:
1. No die() or exit() in the HTTP path. Both kill the entire worker. view(), apply() and location() prepare or render a response but do not terminate PHP execution, so return them from a route guard or otherwise let the routine end immediately afterwards.
2. No request state in static properties. PHP static properties and function-local statics survive between requests. Phlo clears its computed-static cache (obj::$classProps) during the worker reset, but request and user state still belongs on an instance with an explicit lifetime or on %req, never in a PHP static.
3. Mark long-lived objects with $objPers = true. By default Phlo clears its instance map between requests. For objects you explicitly want to reuse (DB connection, prepared statements), set $this->objPers = true so the cleanup leaves them alone.
3.8: Aangepaste sleutels: je eigen constanten
Elke extra benoemde parameter die je doorgeeft aan phlo_app() wordt automatisch een PHP-constante. Dat is de manier om app-brede paden of functie-vlaggen te declareren:
phlo_app (
app: '/var/www/example.nl/',
langs: '/var/www/example.nl/langs/',
files: '/var/www/example.nl/files/',
uploads: '/var/www/example.nl/data/uploads/',
)
Directly bruikbaar in .phlo daarna:
$dict = parse_ini_file(langs.'en.ini')
$path = files.'avatars/'.$user->id.'.jpg'
$url = uploads
reflect::runtime toont alle gedefinieerde constanten. Handig om te ontdekken wat een app biedt zonder www/app.php te openen.
3.9: Voorbeeld: dev en release naast elkaar
phlo_app (
id: 'Example',
host: 'dev.example.nl',
auth: true,
build: true,
debug: true,
trace: true,
app: '/var/www/example.nl/',
composer: '/var/www/example.nl/data/',
daemon: 3001,
langs: '/var/www/example.nl/langs/',
)
phlo_app (
id: 'Example',
host: 'example.nl',
thread: true,
app: '/var/www/example.nl/release/',
php: '/var/www/example.nl/release/',
data: '/var/www/example.nl/data/',
composer: '/var/www/example.nl/data/',
daemon: 3001,
langs: '/var/www/example.nl/langs/',
)
Dev heeft build, debug, auth, het Control Center en trace. Release heeft worker mode (thread) en wijst de webroot/php output naar release/. data, composer, langs blijven hetzelfde, dat is gedeelde staat.
3.10: De request- en response-objecten
Twee runtime-objecten dragen elke aanvraag. %req (lezen) en %res (schrijven) zijn altijd beschikbaar.
%req berekende eigenschappen:
| Eigenschap | Bevat |
|---|---|
%req->method |
HTTP-werkwoord, in hoofdletters |
%req->path |
Aanvraagpad zonder leidende schuine streep |
%req->part($i) |
Padsegment op index |
%req->query |
Geparsed querystring-array |
%req->async |
true wanneer de aanvraag afkomstig is van phlo.js (SPA-navigatie, asynchrone formulieren) |
%req->cli |
true wanneer uitgevoerd vanaf de opdrachtregel |
%req->secure, %req->scheme, %req->base, %req->url |
URL-onderdelen, eenmaal berekend |
%req->referer, %req->acceptLanguage |
Veelvoorkomende headers, genormaliseerd |
%res oppervlak:
| Lid | Doet |
|---|---|
%res->header($key, $value) |
Plaatst een responsheader in de wachtrij (verzonden bij renderen) |
%res->type |
Content-Type voor de respons |
%res->text($body) / %res->json(...) / %res->xml($body) |
Stelt de body in (en type voor json/xml); ketenbaar |
%res->render($code = null) |
Verzendt status, headers en body; markeert de respons als voltooid |
%res->streaming |
true schakelt apply() over naar onmiddellijke flush-per-opdracht (zie het WebSocket-hoofdstuk) |
%res->status, %res->done |
Statuscode; of de output al is verzonden |
output($content, $filename = null, $attachment = null, $file = null, $code = null, $type = null) is de responsfunctie voor bestanden, blobs en JSON-met-een-status: het dient een bestand (mime op naam, optionele attachment), of JSON wanneer $content een array is (output(['id' => $id], code: 201), output(['error' => 'not found'], code: 404)); type overschrijft de content-type voor een vooraf-gecodeerde string body. view()/apply()/output()/error()/location() zijn de responsfuncties die app-code gebruikt; de %res->json/text/xml/render leden hierboven zijn de low-level primitieve waar ze op bouwen, voor de zeldzame hand-geassembleerde respons (aangepaste headers, een speciale content-type). Verpak ze niet in per-app jsonOut()/respond() helpers.
Les.
die($content)lijkt alsof het "een respons verzendt", maar het omzeiltrender(): geplaatste headers (inclusief Content-Type) verlaten nooit de server, en in worker-modus beëindigtdie()de hele worker. Deze site heeft zijn machine-leesbare eindpunten wekenlang alstext/htmlop deze manier aangeboden. Eindig altijd metoutput(...),view(),apply(),location()of een expliciete%res->...->render().
Runtime-fouten komen terecht in data/errors.json, gekeyd door een stabiele 8-teken referentie-id (een hash van host, locatie en het pad-afgekapte bericht) zodat dezelfde fout in één vermelding met een teller en een laatste-verschijnings-tijdstempel wordt gededupliceerd. Elke vermelding registreert ook het bron-gemapte .phlo bestand en regel, de host en het aanvraagpad; de nieuwste 200 worden bewaard. Lees ze met reflect::errors [limit] of in het Phlo Control Center.
Die referentie-id is de veilige handle tussen een gebruiker en jou: het verschijnt op de productie-foutpagina (Reference: a3f9c1d2) en reist mee in de error payload van asynchrone/JSON-responsen, zodat een gebruiker deze kan citeren en jij de volledige vermelding kunt opzoeken in errors.json. De productiepagina drukt nooit het bericht of de trace af; die zijn alleen voor debug.
Een app kan zijn eigen gebrandmerkte foutpagina renderen door een statische errorPage(int $code, string $id, ?string $msg): string op zijn app klasse te declareren. De engine roept deze statisch aan met de HTTP-code, de ondoorzichtige referentie-id, en het echte foutbericht alleen onder debug (null anders), zodat een productiepagina geen interne details kan lekken terwijl een debug-omgeving het bericht op een gestileerde pagina kan tonen. Als de hook een fout gooit of niets retourneert, valt de engine terug op zijn eigen pagina: de diagnostiek onder debug, de minimale pagina anders.
Laatst bijgewerkt op 23-08-2026