Laravel 11多态关联下如何获取单个Domain记录?
我正在开发Laravel 11项目,现有Site、Domain模型及充当多态中间表的Siteable表(包含site_id、siteable_type、siteable_id字段)。原本在Site模型中通过以下MorphToMany关联可获取该站点的所有Domain:
public function domains(): MorphToMany { return $this->morphedByMany(Domain::class, 'siteable'); }
但需求改为每个Site仅对应一个Domain,尝试改用HasOneThrough关联:
public function domain(): HasOneThrough { return $this->hasOneThrough(Domain::class, Siteable::class, 'siteable_id', 'id', 'id'); }
Siteable表的迁移结构如下:
Schema::create('siteables', function (Blueprint $table) { $table->ulid('id')->primary()->index(); $table->foreignUlid('site_id')->index()->constrained()->onUpdate('cascade')->onDelete('cascade'); $table->ulidMorphs('siteable'); $table->timestamps(); // define indexes for performance $table->index(['site_id', 'siteable_type', 'siteable_id']); });
之后尝试使用morphOne关联时出现SQL错误:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'domains.siteable_id' in 'field list' (Connection: mysql, SQL: select `domains`.* from `domains` inner join (select MAX(`domains`.`id`) as `id_aggregate`, `domains`.`siteable_id`, `domains`.`siteable_type` from `domains` where `domains`.`siteable_id` in (01hy0zwf63wpt6t8c64q1388q9, 01hy0zxw8yb1nncrw7ytg50wwr) and `domains`.`siteable_type` = App\Models\Site group by `domains`.`siteable_id`, `domains`.`siteable_type`) as `latestOfMany` on `latestOfMany`.`id_aggregate` = `domains`.`id` and `latestOfMany`.`siteable_type` = `domains`.`siteable_type` and `latestOfMany`.`siteable_id` = `domains`.`siteable_id`).
添加created_at筛选后仍报相同错误:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'domains.siteable_id' in 'field list' (Connection: mysql, SQL: select `domains`.* from `domains` inner join (select MAX(`domains`.`id`) as `id_aggregate`, min(`domains`.`created_at`) as `created_at_aggregate`, `domains`.`siteable_id`, `domains`.`siteable_type` from `domains` inner join (select MAX(`domains`.`created_at`) as `created_at_aggregate`, `domains`.`siteable_id`, `domains`.`siteable_type` from `domains` where `domains`.`siteable_id` in (01hy0zwf63wpt6t8c64q1388q9, 01hy0zxw8yb1nncrw7ytg50wwr) and `domains`.`siteable_type` = App\Models\Site group by `domains`.`siteable_id`, `domains`.`siteable_type`) as `latestOfMany` on `latestOfMany`.`created_at_aggregate` = `domains`.`created_at` and `latestOfMany`.`siteable_type` = `domains`.`siteable_type` and `latestOfMany`.`siteable_id` = `domains`.`siteable_id` group by `domains`.`siteable_id`, `domains`.`siteable_type`) as `latestOfMany` on `latestOfMany`.`id_aggregate` = `domains`.`id` and `latestOfMany`.`created_at_aggregate` = `domains`.`created_at` and `latestOfMany`.`siteable_type` = `domains`.`siteable_type` and `latestOfMany`.`siteable_id` = `domains`.`siteable_id`).
请问该如何正确配置关联,以获取单个Domain记录?
核心问题是:morphOne关联默认会去Domain模型对应的表中查找siteable_id和siteable_type字段,但你的多态关联字段实际存储在中间表Siteable里,而非domains表本身,因此直接使用morphOne会报错。下面提供两种可行的解决方法:
方案1:基于hasOne包装多态中间表关联
在Site模型中,通过hasOne关联到Siteable中间表,再通过中间表的多态关联获取Domain:
// Site模型 public function domainRelation(): HasOne { return $this->hasOne(Siteable::class) ->where('siteable_type', Domain::class); } // 可选:添加访问器简化调用 public function getDomainAttribute() { return $this->domainRelation->siteable ?? null; }
同时需要在Siteable模型中定义多态关联:
// Siteable模型 public function siteable(): MorphTo { return $this->morphTo(); }
使用时直接调用$site->domain即可获取对应的Domain实例。
方案2:使用Laravel 11新增的MorphOneThrough关联
Laravel 11正式支持MorphOneThrough关联,完美适配你的多态中间表场景,配置如下:
use Illuminate\Database\Eloquent\Relations\MorphOneThrough; // Site模型 public function domain(): MorphOneThrough { return $this->morphOneThrough( Domain::class, // 目标模型 Siteable::class, // 中间表模型 'site_id', // 中间表关联Site的字段 'id', // Domain表的主键 'id', // Site表的主键 'siteable_id', // 中间表关联Domain的字段 'siteable_type' // 中间表的多态类型字段 )->where('siteable_type', Domain::class); }
这个方案无需额外处理,直接调用$site->domain就能拿到对应的Domain模型,符合Laravel的原生关联语法。
数据约束补充
为了确保每个Site仅对应一个Domain,建议在Siteable表的迁移中添加唯一索引:
Schema::create('siteables', function (Blueprint $table) { // ... 原有字段 $table->unique(['site_id', 'siteable_type']); });
这样数据库层面会限制同一个站点不能关联多个同类型的模型,避免出现多条关联记录导致查询异常。
内容的提问来源于Stack Exchange,提问作者Ryan H

