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 始终在记录实例上操作,而不是静态地。

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 支持通过 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

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

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

设置:

  1. security/audit 添加到 data/app.json 中的资源。
  2. 导入模式 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'

效果:

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: 最佳实践

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