任何一个稍微复杂点的应用程序,都存在对象间的关联。ORM 的主要职责除了完成对象和数据库的交互,另一个重要职责就是维护对象间的关系。
QeePHP 支持四种关联关系,分别是“一对一”、“一对多”、“多对多”和“从属”关联关系。“关系”将两类模型连接在一起,例如“作者”和“文章”模型之间存在的关系。
虽然大多数关系都是双向的,譬如“作者”撰写了多篇“文章”,而每篇“文章”也属于一个“作者”。但是如果以一个模型为出发点,那么这个模型就称为来源对象,而关系的另一端则称为目标对象。
定义一个关联非常简单,只需要在模型的 __define() 方法中添加一个属性,并且指明该属性关联到哪个模型即可。
// 作者模型 class Author extends QDB_ActiveRecord_Abstract { static function __define() { return array( .... 'props' => array( .... 'articles' => array(QDB::HAS_MANY => 'Article'), .... ), .... ); } }
上述代码为 Author 模型添加了一个 articles 属性,该属性用 QDB::HAS_MANY 指定为“一对多”关联,并切关联到 Article 对象。
当定义好这个关联后,如果我们要知道某个作者的所有文章,代码很简洁:
// 查找指定名字的作者 $author = Author::find('name = ?', $name)->getOne(); // 显示该作者所有文章的标题 foreach ($author->articles as $article) { echo $article->title; }
我们不需要在查询中告诉 QeePHP 我们除了要知道作者信息,还需要知道该作者的文章信息。只要能够取得作者对象,很自然的通过 articles 属性就能遍历该作者的所有文章。
一对一关联表示一个对象有另一个对象与其关联,但不会有多个对象与其关联。例如每个“公民”有且只有一个“身份证号码”。
在一对一关联中,存储目标对象的数据表必须添加一个字段,用来保存来源对象的主键值。由于只能保存一个主键值,因此来源对象只能有一个主键。
存储 User 用户模型的数据表结构 | |
---|---|
id | 主键 |
name | 名字 |
存储 IDCard 身份证模型的数据表结构 | |
---|---|
user_id | 用户对象主键 |
number | 身份证号码 |
address | 住址 |
虽然没有特别要求,但目标对象的数据表中存储来源对象主键值的字段一般命名为“来源模型名_id”,所以 IDCard 存储表的主键字段命名为 user_id。
数据表结构符合要求后,我们就可以在模型中添加关联了:
// User 模型的 __define() 方法 .... '关联属性名' => array(QDB::HAS_ONE => '关联模型类名称') ....
对于每一个关联,QeePHP 都提供了丰富的选项来控制关联的行为:
关联中,识别来源对象的属性名,如果不设置则以来源对象的主键为准。
假设我们的 User 对象用 idcard_number 属性来存储身份证号码,那么 IDCard 对象就可以按照 User 对象的 idcard_number 属性来关联,而不是 User 对象的主键。这种情况,数据表和关联定义如下:
存储 User 用户模型的数据表结构 | |
---|---|
id | 主键 |
name | 名字 |
idcard_number | 身份证号码 |
存储 IDCard 身份证模型的数据表结构 | |
---|---|
number | 身份证号码 |
address | 住址 |
// User 模型的 __define() 方法 .... 'idcard' => array( QDB::HAS_ONE => 'IDCard', 'source_key' => 'idcard_number', 'target_key' => 'number' ), ....
这里不但出现了 source_key,还出现了 target_key。因为只要不是关联到来源对象的主键,都必须指定 source_key 和 target_key 设置。
目标对象数据表中存储来源对象属性值的字段名,如果未指定则设置为“来源模型名_id”。
当通过来源对象的关联属性访问目标对象时,查询多少目标对象,默认为 all,既查询所有与来源对象相关的目标对象。
on_find 可以指定为不同的值:
值 | 意义 |
---|---|
all | 查询所有目标对象 |
true | 功能同 all |
skip | 不查询目标对象 |
false | 功能同 skip |
对于“一对一”关联来说,只要 on_find 的值不是 false、skip 或 0,那么都可以正确查询目标对象。
查询目标对象时,取得目标对象的哪些属性,默认为“*”,既取得所有属性。如果查询目标对象时只需要指定的属性,将 on_find_keys 指定为以逗号分割的多个属性名即可。
删除来源对象时,相关的目标对象如何处理,默认为“cascade”,既一起删除。例如删除用户对象时,将会自动删除该用户的身份证对象。
on_delete 可以指定为不同的值:
值 | 意义 |
---|---|
cascade | 删除来源对象时删除目标对象 |
true | 功能同 cascade |
set_null | 删除来源对象时,将目标对象以 target_key 指定的属性设置为 null |
set_value | 删除来源对象时,将目标对象以 target_key 指定的属性设置为特定值 |
skip | 删除来源对象时不删除目标对象 |
false | 功能同 skip |
reject | 删除来源对象时,如果该对象有关联的目标对象,则拒绝删除 |
当 on_delete 为 set_value 时,可以通过 on_delete_set_value 设置来指定填充值。
当 on_delete 为 reject 时,则 QeePHP 在删除来源对象前,会确认是否有目标对象与该来源对象关联。如果有关联,则 QeePHP 会抛出 QDB_ActiveRecord_Association_RejectException 异常。
如果 on_delete 指定为 set_value,则通过该设置来指定填充值。
保存来源对象时,怎么处理目标对象,默认为“replace”,既尝试替换已有的目标对象,如果不存在则新建目标对象。
on_save 可以指定为不同的值:
值 | 意义 |
---|---|
save | 保存来源对象的时候也保存目标对象,至于是创建还是更新,由目标对象的 save() 方法确定 |
true | 功能同 save |
create | 保存来源对象时,总是创建新的目标对象(如果目标对象主键出现重复,将会出错) |
update | 保存来源对象时,总是更新目标对象(如果目标对象不存在,将会出错) |
replace | 保存来源对象时,先检查目标对象是否已经存在,如果存在则更新,否则创建 |
skip | 保存来源对象时,不保存目标对象 |
false | 功能同 skip |
only_create | 保存来源对象时,目标对象的 save() 方法将只保存新建的对象,既不会更新已有的目标对象 |
only_update | 功能类似 only_create,但只是更新,而没有创建操作 |
由于“一对一”关联的特殊性,通常应该将 on_save 保持为默认值“replace”。但 replace 会在保存目标对象时导致额外的一次查询,所以性能敏感的情况下,可以将 on_save 改为“save”,并且为目标对象添加自己的主键。这样目标对象的 save() 方法将能正确决定是创建还是更新。