8: ORM
Phlo 附带一个强大的内置 ORM,允许您将数据库表定义为类。模型可以通过 columns 快速定义,或通过声明式 schema 完整定义。记录被视为 instances,支持 props、methods、views、relations 和多种数据库引擎。
8.1: 基础知识
一个 ORM 模型是一个 .phlo 文件,包含:
@ class:模型(和表)的名称@ extends: modelstatic table和columns或schema- 可选:关系(
parent,child,many) - Props、methods 和 views 针对每个 record instance 操作
示例:
@ class: user
@ extends: model
view => $this->name
static table = 'users'
static columns = 'id,name,email,active,created'8.2: 定义模型
7.2.1 使用 columns(快速且轻量)
使用 columns 创建简单的表格:
@ class: shipment
@ extends: model
view: $this->destination ($this->user)
static table = 'shipments'
static order = 'changed DESC'
static columns = 'id,user,destination,costs,valid,weight,shipped,created,changed'
static objParents = ['user' => 'user']
@ class: user
@ extends: model
view => $this->name
static table = 'users'
static order = 'changed DESC'
static columns = 'id,name,email,level,active,created,changed'
static objChildren = ['shipments' => 'shipment']
7.2.2 使用 schema 和 field(...)(丰富且声明式)
使用 schema 你可以在一个地方定义字段、关系和用户界面:
@ class: shipment
@ extends: model
view: $this->destination ($this->user)
static table = 'shipments'
static schema => arr (
id: field (type: 'token', length: 4, title: 'ID'),
destination: field (type: 'text', required: true, search: true),
user: field (type: 'parent', obj: 'user', required: true),
costs: field (type: 'price', prefix: '€ '),
valid: field (type: 'bool'),
attachments: field (type: 'child', obj: 'attachment', list: true),
)
@ class: user
@ extends: model
view => $this->name
static table = 'users'
static schema => arr (
id: field (type: 'token'),
name: field (type: 'text', search: true, required: true),
email: field (type: 'email', required: true),
shipments: field (type: 'child', obj: 'shipment'),
groups: field (type: 'many', obj: 'group', table: 'user_groups'),
)
schema在与 PhloCMS 结合使用时尤其强大,但也可以独立工作。
8.3: CRUD
获取:
$user = user::record(id: 1)
$list = shipment::records(order: 'created DESC')
创建:
$shipment = shipment::create(destination: 'Paris', user: 1)
编辑和保存:
$shipment->destination = 'Lyon'
$shipment->objSave
删除:
shipment::delete('id=?', $shipment->id)
record(...)→ 单个记录(或 null)records(...)→ 记录数组(类实例)create(...)→ 插入 + 立即获取objSave→ 保存实例(根据 id 插入/更新)delete(...)→ 带有 SQL where 子句的静态删除
8.4: 关系导航
关系作为属性可用:
| 类型 | 声明 | 用法 |
|---|---|---|
| parent | type: parent |
$shipment->user |
| child | type: child |
$user->shipments |
| many | type: many |
$user->groups |
多对多
type: many 使用一个中间表:
groups: field (
type: 'many',
obj: 'group',
table: 'user_groups',
)
导航:
$user = user::record(id: 1)
foreach ($user->groups as $group)
echo $group->title
关系是批量加载以提高性能。没有跨数据库连接;每个类从其自己的引擎加载。
8.5: 实例动态
每个记录都是您模型类的真实实例。您可以使用 props、methods 和 views 来添加虚拟字段、计算或表示:
@ class: shipment
@ extends: model
prop summary => $this->destination.' ('.$this->user.')'
method tax => $this->costs * 0.21
view:
<p>$this->summary</p>
<p>$this->tax</p>
用法:
$shipment = shipment::record(id: 'AB12')
echo $shipment->summary
echo $shipment
将记录作为字符串使用会调用其视图表示。
Props 和 methods 始终在记录实例上操作,而不是静态地。
8.6: 过滤和查询
所有查询方法都接受命名参数和类似 SQL 的过滤器:
shipment::records(destination: 'Paris')
shipment::records(where: 'valid=1 AND weight>10')
shipment::pair(columns: 'id,destination')
支持:where、order、group、joins、缓存和模式感知列。
8.7: 缓存和性能
ORM 使用内部缓冲区 (objRecords, objLoaded) 进行关系查找,并通过以下方式可选地使用 APCu 缓存:
static objCache = true
或者以秒为单位的数字:
static objCache = 600
记录和关系是批量加载的。
在循环中使用 records() 进行批量选择,而不是使用 record()。
8.8: 多个引擎
Phlo 支持通过 prop DB 使用多个后端。默认是 %MySQL,但您可以为每个模型设置任何引擎。
SQLite
prop DB => %SQLite(data.'users.db')
@ class: notes
@ extends: model
prop DB => %SQLite(data.'notes.db')
static table = 'notes'
static columns = 'id,title,body'
PostgreSQL
prop DB => %PostgreSQL
@ class: invoices
@ extends: model
prop DB => %PostgreSQL
static table = 'invoices'
static columns = 'id,customer_id,total,created'
不同引擎上的表可以在关系中组合;每个类获取自己的数据。
/data/creds.ini
对于 MySQL 和 PostgreSQL 等引擎,请将您的凭据放在:
/data/creds.ini
[mysql]
host = localhost
database = db_name
user = db_user
password = db_password
[postgresql]
host = localhost
database = my_pg_db
user = pg_user
password = pg_pass
Phlo 会通过 %creds->... 自动加载这些内容。
8.9: 选择加入的功能:审计,验证,自定义 PK
一个 @ extends: model 让你可以开箱即用地获得 CRUD + 身份映射 + 关系 + 软删除。每个模型可以通过静态标志启用三个额外的选项。
X.9.1 审计日志
static objAudit = true
从那时起,每个 create、objSave(更新)和 delete 都会通过 security/audit 资源记录到审计表中:
| 操作 | 记录内容 |
|---|---|
create(...) |
完整的新值 |
objSave(更新) |
差异:仅更改的字段,from → to |
delete(...) |
完整的旧值,按受影响的记录 |
设置:
- 将
security/audit添加到data/app.json中的资源。 - 导入模式 SQL 一次:
mysql <database> < /srv/phlo/resources/security/audit.sql。
排除敏感字段:
method afterCreate => %audit->log($this, 'create', [], (array)$this, exclude: ['password_hash'])
按环境切换,仅限开发:
static objAudit => debug
或仅限发布:
static objAudit => !debug
(debug 是来自 phlo_app(debug: ...) 的运行时常量。)
X.9.2 验证
static objValidate = true
在 create() 之前,Phlo 会对 static schema() 中的每个字段运行匹配的 objValidate($value)。在出现错误时:create() 返回 null,错误可以通过 Class::objErrors() 获取:
if (!user::create($args)){
return apply(errors: user::objErrors())
}
schema() 中的字段规则:
static schema => arr (
email: field (type: 'email', required: true),
name: field (type: 'text', length: 100, required: true),
slug: field (type: 'text', pattern: '^[a-z0-9-]+$'),
status: field (type: 'text', enum: ['draft', 'sent', 'paid']),
)
自定义字段验证:在你自己的字段子类中重写 method objValidate($value)。
X.9.3 自定义主键
默认是 id(自增整数)。可以重写为其他主键:
static idColumn = 'sku'
static idType = 'string'
效果:
- 身份映射使用
sku作为其键 Class::record(sku: 'XYZ-123')(而不是id:)- 使用
create():你自己提供主键值(没有自增) $record->id不起作用,使用$record->sku
X.9.4 组合
这三个选项是独立的,可以组合使用:
@ class: giftcard
@ extends: model
static objAudit = true
static objValidate = true
static idColumn = 'sku'
static idType = 'string'
每个功能默认关闭(false、'id'、'int')。没有选项的模型仍然是普通模型。
8.10: 函数概述
| 函数 / 属性 | 类型 | 描述 |
|---|---|---|
record(...) |
static | 获取 1 条记录(或 null) |
records(...) |
static | 获取多条记录(数组) |
create(...) |
static | 插入 + 获取 |
objSave |
instance | 插入或更新 |
delete(where, …) |
static | 删除 |
pair, item, column |
static | 快速查询助手 |
objParents / schema: parent |
declarative | 父关系 |
objChildren / schema: child |
declarative | 子关系 |
schema: many |
declarative | 多对多 |
objCache |
static | 可选的 APCu 缓存 |
prop DB |
static | 每个模型的引擎 |
8.11: 最佳实践
- 使用
columns进行快速、简单的模型。 - 使用
schema进行丰富的定义和CMS集成。 - 定义一个
view以获取字符串表示。 - 使用 props 进行虚拟字段。
- 使用
objSave而不是save。 - 使用
records()进行批量加载。 - 使用
prop DB为每个引擎分离模型。 - 将凭据保存在
/data/creds.ini中。 - 保持模型声明式;将逻辑放在 props/methods 中。