9: 实例管理
Phlo 使用其自己的 instance manager 来高效且可预测地初始化和重用对象。该系统确定 何时运行控制器代码,实例如何存储,以及如何防止循环引用。
9.1: 基础知识
当你定义一个 .phlo 文件时,构建阶段将其转换为一个类。
每次通过 %name 调用一个对象时,都会经过 instance manager (phlo() 在 /phlo/phlo.php 中)。
示例:
prop title = 'Welcome'
route GET home => $this->main
method main => view($this->home)
view home:
<h1>$this->title</h1>
当请求路由 /home 时:
- 实例管理器检查该类的实例是否已经存在。
- 如果不存在,则 创建并存储。
- 创建后,控制器代码 运行(见 §8.2)。
- 然后调用请求的方法。
9.2: 控制器代码
在 .phlo 文件中,不属于 route、prop、static、method、function、view、<style> 或 <script> 的所有代码都是 controller code。
这段代码在 实例化后 运行,一旦实例完全存在。
示例:
prop ready = false
%session->start()
$this->ready = true
最后两行是 controller code,因为它们位于顶层。
- 这段代码在实例 首次创建 时运行一次。
- 与
__construct的区别在于,controller code 运行时实例 完全存在,这防止了循环引用和不完整对象的出现。
9.3: `__handle()` 的角色
Phlo 为每个类生成一个特殊的 __handle() 方法。实例管理器会在 通过 %name 请求实例时 调用它。
__handle():
- 如果控制器代码尚未运行,则运行控制器代码;
- 确保 props 和 statics 正确可用;
- 为后续调用缓存实例。
您永远不会自己调用或重写 __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: obj 基类:powertools
每个编译的类都扩展了 obj,而 obj 不仅仅是 __get/__set。这些是当类需要动态行为时你可以使用的工具。
拦截钩子。 实现 objCall、objGet 或 objSet 来捕获访问链。返回 null 会继续执行正常行为;任何非 null 的返回会短路:
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 在每次读取时会在数据/闭包/方法/属性查找之前运行,objCall 在每次未知方法调用时运行,objSet 在每次写入之前运行(非 null 的返回会吞掉写入)。这就是装饰器、延迟加载和只读保护背后的机制。
绑定闭包。 分配一个闭包,它会绑定到实例上:$obj->greet = fn() => "Hi $this->name",之后 $obj->greet() 运行时 $this 被绑定。适合于不需要子类化的每个实例行为。
数据 API。 objImport(name: 'x', age: 3) 批量分配并返回 $this(可链式调用)。objKeys()、objValues() 和 objLength() 检查数据;objClear() 清空数据。迭代一个 obj(foreach $record AS $key => $value)和 json_encode($record) 精确暴露存储的数据。每次写入都会翻转 objChanged,这是 ORM 用来决定 objSave 是否写入任何内容的脏标志。
计算属性缓存。 prop x => ... 在首次访问时缓存;参数形式按参数集缓存。计算静态属性也是如此,按类缓存。
工作者持久性。 prop objPers = true 使实例在工作模式请求之间存活:phlo() 注册表仅在每次请求重置时保留 objPers 实例。适用于数据库连接和解析的配置;不适用于任何请求或用户范围的内容。
课程。 父类中的普通属性会遮蔽子类中的计算属性。抽象父类中的
prop dir = void编译为一个真实的 PHP 属性,因此子类的prop dir => guide获取器永远不会被查询:$this->dir默默读取void。当子类必须用计算属性重写时,也要将父属性声明为计算属性:prop dir => void。
9.6: 最佳实践
- 使用控制器代码进行 初始设置,而不是用于依赖请求的逻辑。
- 将控制器代码 放在顶部 或直接放在 props 下面以提高可读性。
- 避免在
__construct中产生副作用;使用控制器代码而不是自定义构造函数。 - 让实例通过
%name懒惰地初始化,而不是手动创建它们。 - 有意识地使用控制器代码来解决循环引用。