对象关联

任何一个稍微复杂点的应用程序,都存在对象间的关联。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 都提供了丰富的选项来控制关联的行为:

  • source_key:

    关联中,识别来源对象的属性名,如果不设置则以来源对象的主键为准。

    假设我们的 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 设置。

  • target_key:

    目标对象数据表中存储来源对象属性值的字段名,如果未指定则设置为“来源模型名_id”。

  • on_find:

    当通过来源对象的关联属性访问目标对象时,查询多少目标对象,默认为 all,既查询所有与来源对象相关的目标对象。

    on_find 可以指定为不同的值:

    意义
    all 查询所有目标对象
    true 功能同 all
    skip 不查询目标对象
    false 功能同 skip

    对于“一对一”关联来说,只要 on_find 的值不是 false、skip 或 0,那么都可以正确查询目标对象。

  • on_find_keys:

    查询目标对象时,取得目标对象的哪些属性,默认为“*”,既取得所有属性。如果查询目标对象时只需要指定的属性,将 on_find_keys 指定为以逗号分割的多个属性名即可。

  • on_delete:

    删除来源对象时,相关的目标对象如何处理,默认为“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:

    如果 on_delete 指定为 set_value,则通过该设置来指定填充值。

  • on_save:

    保存来源对象时,怎么处理目标对象,默认为“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() 方法将能正确决定是创建还是更新。

一对多

从属

多对多