PAYATHON 2026

如何在 PHP ActiveRecord 中设置同表多外键关系?

支付老李

结论

不必为每种付款类型单独建表。保留统一的 payments 表,再在 Property 模型中根据 payments.type 定义带条件的关联即可。

可以这样访问:

$property->commission_payment->user
$property->referral_payment_record->user

由于 properties 表中已有 commission 和 referral_payment 字段,关联不能再命名为 commission 或 referral_payment,否则会与字段重名。

模型关系

下面使用 php-activerecord 常见的关联声明格式。不同分支或版本的数组语法可能稍有差异,但关系设计相同。

Payment 模型

每条付款记录属于一个房产和一个用户:

<?php

class Payment extends ActiveRecord\Model
{
    public static $belongs_to = [
        ['property'],
        ['user'],
    ];
}

User 模型

一个用户可以有多条付款记录:

<?php

class User extends ActiveRecord\Model
{
    public static $has_many = [
        ['payments'],
        ['properties', 'through' => 'payments'],
    ];
}

Property 模型

除了关联全部付款记录,还可以按付款类型定义带条件的关联:

<?php

class Property extends ActiveRecord\Model
{
    public static $has_many = [
        ['payments'],
        ['users', 'through' => 'payments'],
    ];

    public static $has_one = [
        [
            'commission_payment',
            'class_name'  => 'Payment',
            'foreign_key' => 'property_id',
            'conditions'  => ['type = ?', 'commission'],
        ],
        [
            'referral_payment_record',
            'class_name'  => 'Payment',
            'foreign_key' => 'property_id',
            'conditions'  => ['type = ?', 'referral_payment'],
        ],
    ];
}

定义完成后,就能分别取得付款记录及对应的收款用户:

$property = Property::find($propertyId);

$commissionPayment = $property->commission_payment;

if ($commissionPayment !== null) {
    $commissionUser = $commissionPayment->user;

    echo $commissionUser->name;
    echo $commissionUser->email;
}

$referralPayment = $property->referral_payment_record;

if ($referralPayment !== null) {
    echo $referralPayment->user->name;
}

封装为直接的用户访问方法

如果业务代码通常只需要用户,可以在 Property 中添加普通方法,省去每次访问两层关联:

<?php

class Property extends ActiveRecord\Model
{
    public static $has_many = [
        ['payments'],
    ];

    public static $has_one = [
        [
            'commission_payment',
            'class_name'  => 'Payment',
            'foreign_key' => 'property_id',
            'conditions'  => ['type = ?', 'commission'],
        ],
        [
            'referral_payment_record',
            'class_name'  => 'Payment',
            'foreign_key' => 'property_id',
            'conditions'  => ['type = ?', 'referral_payment'],
        ],
    ];

    public function commission_user()
    {
        $payment = $this->commission_payment;

        return $payment ? $payment->user : null;
    }

    public function referral_payment_user()
    {
        $payment = $this->referral_payment_record;

        return $payment ? $payment->user : null;
    }
}

调用方式如下:

$commissionUser = $property->commission_user();
$referralUser   = $property->referral_payment_user();

这种写法不要求当前 php-activerecord 版本完整支持跨两层的条件关联,通常有更好的兼容性。

如果一种付款类型可能对应多人

只有当一个房产的每种付款类型最多对应一名用户时,才适合使用 $has_one。

如果佣金可能拆分给多个用户,应改用 $has_many:

public static $has_many = [
    [
        'commission_payments',
        'class_name'  => 'Payment',
        'foreign_key' => 'property_id',
        'conditions'  => ['type = ?', 'commission'],
    ],
];

然后遍历这些付款记录:

foreach ($property->commission_payments as $payment) {
    echo $payment->user->name;
}

数据库约束与字段设计

如果每个房产的每种付款类型只能有一条记录,建议添加唯一约束:

CREATE UNIQUE INDEX uniq_property_payment_type
ON payments (property_id, type);

外键也应建立索引:

CREATE INDEX idx_payments_property_id
ON payments (property_id);

CREATE INDEX idx_payments_user_id
ON payments (user_id);

如果 payments 除了记录收款人,还代表实际应付款项,金额更适合存放在付款记录中:

payments

- id
- payment_type
- amount
- property_id
- user_id

这样就不必在 properties 中不断添加 commission、referral_payment 等付款类型专用字段。增加付款类型时,只需插入新的 payment_type,无需修改表结构。

此外,type 在部分 ORM 或数据库环境中可能有特殊含义。如果项目的表结构尚未确定,使用 payment_type 更清楚,也能降低它与单表继承机制或 SQL 关键字发生冲突的风险。

备注:内容仅供参考。