9: Instance Management
Phlo uses its own instance manager to initialize and reuse objects efficiently and predictably. This system determines when controller code runs, how instances are stored, and how circular references are prevented.
9.1: The basics
When you define a .phlo file, the build phase turns it into a class.
Every call to an object via %name goes through the instance manager (phlo() in /phlo/phlo.php).
Example:
prop title = 'Welcome'
route GET home => $this->main
method main => view($this->home)
view home:
<h1>$this->title</h1>
When the route /home is requested:
- The instance manager checks whether an instance of this class already exists.
- If not, it is created and stored.
- After creation, the controller code runs (see §8.2).
- Then the requested method is called.
9.2: Controller code
All code in a .phlo file that does not belong to route, prop, static, method, function, view, <style> or <script> is controller code.
This code runs after instantiation, once the instance fully exists.
Example:
prop ready = false
%session->start()
$this->ready = true
The last two lines are controller code because they sit at top level.
- This code runs once, when the instance is first created.
- The difference from
__constructis that the instance fully exists by the time controller code runs, which prevents circular references and incomplete objects.
9.3: The role of `__handle()`
Phlo generates a special __handle() method for every class.
The instance manager calls it whenever an instance is requested via %name.
__handle():
- runs the controller code if it hasn't run yet;
- makes sure props and statics are available correctly;
- caches the instance for subsequent calls.
You never call or override __handle() yourself; it is part of the generated class and the instance manager.
9.4: Lazy initialization
Because controller code runs only after construction, instances can reference each other without triggering unwanted recursive creation.
Example:
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)
}
%aand%bare created lazily.- The controller code in both files runs once their instance fully exists.
- You can reference instances from each other freely, because the instance already exists before its controller code runs.
9.5: The obj base class: powertools
Every compiled class extends obj, and obj is more than __get/__set. These are the tools you reach for when a class needs to behave dynamically.
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. The same applies to computed statics, cached per class.
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 compiles 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
- Use controller code for initial setup, not for request-dependent logic.
- Place controller code at the top or directly below props for readability.
- Avoid side effects in
__construct; use controller code instead of custom constructors. - Let instances initialize themselves lazily via
%nameinstead of creating them manually. - Use controller code deliberately to resolve circular references.