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 时:
- 实例管理器通过
__handle()(或默认规则)从参数中派生的身份检查是否已经存在匹配的实例。 - 如果没有,则创建并存储。
- 创建后,控制器代码运行(见 §8.2)。
- 然后调用请求的方法。
9.2: 控制器代码
在 .phlo 文件中,所有不属于 route、prop、static、method、function、view、<style> 或 <script> 的代码被称为控制器代码。
这段代码在实例化后运行,一旦实例完全存在。
示例:
prop ready = false
%session->start()
$this->ready = true
最后两行是控制器代码,因为它们位于顶层。
- 这段代码在构造后运行,每次管理器创建新实例时运行一次:对于共享实例,或者在
__handle()返回null的类的每次调用中。 - 与
__construct的区别在于,当控制器代码运行时,实例完全存在,这防止了循环引用和不完整对象的出现。
9.3: `__handle()` 的角色:实例身份
__handle() 是一个 可选的静态 方法,您可以自己定义以控制实例的身份。它与 __construct 交织在一起:转译器将您的构造函数的参数列表附加到它上面,因此句柄表达式使用构造函数参数,而实例管理器则使用即将构造的相同参数调用它:
static __handle => "img/$file"
method __construct(public string $file)
返回值决定了管理器如何处理实例:
__handle() 返回 |
意义 |
|---|---|
| 一个 字符串 | 注册表键:每次产生相同键的调用返回相同的共享实例(按参数的多例模式)。%img 对于同一文件是一个对象;标志也可以是身份的一部分,如 "INI/$path$filename".(!$parse ? '/0' : void)。 |
null |
从不缓存:每次调用构造一个新的实例(cookiewall,field)。 |
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)
}
%a和%b是惰性创建的。- 两个文件中的控制器代码在它们的实例完全存在后运行一次。
- 你可以自由地相互引用实例,因为在控制器代码运行之前,实例已经存在。
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
- 使用控制器代码进行 初始设置,而不是用于依赖请求的逻辑。
- 将控制器代码 放在顶部 或直接放在 props 下面以提高可读性。
__construct捕获实例参数;最好仅限于此,理想情况下使用提升参数(method __construct(public string $file)),因为__handle()在构造之前从这些相同的参数中推导实例的身份。重型工作应放在懒惰的 props 中;应用级启动在控制器代码中进行。- 让实例通过
%name懒惰地初始化,而不是手动创建它们。 - 有意识地使用控制器代码来解决循环引用。
最近更新于 2026年8月23日