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:

  1. De instantiebeheerder controleert, via de identiteit die __handle() (of de standaardregel) afleidt van de argumenten, of er al een bijpassende instantie bestaat.
  2. Zo niet, dan wordt deze gemaakt en opgeslagen.
  3. Na creatie wordt de controllercode uitgevoerd (zie §8.2).
  4. 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.

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)
}

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 = void in an abstract parent transpiles to a real PHP property, so a child's prop dir => guide getter is never consulted: $this->dir silently reads void. When children must override with a computed prop, declare the parent prop computed as well: prop dir => void.

9.6: Best practices

Laatst bijgewerkt op 23-08-2026

We gebruiken essentiële cookies om deze site te laten werken. Met uw toestemming gebruiken we ook analytics om de site te verbeteren.