You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel多态多对多关联(含中间表)配置问题求助

Laravel 多态关联配置问题

现有表结构

  • shops(id, name, ...)
  • shopables(id, shop_id, shopable_type, shopable_id, price, count, ...)(另外,是否应该将其重命名为good_shop(good_type, good_id)?)
  • parts(id, name, ...)
  • items(id, name, ...)
  • ASICs(id, name, ...)

注意:shopable_type字段存储的是part、item、ASIC,而非完整类名(如App\Part)。

需求

需要在模型中实现以下关联方法:

  • Part、Item、ASIC模型:shops()方法(关联对应的店铺)
  • Shop模型:goods()方法(关联所有类型的商品:parts、items、ASICs)

现有代码问题

尝试配置关联但无法成功,以下是现有模型代码:

Shop 模型

<?php

namespace App\Models;

use App\Models\traits\HasImage;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;

class Shop extends Model
{
    use HasFactory;
    use HasImage;

    protected $fillable = ['name', 'description', 'type', 'slug', 'delivery_time', 'warranty'];

//    public function parts()
//    {
//        return $this
//            ->belongsToMany(Part::class)
//            ->using(GoodShopPivot::class)
//            ->withPivot('id', 'count');
//    }
//
//    public function items()
//    {
//        return $this
//            ->belongsToMany(Item::class)
//            ->using(GoodShopPivot::class)
//            ->withPivot('id', 'count', 'price', 'currency');
//    }


    public function shopables()
    {
        Relation::morphMap([
            'part' => 'App\Part',
            'item' => 'App\Item',
            'asic' => 'App\ASIC',
        ]);

        return $this->morphTo('shopable');
    }
}

Part 模型(Item、ASIC模型结构类似)

<?php

namespace App\Models;

class Part extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $dates = ['deleted_at'];

    /**
     * The attributes that should be hidden for arrays.
     */
    protected $hidden = ['created_at', 'updated_at', 'GPU_real_hashrate', 'real_temperature', 'PSU_breaks_chance'];

    protected $guarded = ['id'];

//    public function shops()
//    {
//        return $this
//            ->belongsToMany(Shop::class)
//            ->withPivot(['count', 'price'])
//            ->using(GoodShopPivot::class);
//    }
//
    public function shops()
    {
//        return $this->morphToMany('App\Shop', 'shopable')->withPivot('price', 'count', 'currency');

        return $this
            ->morphTo(Shop::class, 'shopable')
        ;
    }
}

解决方案

1. 表名建议

shopables表的命名符合Laravel多态关联的常规约定({关联名}ables),如果重命名为good_shop,需要手动指定表名增加配置成本,建议保留原表名。

2. 模型关联配置修正

Shop 模型:实现goods()方法

这里需要使用多对多多态关联(morphToMany),因为一个店铺对应多个不同类型的商品,同时一个商品也可属于多个店铺:

<?php

namespace App\Models;

use App\Models\traits\HasImage;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;

class Shop extends Model
{
    use HasFactory;
    use HasImage;

    protected $fillable = ['name', 'description', 'type', 'slug', 'delivery_time', 'warranty'];

    public function goods()
    {
        // 多对多多态关联,关联所有商品类型
        return $this->morphToMany(
            \App\Models\Model::class,
            'shopable',
            'shopables',
            'shop_id',
            'shopable_id'
        )->withPivot('price', 'count', 'currency'); // 携带中间表字段
    }
}

Part 模型:实现shops()方法(Item、ASIC模型同理)

反向关联店铺,同样使用morphToMany:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Part extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $dates = ['deleted_at'];

    protected $hidden = ['created_at', 'updated_at', 'GPU_real_hashrate', 'real_temperature', 'PSU_breaks_chance'];

    protected $guarded = ['id'];

    public function shops()
    {
        return $this->morphToMany(
            \App\Models\Shop::class,
            'shopable',
            'shopables',
            'shopable_id',
            'shop_id'
        )->withPivot('price', 'count', 'currency');
    }
}

3. 全局多态映射优化

将Relation::morphMap配置放在App\Providers\AppServiceProvider的boot()方法中,全局生效避免重复定义:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Relations\Relation;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        //
    }

    public function boot(): void
    {
        Relation::morphMap([
            'part' => \App\Models\Part::class,
            'item' => \App\Models\Item::class,
            'asic' => \App\Models\ASIC::class,
        ]);
    }
}

4. 使用示例

// 获取某个店铺的所有商品
$shop = Shop::find(1);
$goods = $shop->goods; // 返回包含Part、Item、ASIC的集合

// 获取某个零件所属的所有店铺
$part = Part::find(1);
$shops = $part->shops; // 返回关联店铺集合,可通过$shop->pivot访问price、count等字段

内容的提问来源于stack exchange,提问作者Влад Рогов

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 10:25:45