1: Inleiding
Phlo is een programmeertaal en engine bovenop PHP 8+, bedoeld om compacte, overzichtelijke en performante webapps te bouwen. Routing, controllercode, views, styling en frontend-updates vormen één geheel. Phlo transpileert .phlo naar normale PHP-klassen en genereert de benodigde assets.
De webserver wijst naar je webroot /www. Onbekende paden worden herschreven naar /www/app.php, waar Phlo je configuratie inleest, instanties beheert en routes afhandelt. De buildfase draait automatisch (JIT) zodra bronbestanden veranderen.
1.1: Filosofie
- Korte code, hoge expressiviteit – minimale syntaxis, geen puntkomma’s, compacte routes.
- Vrijheid zonder betutteling – Phlo dwingt geen frameworkstructuur af.
- Balans in hoogte/breedte – logische samenhang i.p.v. versnippering over mapjes.
- Performance door eenvoud – getranspileerde PHP draait zonder runtime-penalty; frontend gebruikt lichte NDJSON-updates.
- Één coherent systeem – routing, rendering, assets en frontendgedrag horen samen.
1.2: Wat is Phlo
Phlo is een superset van PHP met automatische build. Een .phlo-bestand kan bevatten:
- Controllercode op top-level (uitgevoerd na instantiatie)
- Routes (spatie-gescheiden)
- Methoden & props (incl. lazy props met
=>) - Views (HTML-afkortingen,
<if>,<foreach>) - Styles (compacte CSS zonder puntkomma’s)
- Frontend scripts (samen met de view gebundeld)
Phlo is modulair inzetbaar: full-stack, frontend-engine-only, class-writer/codegen of als asset-pipeline.
1.3: Installatie
1) Engine plaatsen
Plaats de map phlo/ in je project (buiten /www).
2) Webserver → webroot /www
Stel de webserver in met document root naar /www en rewrite onbekende paden naar app.php.
Nginx
server {
root /pad/naar/project/www;
location / {
try_files $uri $uri/ /app.php?$query_string;
}
}
Apache (.htaccess in /www)
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ app.php [QSA,L]
Statische bestanden (afbeeldingen, app.js, app.css) worden direct geserveerd; alleen onbekende paden gaan naar Phlo.
3) Inhoud van /www/app.php
Gebruik de werkende entrypoint zoals in je codebase:
<?php
require('/srv/phlo/phlo.php');
phlo_app_jsonfile($app = dirname(__DIR__).'/', "$app/data/app.json");
Wat dit doet:
- Laadt de engine (
phlo/phlo.php) - Bepaalt het projectpad (
$app) - Leest en valideert
/data/app.json(verplicht) - Initialiseert instanties, voert controllercode uit en start routing
4) Build (JIT) en scripts
- De engine voert de build automatisch uit bij de eerstvolgende request na wijziging (JIT).
- Wil je handmatig bouwen (bijv. in CI), doe dat via PHP door
phlo_build()aan te roepen.
1.4: Projectstructuur
Een Phlo-project heeft een vaste indeling:
/www/ ← webroot (publiek)
app.php ← centraal entrypoint
app.js ← automatisch gegenereerde frontend bundel
app.css ← automatisch gegenereerde CSS
/data/app.json ← verplichte configuratie
/phlo/ ← Phlo engine
/php/ ← getranspileerde PHP (automatisch)
Belangrijke punten:
-
app.jsenapp.cssworden volledig automatisch gegenereerd vanuit je.phlo-bronnen enapp.jsoninstellingen. → Je schrijft of wijzigt deze bestanden nooit handmatig. → Bij elke buildfase worden ze overschreven. -
De inhoud van deze bestanden is afhankelijk van de
phloNS,defaultNS,phloJS, en CSS-buildopties in/data/app.json. -
/www/app.phpstart de engine op en verwijst naar je configuratie. -
/phpbevat de getranspileerde klassen. Ook deze bestanden pas je niet handmatig aan; ze worden bij elke buildfase gegenereerd of ververst.
2: Configuratie
Elke Phlo-app heeft een verplicht configuratiebestand:
/data/app.json
De engine leest dit in vanuit app.php en bepaalt hiermee bronnen, libraries, bundling en asset-output.
2.1: Doel en positie
- Locatie:
/data/app.json(relatief t.o.v. projectbasis). - Ingeladen door
/www/app.phpvia de Phlo-engine. - Strikte JSON (geen comments, geen trailing comma’s).
- Paden mogen de placeholder
%app/gebruiken (wordt vervangen door het app-pad uitapp.php).
2.2: Minimale configuratie
{
"id": "MijnApp",
"version": ".1",
"host": "localhost",
"dashboard": "phlo",
"debug": true,
"build": {
"libs": []
}
}
Waarom zo?
build.libsis verplicht (mag een lege array zijn).- Alle overige
build-opties zijn optioneel en voeg je toe wanneer nodig (sources, namespaces, CSS/JS-build, minify, enz.).
Productie/release zonder (volledige) build behandelen we later apart.
2.3: `build.sources`
Alleen nodig als je meer bronpaden wilt dan het app-pad uit /www/app.php.
{
"build": {
"sources": [
"%app/",
"/srv/phloCMS/",
"/srv/phloCMS/fields/"
],
"libs": []
}
}
- Elk pad kan absoluut zijn of met
%app/beginnen. - Alle
.phloin deze paden tellen mee voor routing, transpilen en assets.
2.4: `build.libs`
Declareert welke libraries (uit phlo/libs/) je vooraf wil laden en door je project gekend wil hebben.
{
"build": {
"libs": [
"DB/DB",
"DB/MySQL",
"model",
"payload"
]
}
}
- Items corresponderen met bestanden in
phlo/libs/(zonder extensie). - On-demand autoload blijft bestaan: als je code een nog-niet-geladen lib gebruikt, laadt Phlo deze automatisch.
build.libsis dus jouw expliciete baseline/preload (en kan van belang zijn voor volgorde/initialisatie).
2.5: Namespaces & assets
Gebruik alleen als je bundling/asset-scopes wil sturen.
{
"build": {
"libs": [],
"defaultNS": "app",
"phloNS": ["app", "cms"],
"iconNS": ["cms"],
"icons": "/srv/icons"
}
}
defaultNS– standaard namespace als er geen explicietensis.phloNS– bepaalt welke namespaces in de frontend-bundles komen én voor welke namespaces Phlo assets naar/www/schrijft (dus ruimer dan alleenapp.js/app.css: ook extra bundles, icon-assets/sprites, enz., afhankelijk van aanwezige code/assets per namespace).iconNS+icons– namespaces en bron voor iconsets.
2.6: Frontend-buildopties
{
"build": {
"libs": [],
"phloJS": false,
"buildCSS": true,
"minifyCSS": false,
"buildJS": true,
"minifyJS": false
}
}
| sleutel | type | betekenis |
|---|---|---|
phloJS |
bool | Phlo-frontendengine meebundelen (geavanceerde use-cases). |
buildCSS |
bool | Styles uit .phlo verwerken en CSS-assets schrijven (per namespace). |
minifyCSS |
bool | CSS minificeren. |
buildJS |
bool | Frontend scripts bundelen en JS-assets schrijven (per namespace). |
minifyJS |
bool | JS minificeren. |
Belangrijk: Alles wat Phlo naar /www/ schrijft (zoals app.js, app.css en eventuele namespaced/extra assets) is gegenereerd en wordt bij builds overschreven. Niet handmatig bewerken.
2.7: Overige velden
routes,extendse.d. kunnen door modules worden gebruikt.%app/in paden wordt bij init vervangen door het actuele app-pad.- Hou
app.jsonstrikt valide.
3: Syntax & Structuur
Phlo is een superset van PHP met compacte, puntkomma-loze syntaxis. In één .phlo-bestand combineer je routes, props, methods, views, styles, scripts en controllercode. De builder transpilet naar gewone PHP/JS/CSS.
3.1: Bestand structuur
Top-level elementen:
function— project-globale functiesroute— routering naar methodenstatic— statische waarden of statische arrow-methods- controllercode — top-level statements buiten bovenstaande blokken
prop— statische of computed properties (mogen arguments hebben)method— methoden op de gegenereerde classview Naam:— views<script>…</script>— scripts<style>…</style>— styles
Minimaal, geldig voorbeeld:
route GET home => $this->main
prop title = 'Welkom'
method main => view($this->home)
view home:
<h1>$this->title</h1>3.2: Controllercode
Top-level statements buiten blokken vormen de controller van het bestand.
De controller draait nadat de instantie bestaat (niet in __construct) — dit voorkomt cirkelreferenties.
Voorbeeld (onschadelijke init):
prop initialized = false
$this->initialized = true3.3: Statements
- Geen puntkomma’s: einde van de regel sluit het statement.
- Multiline argumenten: ieder argumentregel eindigt met een komma.
- Spaties rond
=>.
Voorbeelden:
if ($active) $count = $count + 1
foreach ($rows AS $r) $sum = $sum + $r->value
foreach ($rows AS $r){
$sum = $sum + $r->value
$n = $n + 1
}
chunk (
title: 'Overzicht',
main: view($this->home),
)
apply (
title: 'Klaar',
main: '<p>Gereed</p>',
)3.4: Variabelen & Scopes
- Variabelen zoals in PHP:
$naam. - Globale instanties via
%Naam(bijv.%MySQL,%payload) — lazy via instance manager. - Scope: locals binnen functions/methods; props/statics op instantie/klasse;
%…gedeeld projectbreed.
3.5: Constants
Exact zoals in de engine gedefinieerd:
| Constant | Betekenis / Waarde | ||
|---|---|---|---|
phlo |
Huidige Phlo-versie (string) | ||
cli |
true als er geen REQUEST_METHOD is (CLI) |
||
async |
true als HTTP_X_REQUESTED_WITH gelijk is aan 'phlo' |
||
method |
'CLI' of de HTTP-methode van de request |
||
jsonFlags |
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES |
||
br |
'<br>' |
||
bs |
'\\' (backslash) |
||
bt |
'`' (backtick) |
||
colon |
':' |
||
comma |
',' |
||
cr |
"\r" |
||
dash |
'-' |
||
dot |
'.' |
||
dq |
'"' (double quote) |
||
eq |
'=' |
||
lf |
"\n" |
||
nl |
cr.lf → effectief "\r\n" |
||
perc |
'%' |
||
pipe |
' | ' |
||
qm |
'?' |
||
semi |
';' |
||
slash |
'/' |
||
space |
' ' |
||
sq |
"'" (single quote) |
||
tab |
"\t" |
||
us |
'_' |
||
void |
'' (lege string) |
Deze constants zijn overal beschikbaar in
.phlo.
3.6: Strings & Operators
Strings: 'single' en "double".
Operators: conform PHP. Phlo voegt compactere syntaxis toe (arrow-bodies, named arguments); semantiek blijft gelijk.
3.7: Functies
Functies zijn project-globaal en worden geschreven naar /php/app.php.
Singleline:
function add($a, $b) => $a + $b
Meerlijns:
function sum($a, $b){
return $a + $b
}3.8: Methoden
Methoden horen bij de class die uit het .phlo-bestand wordt gegenereerd.
method hello($who) => 'Hi '.$who
method classify($x) {
if ($x > 5) return 'groot'
return 'klein'
}
Arrow voor éénregelige logica; meerlijns gebruikt accolades.
3.9: Props
Statisch (transpileert naar PHP-classproperty; géén function calls toegestaan):
prop title = 'App'
prop defaults = ['theme' => 'dark']
Computed (lazy + cached):
prop now => time()
prop fullName => $this->first.' '.$this->last
Props met arguments (alleen computed):
prop repeat($n) => str_repeat('*', $n)
# gebruik
$this->repeat(5)3.10: Statics
Definitie:
static x = 1
static y => time()
Aanroepen binnen dezelfde class:
dx (
static::$x,
static::x(),
static::y,
)
Aanroepen extern (via classnaam):
dx (
test::$x,
test::x(),
test::y,
)
Belangrijk:
- Props en methods zonder arguments mogen zonder
()worden aangeroepen. - Static methods vereisen altijd
()bij aanroep. - Ook een statische waarde (zoals
x = 1) mag als method aangeroepen worden (x()), voor consistente syntaxis.
3.11: Named Arguments
Volledig ondersteund; maakt aanroepen expliciet en minder foutgevoelig.
%MySQL->delete (
'Users',
'id=?',
id: 1,
)3.12: Error-afhandeling
- Gebaseerd op PHP’s errors/throwables.
debug: trueinapp.jsonactiveert uitgebreide debug via de Phlo-engine.
3.13: Stijlregels
- Spaties rond
=> - Eénregelige
if/foreachzonder accolades oké; meerlijns ⇒ met accolades - Multiline argumenten ⇒ komma aan EOL
- Views:
view Naam:+ contentregel(s), geen extra indent - Geen inline accolades in CSS
4: Routing
Routing in Phlo koppelt een spatie-gescheiden pad + HTTP-methode aan een target (meestal een method).
Routes uit alle .phlo-bestanden worden verzameld; de router wordt geactiveerd met app::route().
4.1: Basisvorm
route [async|both] [GET|POST|PUT|DELETE|PATCH] pad [pad2 ...] => target
- Paden schrijf je met spaties (geen
/in de route-definitie). - Target: een directe call of statement blok.
Voorbeeld:
route GET home => $this->main
method main => view($this->home)4.2: sync / async / both
| Keyword | Gedrag |
|---|---|
| (weglaten) | Alleen sync (gewone HTTP) |
async |
Alleen async (requests van Phlo-frontend) |
both |
Sync én async toegestaan |
route both GET data => $this->loadData
route async POST items save => $this->saveItems4.3: Variabelen
Phlo parse’t ieder pad-segment. Segmenten die met $ beginnen zijn variabelen met extra mogelijkheden:
4.3.1 Verplicht (doorgeven aan target)
route GET user $id => $this->showUser($id)
method showUser($id) => view($this->profile)
4.3.2 Optioneel presence met ? → boolean
route GET search $full? => $this->search($full)
- Matcht
/searchen/search/full. $fullis true wanneer het segmentfullaanwezig is, anders false. (De implementatie checkt letterlijk of het request-segment gelijk is aan de naam zonder?.)
4.3.3 Rest (variabele lengte) met =*
route GET file $path=* => $this->serveFile($path)
- Matcht alle resterende segmenten als één string in
$path.
4.3.4 Default-waarde met =
route GET page $slug=home => $this->page($slug)
- Zonder segment →
$slug = 'home'. - Met segment →
$slug = '<waarde>'.
4.3.5 Lengte-eis met .N
route GET code $pin.6 => $this->enter($pin)
- Matcht alleen als de lengte van
$pinexact 6 is.
4.3.6 Keuzelijsten met :a,b,c
route GET report $range:daily,weekly,monthly => $this->report($range)
- Het segment moet één van de opgegeven waarden zijn.
- Bij afwezigheid (leeg) en met default wordt de default toegepast.
- Anders faalt de match.
Je kunt deze vormen combineren. Voorbeelden:
# enum + verplicht id
route GET export $fmt:csv,json $id => $this->export($fmt, $id)
# enum + default
route GET theme $name:light,dark=light => $this->theme($name)4.4: Payload check met `@`
Je specificeert exacte body-keys met één @ en comma-separated lijst.
De router vergelijkt dit 1-op-1 met de keys uit %payload (exacte set; volgorde zoals de engine die aanlevert).
route POST user @name,email => $this->createUser
method createUser => dx(%payload->name, %payload->email)
Body-keys bind je niet als method-parameters; je leest ze via
%payload.
4.5: Targets
Lokale method
route GET profile show => $this->show
method show => view($this->profile)
Externe class-method (static)
route GET api version $major => api::getVersion($major)
- Static calls: haakjes verplicht, ook zonder arguments.
- Geef pad-variabelen expliciet door.
4.6: Router activeren
Routes worden pas gematcht na:
app::route()
Plaats deze call bijvoorbeeld in app.phlo (of een andere centrale controller) na je app initialisatie en voor een fallback voor 404 afhandeling.
4.7: Aanbevolen structuur
- Zet routes bovenaan per bestand.
-
Houd pad en methodenaam logisch in lijn:
route GET users list => $this->listUsers route POST users add => $this->addUser - Variabelen altijd doorgeven in target.
bothalleen wanneer een endpoint bewust zowel sync als async moet zijn.- Gebruik keuzelijsten
:…i.p.v. losseif-takken voor vaste varianten.
5: Views
In Phlo definieer je views direct in .phlo-bestanden. Een view is een named of naamloos blok dat met view begint en HTML (plus minimale Phlo-constructies) bevat.
5.1: Declaratie
-
Named
view home: <h1>Welkom</h1> -
Naamloos → de naam is automatisch
viewview: <p>Test</p>
5.2: Arguments
Views kunnen arguments hebben (incl. defaults):
view($x = 0):
<p>Value: $x</p>
view detail($input):
<p>Input: $input</p>
Aanroepen:
method show => view($this->detail('abc'))
method show2 => view($this->view(5))
Net als bij methods zijn haakjes verplicht zodra je arguments meegeeft.
5.3: Singleline en multiline
-
Singleline view
view: <p>test</p> -
Multiline view: het blok eindigt bij een lege regel
view: <p>Line 1</p> <p>Line 2</p> view nextView: <p>Etc</p>
5.4: Shorthand HTML
Compacte HTML-shorthand wordt automatisch omgezet:
# shorthand
<p#id.class1.class2/>
# equivalent
<p id="id" class="class1 class2"></p>
- Een trailing slash op een tag zorgt ervoor dat de sluittag automatisch wordt geplaatst.
5.5: Variabelen en expressies
5.5.1 Directe variabelen en single properties
Je mag direct in de view schrijven:
view($name):
<p>Hallo $name, het is nu $this->time.</p>
Direct toegestaan: gewone variabelen en enkelvoudige property-toegang (zoals
$this->time). Geen chained access of function/method calls direct in de HTML.
5.5.2 Functies, methods, chained of complexe expressies
Gebruik expressie-markers:
view($x = 1):
<p>{{ $this->call('test') }}</p>
<p>{( $x > 1 ? 'Meerdere' : 'Enkele' )}</p>
<p>{{ $this->members->all }}</p>
{{ … }}en{( … )}zijn voor evaluatie van functies/methods/complexe of chained expressies.- Gebruik deze markers in tekst en in attribute-waarden (zie 5.6).
(We leggen hier bewust geen extra semantiek op deze twee vormen; beide zijn bedoeld voor inline expressies.)
5.6: Attribuutwaarden
Attribuutwaarden mogen zonder quotes als ze géén spaties, @ of variabelen bevatten:
view:
<p title=correct>Correcte antwoord</p>
<a href=/test1 data-value="$this->value">Link</a>
<a href=/test2 data-value="{{ $this->compute('value') }}">Link</a>
- Met variabelen of expressies ⇒ gebruik quotes (of een expressiemarker binnen quotes).
5.7: Statements
Gebruik control-flow via tags in de HTML:
view:
<p>Lijst:</p>
<foreach $this->list AS $key => $value>
<p>Item: $key</p>
<if $value > 1>
<p>Hoge waarde: $value</p>
<elseif $value === 1>
<p>Precies 1: $value</p>
<else>
<p>Andere waarde: $value</p>
</if>
</foreach>
foreachopent met<foreach …>en sluit met</foreach>.if/elseif/elsesluiten samen af met</if>.
5.8: View renderen
Een view render je met view($this->Naam) (of de naamloze view) als call.
Deze call stuurt de output en beëindigt het script. Hetzelfde geldt voor apply().
route GET home => $this->home
method home => view($this->home)
view home:
<h1>$this->title</h1>
Niet doen:
# ❌ fout – views “aan elkaar plakken” bestaat niet; de eerste call beëindigt.
method dashboard {
view($this->header)
view($this->content)
}
Wil je meerdere stukken output: maak één view die ze samenvoegt, of bouw het in de view zelf op.
5.9: Best practices
- Houd views klein en overzichtelijk; splits grote delen op.
- Gebruik directe variabelen/single properties in de HTML; alles wat complexer is gaat tussen
{{ … }}of{( … )}. - Sluit multiline views af met een lege regel; gebruik singleline waar het kan.
- Gebruik shorthand voor compacte markup, maar hou het leesbaar.
- Onthoud dat
view()enapply()terminerend zijn: geen “plak-rendering”.
6: CSS
Phlo gebruikt een compacte, puntkomma-vrije CSS-syntaxis binnen <style>-blokken.
Je schrijft regels met dubbele punten als scheiding:
selector: property: value- genest:
A: B: property: value→ output:A B { property: value; }
Regels:
- Één regel = één declaratie. Geen puntkomma’s in de bron; de engine voegt ze toe.
- Dubbele punt
:scheidt ketenniveaus én property van value. - Backslash
\in nestings “plakt” het volgende selector-deel aan de parent (glue). Dat volgende deel kan een pseudo (:…), attribute-selector ([…]), enz. zijn. @media (…)mag ín een selector-blok staan; Phlo hoist dit naar de juiste plek met behoud van de huidige selector.
6.1: `<style>`-blok
<style>
html: height: 100dvh
body {
background: #947b6c
font-family: Sans-serif
p: line-height: 2em
}
</style>
Output (conceptueel):
html { height: 100dvh; }
body { background: #947b6c; font-family: Sans-serif; }
body p { line-height: 2em; }6.2: Keten & groepen
Keten met dubbele punten; groepeer met komma — de volledige context wordt per item toegepast.
<style>
body: h1, p: \:first-letter: color: green
</style>
- Context:
body - Doelen:
h1enp(met gelijmde:first-letter) - Backslash vóór
:first-letterlijmt dat deel aan de voorgaande selector binnen de keten.
Output:
body h1:first-letter,
body p:first-letter { color: green; }6.3: Media queries ín selector
Je mag @media (…) binnen het selector-blok schrijven; Phlo verplaatst het naar de juiste plek en behoudt de selector-context:
<style>
h1 {
color: white
@media (max-width: 768px): color: black
}
</style>
Output:
h1 { color: white; }
@media (max-width: 768px){
h1 { color: black; }
}6.4: Variabelen
Phlo ondersteunt CSS-variabelen via $namen.
Je kunt variabelen definiëren in :root, of op elk ander niveau — maar :root is gebruikelijk voor globale theming.
<style>
:root {
$background: #0d0d0d
$surface: #1a1a1a
$text: #ffffff
$accent: #ff4a00
}
body {
background: $background
color: $text
}
button {
background: $accent
color: $text
}
</style>
Output
:root {
--background: #0d0d0d;
--surface: #1a1a1a;
--text: #ffffff;
--accent: #ff4a00;
}
body {
background: var(--background);
color: var(--text);
}
button {
background: var(--accent);
color: var(--text);
}
👉 Phlo zet $variabelen automatisch om naar --custom-properties en gebruikt var(--...) bij aanroepen.
Je kunt variabelen overal hergebruiken — ook binnen media queries en nested selectors.
6.5: Dynamische variabelen
Phlo’s frontend engine bevat de library DOM/CSS.var, waarmee je gedefinieerde $variabelen in CSS direct kunt benaderen en aanpassen vanuit JavaScript, via het globale app.var object.
Elke $variabele in je CSS wordt automatisch beschikbaar onder app.var.<naam>.
Voorbeeld
<style>
:root {
$background: #0d0d0d
$text: #ffffff
}
</style>
<script>
app.var.background = '#000000'
const textColor = app.var.text
</script>
app.var.background = '#000000'→ past live de waarde van--backgroundin de DOM aan, zonder rebuild of reload.const textColor = app.var.text→ leest de huidige waarde terug.
👉 Deze aanpassingen werken real-time in de browser en beïnvloeden direct alle elementen die de variabele gebruiken.
Je kunt dit gebruiken voor o.a.:
- Themawissels (dark/light mode)
- Dynamisch aanpassen van accentkleuren op basis van user input
- Interactieve UIs zonder aparte CSS klassen togglen
Werking
- De CSS-engine zet
$backgroundom naar--background. - De frontend-engine leest/schrijft deze via
document.documentElement.style. app.varbiedt een eenvoudig proxy-object zodat je kunt werken alsof het gewone JS-properties zijn.
6.6: Volledig voorbeeld
Input
html: height: 100dvh
body {
background: #947b6c
font-family: Sans-serif
p: line-height: 2em
}
body: h1, p: \:first-letter: color: green
h1 {
color: white
@media (max-width: 768px): color: black
}
p {
color: navy
\:last-child: color: yellow
}
Output
body {
background: #947b6c;
font-family: Sans-serif;
}
body h1:first-letter,
body p:first-letter {
color: green;
}
body p {
line-height: 2em;
}
h1 {
color: white;
}
html {
height: 100dvh;
}
p {
color: navy;
}
p:last-child {
color: yellow;
}
@media (max-width: 768px){
h1 {
color: black;
}
}6.7: Best practices
- Gebruik
$variabelenvoor kleuren, spacing en fonts — dit maakt theming en dark/light modes eenvoudig. - Definieer globale thema-variabelen in
:root. - Gebruik selector-ketens en grouping voor compacte, leesbare code.
- Plaats
@mediagewoon in het blok; Phlo zet ze netjes op de juiste plek. - Gebruik
\in nestings om pseudo’s of attributen vast te plakken aan de parent selector. - Geen puntkomma’s in je code; Phlo zorgt voor correcte CSS-output.
7: ORM
Phlo bevat een krachtige ingebouwde ORM waarmee je database-tabellen als klassen definieert.
Modellen kunnen snel via columns of uitgebreid via een declaratief schema.
Records worden als instances behandeld, met ondersteuning voor props, methods, views, relaties en meerdere database-engines.
7.1: Basisprincipes
Een ORM-model is een .phlo-bestand met:
@ class:naam van het model (en tabel)@ extends: modelstatic tableencolumnsofschema- Optioneel: relaties (
parent,child,many) - Props, methods en views werken per record-instance
Voorbeeld:
@ class: user
@ extends: model
view => $this->name
static table = 'users'
static columns = 'id,name,email,active,created'7.2: Modellen definiëren
7.2.1 Plat met columns (snel en licht)
Gebruik columns voor eenvoudige tabellen:
@ class: shipment
@ extends: model
view: $this->destination ($this->user)
static table = 'shipments'
static order = 'changed DESC'
static columns = 'id,user,destination,costs,valid,weight,shipped,created,changed'
static objParents = ['user' => 'user']
@ class: user
@ extends: model
view => $this->name
static table = 'users'
static order = 'changed DESC'
static columns = 'id,name,email,level,active,created,changed'
static objChildren = ['shipments' => 'shipment']
7.2.2 Met schema en field(...) (rijk en declaratief)
Met schema definieer je velden, relaties en UI in één keer:
@ class: shipment
@ extends: model
view: $this->destination ($this->user)
static table = 'shipments'
static schema => arr (
id: field (type: 'token', length: 4, title: 'ID'),
destination: field (type: 'text', required: true, search: true),
user: field (type: 'parent', obj: 'user', required: true),
costs: field (type: 'price', prefix: '€ '),
valid: field (type: 'bool'),
attachments: field (type: 'child', obj: 'attachment', list: true),
)
@ class: user
@ extends: model
view => $this->name
static table = 'users'
static schema => arr (
id: field (type: 'token'),
name: field (type: 'text', search: true, required: true),
email: field (type: 'email', required: true),
shipments: field (type: 'child', obj: 'shipment'),
groups: field (type: 'many', obj: 'group', table: 'user_groups'),
)
schemais vooral krachtig in combinatie met PhloCMS, maar werkt ook standalone.
7.3: CRUD
# Ophalen
$user = user::record(id: 1)
$list = shipment::records(order: 'created DESC')
# Aanmaken
$shipment = shipment::create(destination: 'Parijs', user: 1)
# Bewerken & opslaan
$shipment->destination = 'Lyon'
$shipment->objSave
# Verwijderen
shipment::delete('id=?', $shipment->id)
record(...)→ enkel record (of null)records(...)→ array van records (class instances)create(...)→ insert + instant ophalenobjSave→ instance opslaan (insert/update afhankelijk van id)delete(...)→ statisch verwijderen met SQL-where
7.4: Relationele navigatie
Relaties zijn beschikbaar via properties:
| Type | Declaratie | Gebruik |
|---|---|---|
| parent | type: parent |
$shipment->user |
| child | type: child |
$user->shipments |
| many | type: many |
$user->groups |
Many-to-many
type: many gebruikt een pivot-tabel:
groups: field (
type: 'many',
obj: 'group',
table: 'user_groups',
)
Navigatie:
$user = user::record(id: 1)
foreach ($user->groups as $group)
echo $group->title
Relaties worden batch-loaded voor performance. Er vinden geen cross-DB joins plaats; elke class laadt uit zijn eigen engine.
7.5: Instance dynamiek
Elke record is een echte instance van je modelklasse. Je kunt props, methods en views gebruiken om virtuele velden, berekeningen of representaties toe te voegen:
@ class: shipment
@ extends: model
prop summary => $this->destination.' ('.$this->user.')'
method tax => $this->costs * 0.21
view:
<p>$this->summary</p>
<p>{( $this->tax )}</p>
Gebruik:
$shipment = shipment::record(id: 'AB12')
echo $shipment->summary
echo $shipment # gebruikt view als string
Props en methods werken altijd op de record-instance, niet statisch.
7.6: Filtering en queries
Alle querymethodes accepteren named arguments en SQL-achtige filters:
shipment::records(destination: 'Parijs')
shipment::records(where: 'valid=1 AND weight>10')
shipment::pair(columns: 'id,destination')
Ondersteund: where, order, group, joins, caching en schema-bewuste kolommen.
7.7: Caching en prestaties
De ORM gebruikt interne buffers (objRecords, objLoaded) voor relationele lookups en optionele APCu caching via:
static objCache = true # 1 dag
# of
static objCache = 600 # 10 minuten
Records en relaties worden per batch geladen.
Gebruik records() voor bulkselecties i.p.v. record() in lussen.
7.8: Meerdere engines
Phlo ondersteunt meerdere backends via prop DB.
Standaard is %MySQL, maar je kunt elke engine per model instellen.
SQLite
prop DB => %SQLite(data.'users.db')
@ class: notes
@ extends: model
prop DB => %SQLite(data.'notes.db')
static table = 'notes'
static columns = 'id,title,body'
PostgreSQL
prop DB => %PostgreSQL
@ class: invoices
@ extends: model
prop DB => %PostgreSQL
static table = 'invoices'
static columns = 'id,customer_id,total,created'
Tabellen op verschillende engines kunnen gecombineerd worden in relaties; elke class haalt zijn eigen data op.
/data/creds.ini
Voor engines zoals MySQL en PostgreSQL plaats je je credentials in:
/data/creds.ini
[mysql]
host = localhost
database = db_name
user = db_user
password = db_password
[postgresql]
host = localhost
database = my_pg_db
user = pg_user
password = pg_pass
Phlo laadt deze automatisch via %creds->....
7.9: Overzicht functies
| Functie / Property | Type | Beschrijving |
|---|---|---|
record(...) |
static | Haalt 1 record (of null) |
records(...) |
static | Haalt meerdere records (array) |
create(...) |
static | Insert + ophalen |
objSave |
instance | Insert of update |
delete(where, …) |
static | Verwijderen |
pair, item, column |
static | Snelle queryhelpers |
objParents / schema: parent |
declaratief | Parent-relaties |
objChildren / schema: child |
declaratief | Child-relaties |
schema: many |
declaratief | Many-to-many |
objCache |
static | Optionele APCu caching |
prop DB |
static | Per-model engine |
7.10: Best practices
- Gebruik
columnsvoor snelle, simpele modellen. - Gebruik
schemavoor rijke definities en CMS-integratie. - Definieer een
viewvoor stringrepresentaties. - Gebruik props voor virtuele velden.
objSavei.p.v.save.- Gebruik
records()bij bulkloads. - Scheid modellen per engine met
prop DB. - Gebruik credentials in
/data/creds.ini. - Houd modellen declaratief; logica in props/methods.
8: Instance Management
Phlo gebruikt een eigen instance manager om objecten efficiënt en voorspelbaar te initialiseren en te hergebruiken. Dit systeem bepaalt wanneer controllercode wordt uitgevoerd, hoe instanties worden opgeslagen en hoe cirkelreferenties worden voorkomen.
8.1: Basisuitleg
Wanneer je een .phlo-bestand definieert, wordt dit tijdens de buildfase omgezet naar een class.
Elke aanroep naar een object via %naam gaat via de instance manager (phlo() in /phlo/phlo.php).
Voorbeeld:
prop title = 'Welkom'
route GET home => $this->main
method main => view($this->home)
view home:
<h1>$this->title</h1>
Wanneer de route /home wordt aangeroepen:
- De instance manager kijkt of er al een instantie van deze class bestaat.
- Zo niet, dan wordt deze aangemaakt en opgeslagen.
- Na het aanmaken wordt de controllercode uitgevoerd (zie §8.2).
- Vervolgens wordt de gevraagde method aangeroepen.
8.2: Controllercode
Alle code in een .phlo-bestand die niet onder route, prop, static, method, function, view, <style> of <script> valt, is controllercode.
Deze code wordt na instantiatie uitgevoerd, zodra de instantie volledig bestaat.
Voorbeeld:
prop ready = false
%session->start() # controllercode (top-level)
$this->ready = true
- Deze code draait één keer bij de eerste aanmaak van de instantie.
- Het verschil met
__constructis dat de instantie volledig bestaat op het moment dat controllercode wordt uitgevoerd → zo voorkom je cirkelreferenties en incomplete objecten.
8.3: De rol van `__handle()`
Phlo genereert voor elke class een speciale __handle() methode.
Deze wordt aangeroepen door de instance manager wanneer een instantie wordt opgevraagd via %naam.
__handle():
- voert de controllercode uit als dat nog niet gebeurd is;
- zorgt dat props en statics correct beschikbaar zijn;
- zorgt voor caching van de instantie voor volgende aanroepen.
Je hoeft __handle() zelf niet aan te roepen of te overschrijven — het is onderdeel van de gegenereerde klasse en de instance manager.
8.4: Lazy initialisatie
Omdat controllercode pas na constructie wordt uitgevoerd, kunnen instanties naar elkaar verwijzen zonder dat er ongewenste recursieve aanmaak plaatsvindt.
Voorbeeld:
# file a.phlo
prop message = 'A ready'
# file b.phlo
prop message = 'B ready'
# file main.phlo
route GET test => $this->show
method show {
dx(%a->message, %b->message)
}
%aen%bworden lazy aangemaakt.- De controllercode in beide bestanden draait zodra hun instantie volledig bestaat.
- Je kunt probleemloos onderling verwijzen, want de instantie bestaat al vóór de controllercode draait.
8.5: Best practices
- Gebruik controllercode voor initiële setup, niet voor request-afhankelijke logica.
- Zet controllercode bovenaan of direct onder props voor overzicht.
- Vermijd side-effects in
__construct; gebruik controllercode i.p.v. custom constructors. - Laat instanties zichzelf lazy initialiseren via
%naamin plaats van handmatige aanmaak. - Gebruik controllercode bewust om cirkelreferenties op te lossen.
9: Tooling & CLI
Phlo heeft een zeer lichtgewicht toolchain. Er is geen externe compiler of CLI-framework nodig: alles draait op de Phlo-engine zelf in /phlo/.
Tijdens requests zorgt Phlo automatisch voor het bouwen en updaten van de gegenereerde PHP-, JS- en CSS-bestanden. Je kunt dit proces handmatig starten voor CI/CD of pre-release builds, maar in normale setups is dat niet nodig.
9.1: Buildproces
Phlo transpileert .phlo-bestanden naar PHP, JS en CSS.
Dit gebeurt:
- Automatisch bij wijzigingen tijdens requests (JIT-build).
- Optioneel handmatig, door
phlo_build()aan te roepen (bijv. vanuit een script of CI).
De entrypoints:
/www/app.php– centrale PHP entrypoint/www/app.js– automatisch gegenereerde frontend bundle/www/app.css– automatisch gegenereerde CSS bundle
Bij elke request kijkt Phlo:
- of er wijzigingen zijn in de bronbestanden,
- of de gegenereerde bestanden up-to-date zijn,
- en voert zo nodig een incrementele build uit.
9.2: CLI
Phlo heeft geen extern CLI-framework nodig; je kunt gewoon php gebruiken.
Voor speciale situaties (prebuilds, CI, staging) kun je handmatig builden door:
php -r "require 'phlo/build.php'; phlo_build();"
of vanuit een eigen PHP-script:
<?php
require 'phlo/build.php';
phlo_build();
Dit doorloopt hetzelfde buildproces als bij runtime, maar dan buiten de request context.
Er is dus geen
phloCLI command of package. Je gebruikt gewoon PHP zelf.
9.3: Debugmodus
Debugmodus wordt ingesteld in data/app.json via:
{
"debug": true
}
In debugmodus:
- Worden fouten en backtraces weergegeven in de browser.
- Worden wijzigingen aan
.phlo-bestanden direct herbouwd. - Is de debug-overlay van de frontend actief (indien geconfigureerd).
9.4: Deployment
Voor productie:
- Zet
"debug": falseindata/app.json. - Voer een handmatige
phlo_build()uit zodat alle bestanden vooraf zijn getranspileerd. - Zet de
/www/map online (inclusiefapp.php,app.js,app.css). - Zorg dat de
/php/map met gegenereerde backend code meeverhuist.
Phlo heeft geen dependencies op npm, webpack of andere toolchains. Alle tooling is ingebouwd en draait direct onder PHP.
9.5: Best practices
- Laat het buildproces zoveel mogelijk automatisch lopen; handmatig builden alleen voor release of CI.
- Wijzig niets handmatig in
/www/app.jsof/www/app.css— deze worden altijd overschreven. - Gebruik
php -r "require ...; phlo_build();"voor precompilatie in CI. - Zet
"debug": falsevoor productie om performance overhead te minimaliseren. - Hou de
/phlo/folder up-to-date in je deployment, aangezien daarin de engine zit.
✅ Juiste uitleg buildproces (JIT + handmatig via phlo_build())
✅ Geen fake CLI commands
✅ Debugmodus zoals in app.json
✅ Deployment zoals Phlo dat echt doet
✅ Correcte omgang met /www/app.js en /www/app.css (nooit handmatig wijzigen)
10: Vertalingen
Phlo heeft ingebouwde ondersteuning voor meertaligheid en dynamische vertaling. De engine bevat functies en libraries waarmee je teksten inline kunt vertalen, taalwissels kunt toepassen, en async vertalingen kunt laden zonder de gebruikerservaring te onderbreken.
10.1: Taalhelpers
Phlo biedt voor de meest gebruikte talen korte helperfuncties, die je direct in views en code kunt gebruiken:
nl()– Nederlandsen()– Engelsde()– Duitsfr()– Franses()– Spaans
Voorbeeld in een view:
view:
<p>{( nl('Hallo wereld') )}</p>
<p>{( en('Hello world') )}</p>
De taalhelpers:
- geven direct de string terug in de betreffende taalcontext,
- worden vooral gebruikt voor meertalige contentblokken en statische teksten.
10.2: Overig gebruik
Voor dynamische vertalingen biedt Phlo twee kernfuncties: translate() en translation().
translate()
- Synchronous, geeft de vertaling van een string onmiddellijk terug in de huidige taal.
- Handig voor teksten die al vertaald in het systeem staan.
<p>{( translate('welcome_message') )}</p>
translation()
- Asynchronous, bedoeld voor dynamische of nog-niet-geladen vertalingen.
- Eerst wordt de brontekst weergegeven, daarna wordt de vertaling geladen en in de UI bijgewerkt.
- De vertaling wordt teruggegeven in de huidige actieve taalcode van de app.
<p>{( translation('dynamic_intro_text') )}</p>
- Dit voorkomt vertraging bij het renderen van pagina’s.
- De frontend engine voert op de achtergrond een vertaalrequest uit en past de tekst aan zodra de vertaling beschikbaar is.
10.3: Dynamische taalkeuze
De actieve taal kan dynamisch ingesteld en gewijzigd worden via de frontend. Als de gebruiker van taal wisselt, wordt de UI automatisch hervertaald zonder reload.
Typisch verloop:
- App start met een standaardtaal (bijv.
nl). - De gebruiker kiest een andere taal.
- De frontend engine wisselt de taalcode.
- Teksten die via
translation()zijn ingeladen, worden hervertaald zodra de nieuwe taal beschikbaar is. - Teksten via helpers zoals
nl()ofen()blijven hun vaste waarde tonen.
10.4: Best practices
- Gebruik
translate()voor teksten die je lokaal of statisch beheert. - Gebruik
translation()voor teksten die van externe bronnen komen of async geladen worden. - Gebruik taalhelpers (
nl(),en(), …) voor statische contentblokken of vaste viewteksten. - Houd je vertaalkeys consequent en gestructureerd (bijv.
page.home.introi.p.v. losse strings). - Zet de taalwisseling centraal in de app zodat alle UI-elementen consistent meeveranderen.
- Combineer vertaalfuncties met Phlo’s frontend engine voor een soepele taalwisseling zonder reloads.
11: Geavanceerd
Phlo is ontworpen als een modulair systeem. Je kunt delen van de engine los inzetten, combineren met bestaande projecten of uitbreiden met eigen functionaliteit, zonder de kern te hoeven wijzigen.
11.1: Modulair gebruik
Phlo kan zowel als volledig framework worden ingezet, als in delen. Bijvoorbeeld:
- Alleen de frontend engine gebruiken, zonder backend.
- Alleen de backend transpiler gebruiken, bijvoorbeeld als classgenerator of assetbundler.
- Alleen specifieke libraries (
/phlo/libs) inzetten in een bestaand PHP-project. - Phlo gebruiken als router + view engine, maar je eigen database-laag behouden.
Voorbeeld: alleen de frontend gebruiken in een statische site
<script src="/app.js" defer></script>
en vervolgens Phlo-frontend functies aanspreken zoals app.apply() en app.state zonder .phlo backend.
Of: Phlo opnemen in een bestaande PHP-applicatie en slechts een paar .phlo bestanden in sources toevoegen.
Phlo transpileert die dan naar /php/, waarna je de gegenereerde classes direct kunt gebruiken.
11.2: Integratie
Je kunt Phlo integreren in een bestaande codebase door:
- De Phlo engine in een submap te plaatsen, bijv.
/vendor/phlo/. app.phptoe te voegen als centrale router voor alle niet-bestaande routes.- In
app.jsonhetbuild.sources-veld aan te vullen met de paden van je bestaande code, naast de Phlo mappen.
Voorbeeld data/app.json:
{
"id": "LegacyApp",
"version": ".1",
"host": "localhost",
"dashboard": "phlo",
"debug": true,
"build": {
"libs": ["session", "json"],
"sources": [
"%app/",
"/legacy/phlo/"
]
}
}
Hiermee kun je bestaande PHP code behouden en toch .phlo bestanden toevoegen.
Phlo transpileert die en voegt ze toe aan je project, zonder dat je je hele structuur hoeft om te gooien.
11.3: Best practices
- Houd aangepaste commando’s klein en herbruikbaar.
- Voeg integratie stapsgewijs toe; begin bijvoorbeeld met alleen routing of views.
- Zet aparte modules in
/phlo/libsals je ze vaker gebruikt, i.p.v. ze los in te bakken. - Gebruik
sourcesom meerdere codebases te combineren zonder duplicatie. - Test custom apply-commando’s goed in combinatie met Phlo’s update-mechanisme.
12: Bijlagen
De bijlagen bevatten kant-en-klare voorbeelden, projecttemplates en extra showcases die laten zien hoe Phlo in de praktijk wordt toegepast. Dit onderdeel is bedoeld als naslagwerk en inspiratiebron — niet als primaire documentatie.
12.1: Codevoorbeelden
Een verzameling kleine, goed leesbare .phlo-fragmenten die veelgebruikte patronen illustreren:
Basisroute met view
prop title = 'Welkom'
route GET home => $this->main
method main => view($this->home)
view home:
<h1>$this->title</h1>
Route met variabele
route GET user $id => $this->show($id)
method show($id) {
$user = %users->record(id: $id)
view($this->profile, $user)
}
view profile($user):
<h1>$user->name</h1>
Async vertaling
view:
<p>{( translation('welcome_text') )}</p>12.2: Projecttemplate
Een minimale projectstructuur met alle verplichte onderdelen:
/www/
app.php ← centraal entrypoint
app.js ← gegenereerde frontend bundle
app.css ← gegenereerde CSS
/data/
app.json ← verplichte configuratie
/phlo/ ← Phlo engine
/php/ ← getranspileerde backend code (automatisch)
/app/ ← je Phlo bronbestanden (*.phlo)
Dit is de aanbevolen basisstructuur.
Extra sources kunnen in data/app.json worden gedefinieerd als je meerdere bronpaden wilt combineren.
12.3: CSS Showcase
Een visueel voorbeeld van Phlo’s compacte CSS-syntaxis:
Input
<style>
html: height: 100dvh
body {
background: #947b6c
font-family: Sans-serif
p: line-height: 2em
}
body: h1, p: \:first-letter: color: green
h1 {
color: white
@media (max-width: 768px): color: black
}
p {
color: navy
\:last-child: color: yellow
}
</style>
Output
body {
background: #947b6c;
font-family: Sans-serif;
}
body h1:first-letter,
body p:first-letter {
color: green;
}
body p {
line-height: 2em;
}
h1 {
color: white;
}
html {
height: 100dvh;
}
p {
color: navy;
}
p:last-child {
color: yellow;
}
@media (max-width: 768px){
h1 {
color: black;
}
}12.4: ORM Modellen
Voorbeeld van een typische ORM-type-definitie in Phlo:
type users {
static columns = null
field(id, type: number, auto: true)
field(name, type: text)
field(email, type: text)
}
route GET users => $this->overview
method overview {
foreach (%users->records(order: 'name') as $u)
dx($u->id, $u->name)
}
Enkel record ophalen:
$user = %users->record(id: 1)
dx($user->name)12.5: Verdere bronnen
- phloCMS — volledige CMS gebouwd op Phlo
- phloWS — Web Services laag
- phloWA — WhatsApp Gateway module
- demo.zip — bevat o.a. een showcase van CSS, views en routing