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: obj 基类:powertools

每个转译的类都扩展了 obj,而 obj 的功能不仅仅是 __get/__set。当一个类需要动态行为时,这些就是你要使用的工具。

临时值对象。 obj 也是你直接创建的日常容器:obj(x: 1, y: 2) 让你在一次调用中获得一个实时对象,无需类定义。读取一个不存在的键会返回 null,而不是警告,因此可选数据不需要 isset 的繁琐操作。分配一个闭包,它就变成了一个绑定成员:在 $point->sum = fn() => $this->x + $this->y 之后,$point->sum$point->sum() 都会以 $this 绑定到对象来评估它。foreach 迭代存储的数据,而 json_encode($point) 则精确地序列化这些数据(闭包和计算值会被排除),因此一个 obj 可以干净地传递到 views、payloads 和 JSON 响应中。

$point = obj(x: 1, y: 2)
$point->sum = fn() => $this->x + $this->y
$total = $point->sum

拦截钩子。 实现 objCallobjGetobjSet 来捕获访问链。返回 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 在每次写入之前运行(非空返回会吞掉写入)。这就是装饰器、延迟加载和只读保护的机制。

绑定闭包。 分配一个闭包,它会绑定到实例上:$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: Best practices

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