9: Instantiebeheer
Phlo gebruikt zijn eigen instance manager om objecten efficiënt en voorspelbaar te initialiseren en opnieuw te gebruiken. Dit systeem bepaalt wanneer controller code wordt uitgevoerd, hoe instanties worden opgeslagen en hoe circulaire referenties worden voorkomen.
9.1: De basisprincipes
Wanneer je een .phlo-bestand definieert, verandert de build-fase het in een klasse. Elke aanroep naar een object via %name gaat via de instance manager (phlo() in /phlo/phlo.php).
Voorbeeld:
prop title = 'Welcome'
route GET home => $this->main
method main => view($this->home)
view home:
<h1>$this->title</h1>
Wanneer de route /home wordt aangevraagd:
- De instantiebeheerder controleert, via de identiteit die
__handle()(of de standaardregel) afleidt van de argumenten, of er al een bijpassende instantie bestaat. - Zo niet, dan wordt deze gemaakt en opgeslagen.
- Na creatie wordt de controllercode uitgevoerd (zie §8.2).
- Vervolgens wordt de aangevraagde methode aangeroepen.
9.2: Controller code
Alle code in een .phlo-bestand die niet behoort tot route, prop, static, method, function, view, <style> of <script> is controller code. Deze code wordt uitgevoerd na de instantie, zodra de instantie volledig bestaat.
Voorbeeld:
prop ready = false
%session->start()
$this->ready = true
De laatste twee regels zijn controllercode omdat ze op het hoogste niveau staan.
- Deze code wordt uitgevoerd na de constructie, elke keer dat de manager een nieuwe instantie aanmaakt: eenmaal voor een gedeelde instantie, bij elke aanroep voor klassen waarvan
__handle()nullretourneert. - Het verschil met
__constructis dat de instantie volledig bestaat tegen de tijd dat de controllercode wordt uitgevoerd, wat cirkelreferenties en onvolledige objecten voorkomt.
9.3: De rol van `__handle()`: instantie-identiteit
__handle() is een optionele statische die je zelf definieert om de identiteit van instanties te beheersen. Het is verweven met __construct: de transpiler voegt de parameterlijst van je constructor eraan toe, zodat de handle-expressie spreekt in constructorargumenten, en de instantiebeheerder roept het aan met dezelfde argumenten waarmee het op het punt staat te construeren:
static __handle => "img/$file"
method __construct(public string $file)
De retourwaarde bepaalt hoe de manager de instantie behandelt:
__handle() retourneert |
Betekenis |
|---|---|
| een string | De registersleutel: elke aanroep die dezelfde sleutel produceert, retourneert dezelfde gedeelde instantie (multiton op argument). %img voor hetzelfde bestand is één object; een vlag kan ook deel uitmaken van de identiteit, zoals in "INI/$path$filename".(!$parse ? '/0' : void). |
null |
Nooit gecached: elke aanroep construeert een nieuwe instantie (cookiewall, field). |
true |
Hergebruik de instantie die geregistreerd is onder de klassenaam en objImport(...) de nieuwe argumenten erin. |
Een klasse zonder __handle() volgt de standaardregel: zonder argumenten aangeroepen is het een singleton die is gekeyed op zijn naam; met argumenten aangeroepen is het een nieuwe, niet-geregistreerde instantie.
Je definieert __handle(); de instantiebeheerder roept het aan. Roep het nooit zelf aan.
9.4: Luie initialisatie
Omdat controllercode alleen na constructie wordt uitgevoerd, kunnen instanties elkaar verwijzen zonder ongewenste recursieve creatie te activeren.
Voorbeeld:
a.phlo:
prop message = 'A ready'
b.phlo:
prop message = 'B ready'
main.phlo:
route GET test => $this->show
method show {
dx(%a->message, %b->message)
}
%aen%bworden lui aangemaakt.- De controllercode in beide bestanden wordt uitgevoerd zodra hun instantie volledig bestaat.
- Je kunt vrijelijk naar instanties van elkaar verwijzen, omdat de instantie al bestaat voordat de controllercode wordt uitgevoerd.
9.5: The obj base class: powertools
Every transpiled class extends obj, and obj is more than __get/__set. These are the tools you reach for when a class needs to behave dynamically.
The ad-hoc value object. obj is also the everyday container you create directly: obj(x: 1, y: 2) gives you a live object in one call, no class definition needed. Reading a key that does not exist returns null instead of a warning, so optional data needs no isset dance. Assign a closure and it becomes a bound member: after $point->sum = fn() => $this->x + $this->y, both $point->sum and $point->sum() evaluate it with $this bound to the object. foreach iterates the stored data and json_encode($point) serializes exactly that data (closures and computed values stay out), so an obj passes cleanly into views, payloads and JSON responses.
$point = obj(x: 1, y: 2)
$point->sum = fn() => $this->x + $this->y
$total = $point->sum
Interception hooks. Implement objCall, objGet or objSet to trap the access chain. Returning null falls through to the normal behavior; anything non-null short-circuits:
method objGet($key) => $this->cache[$key] ?? null
method objCall($method, ...$args) => str_starts_with($method, 'find') ? $this->finder($method, $args) : null
method objSet($key, $value) => $key === 'id' ? true : null
objGet runs before data/closure/method/prop lookup on every read, objCall on every unknown method call, and objSet before every write (a non-null return swallows the write). This is the mechanism behind decorators, lazy loading and read-only guards.
Bound closures. Assign a closure and it binds to the instance: $obj->greet = fn() => "Hi $this->name", later $obj->greet() runs with $this bound. Handy for per-instance behavior without subclassing.
Data API. objImport(name: 'x', age: 3) bulk-assigns and returns $this (chainable). objKeys(), objValues() and objLength() inspect the data; objClear() wipes it. Iterating an obj (foreach $record AS $key => $value) and json_encode($record) expose exactly the stored data. Every write flips objChanged, the dirty flag the ORM uses to decide whether objSave writes anything.
Computed prop caching. prop x => ... caches on first access; the argument form caches per argument set. A source-level static x => ... is different: it transpiles to a plain static method and is recomputed on every call. Only an engine-level protected _x() fallback reached as x() uses obj::$classProps, the per-class cache that worker reset clears.
Worker persistence. prop objPers = true makes an instance survive between worker-mode requests: the phlo() registry only keeps objPers instances on its per-request reset. Right for DB connections and parsed config; wrong for anything request- or user-scoped.
Lesson. A plain prop in a parent class SHADOWS a computed prop in a child.
prop dir = voidin an abstract parent transpiles to a real PHP property, so a child'sprop dir => guidegetter is never consulted:$this->dirsilently readsvoid. When children must override with a computed prop, declare the parent prop computed as well:prop dir => void.
9.6: Best practices
- Gebruik controllercode voor initiële setup, niet voor logica die afhankelijk is van verzoeken.
- Plaats controllercode bovenaan of direct onder props voor de leesbaarheid.
__constructvangt de instantie-argumenten; houd het daarbij, idealiter met gepromote parameters (method __construct(public string $file)), omdat__handle()de identiteit van de instantie afleidt van diezelfde argumenten vóór de constructie. Zware taken horen thuis in lazy props; app-niveau opstarten in controllercode.- Laat instanties zichzelf lui initialiseren via
%namein plaats van ze handmatig te creëren. - Gebruik controllercode opzettelijk om circulaire referenties op te lossen.
Laatst bijgewerkt op 23-08-2026