8: ORM

Phlo 附带一个强大的内置 ORM,允许您将数据库表定义为类。模型可以通过 columns 快速定义,或通过声明式 schema 完整定义。记录被视为 instances,支持 props、methods、views、relations 和多种数据库引擎。

8.1: 基础知识

一个 ORM 模型是一个 .phlo 文件,包含:

示例:

@ 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 使用 schemafield(...)(丰富且声明式)

使用 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)

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 始终在 record instance 上操作,而不是静态地。

8.6: 过滤和查询

所有查询方法都接受命名参数和类似 SQL 的过滤器:

shipment::records(destination: 'Paris')
shipment::records(where: 'valid=1 AND weight>10')
shipment::pair(columns: 'id,destination')

支持:whereordergroupjoins、缓存和模式感知列。

8.7: 缓存和性能

ORM 使用内部缓冲区 (objRecords, objLoaded) 进行关系查找,并通过可选的 APCu 缓存

static objCache = true

或者是秒数:

static objCache = 600

记录和关系是批量加载的。
在循环中使用 records() 进行批量选择,而不是使用 record()

8.8: 多个引擎

Phlo 是与数据库无关的:模型通过 static DB 解析其引擎,并且没有隐式默认值。通过从 app.phlo 将引擎注入到模型基类中,为整个应用程序设置一次引擎:

static %model.DB => %MySQL

为单个模型重写它,使用自己的 static DB

SQLite

@ class: notes
@ extends: model

static DB => %SQLite(data.'notes.db')
static table = 'notes'
static columns = 'id,title,body'

PostgreSQL

@ class: invoices
@ extends: model

static 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: 选择加入的功能:审计、验证、自定义主键

一个 @ extends: model 让你开箱即用地获得 CRUD + 身份映射 + 关系。每个模型可以通过一个静态标志启用三个额外的选项。

8.9.1 审计日志

static objAudit = true

从那时起,每个 createobjSave(更新)和 delete 都会通过 security/audit 资源记录到审计表中:

操作 记录内容
create(...) 完整的新值
objSave(更新) 差异:仅更改的字段,fromto
delete(...) 完整的旧值,按受影响的记录

设置:

  1. security/audit 添加到 data/app.json 中的资源。
  2. 导入模式 SQL 一次:mysql <database> < /opt/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: ...) 的运行时常量。)

读取日志。security/audit 资源还提供了一个小型查询 API,模型作为第一个参数:

调用 返回
%audit->history($model, $recordId, $limit = 50) 每个记录的所有更改,最新的在前
%audit->byUser($model, $userId, $fromTs = 0, $limit = 100) $fromTs 以来用户所做的每个更改
%audit->purge($model, $olderThanSeconds = 31536000) 删除超过保留期限的条目
$rows = %audit->history(invoice, $invoiceId)

每个 audit_log 行都包含 tsuser(在更改时的 %session->user,或为 null)、modelrecord_idactionip 和一个 changes JSON blob,其结构取决于操作:create 存储完整的新行,update 仅存储更改的字段,格式为 {"col": {"from": ..., "to": ...}},而 delete 存储完整的旧行。

8.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)

8.9.3 自定义主键

默认值为 id(自增整数)。可以重写以使用其他主键:

static idColumn = 'sku'
static idType   = 'string'

效果:

8.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 缓存
static DB static 每个模型的引擎

8.11: Best practices

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