9: 实例管理

Phlo 使用其自己的 instance manager 来高效且可预测地初始化和重用对象。该系统确定 何时运行控制器代码,实例如何存储,以及如何防止循环引用。

9.1: 基础知识

当你定义一个 .phlo 文件时,构建阶段将其转换为一个类。
每次通过 %name 调用一个对象时,都会经过 instance manager(在 /phlo/phlo.php 中的 phlo())。

示例:

prop title = 'Welcome'

route GET home => $this->main

method main => view($this->home)

view home:
<h1>$this->title</h1>

当请求路由 /home 时:

  1. 实例管理器通过 __handle()(或默认规则)从参数中派生的身份检查是否已经存在匹配的实例。
  2. 如果没有,则创建并存储
  3. 创建后,控制器代码运行(见 §8.2)。
  4. 然后调用请求的方法。

9.2: 控制器代码

.phlo 文件中,所有属于 routepropstaticmethodfunctionview<style><script> 的代码被称为控制器代码
这段代码在实例化后运行,一旦实例完全存在。

示例:

prop ready = false

%session->start()
$this->ready = true

最后两行是控制器代码,因为它们位于顶层。

9.3: `__handle()` 的角色:实例身份

__handle() 是一个 可选的静态 方法,您可以自己定义以控制实例的身份。它与 __construct 交织在一起:转译器将您的构造函数的参数列表附加到它上面,因此句柄表达式使用构造函数参数,而实例管理器则使用即将构造的相同参数调用它:

static __handle => "img/$file"
method __construct(public string $file)

返回值决定了管理器如何处理实例:

__handle() 返回 意义
一个 字符串 注册表键:每次产生相同键的调用返回相同的共享实例(按参数的多例模式)。%img 对于同一文件是一个对象;标志也可以是身份的一部分,如 "INI/$path$filename".(!$parse ? '/0' : void)
null 从不缓存:每次调用构造一个新的实例(cookiewallfield)。
true 重用在类名下注册的实例,并将新参数 objImport(...) 导入其中。

一个 没有 __handle() 的类遵循默认规则:在没有参数的情况下调用时,它是一个以其名称为键的单例;在有参数的情况下调用时,它是一个新的、未注册的实例。

你定义 __handle();实例管理器调用它。切勿自己调用。

9.4: 惰性初始化

因为控制器代码仅在构造后运行,实例可以相互引用而不会触发不必要的递归创建。

示例:

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

最近更新于 2026年8月23日

我们使用必要的cookie来使该网站正常工作。在您的许可下,我们还使用分析工具来改善网站。