如何在 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 关键字发生冲突的风险。
备注:内容仅供参考。
