为什么 Laravel 不能猜测你的 Factory 关系
发布: (2026年1月14日 GMT+8 02:43)
3 min read
原文: Dev.to
Source: Dev.to
问题
final class Client extends Model
{
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function distributor(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
$owner = UserFactory::new()->create(['name' => 'Owner']);
$distributor = UserFactory::new()->create(['name' => 'Distributor']);
$client = ClientFactory::new()
->for($owner)
->for($distributor)
->create();
Laravel 会把 user_id 设置为 distributor 的 ID,而把 distributor_id 留为 null。这是因为 for($model) 只会检查模型的 类,而不会考虑变量名。由于 $owner 和 $distributor 都是 User 实例,Laravel 会选取它找到的 第一个 User 关系(user)。
明确指定关系
通过在第二个参数传入关系名称来告诉 Laravel 使用哪个关系:
$client = ClientFactory::new()
->for($owner, 'user')
->for($distributor, 'distributor')
->create();
现在两个外键都会被正确填充。
另一种方式:直接设置外键
有时直接为键赋值会更清晰:
$client = ClientFactory::new()->create([
'user_id' => $owner->id,
'distributor_id' => $distributor->id,
]);
这样可以避免一连串的 for() 调用,并且意图更加明确。
设计考虑
遵循 Laravel 的约定通常能获得最佳体验:
- 单一模型的关系(例如
Client→User)非常直接。 - 为同一模型添加第二个关系可能会让人困惑。在某些情况下,引入专门的模型(例如
Distributor)能够更好地表达业务领域。 - 命名很重要。如果业务语言使用 “owner” 与 “distributor”,则相应地命名关系可以降低认知负担。
Client → Customer
user → owner
使用清晰、领域特定的名称可以减少意外。
实际案例
$address = AddressFactory::new()
->for($user)
->has(
DirectionFactory::new()
->has(
DirectionScheduleFactory::new()->count(10),
'schedules'
)
)
->create();
在这个例子中,默认的 for($user) 能正常工作,因为 Address 上只有一个指向 User 的关系。
要点总结
for()会选取第一个与模型类匹配的关系。- 当存在多个关系时,使用第二个参数明确指定关系。
- 也可以在工厂负载中直接设置外键。
- 将模型和关系的命名与业务语言保持一致,以避免混淆。
- 当业务需求偏离 Laravel 的约定时,显式指定胜过隐式魔法。
感谢 Joel Clermont 提供的原始视频,它启发了本文的创作。他的 Laravel 小技巧值得一看。