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

Laravel双向多态M:N关联:如何优雅实现pin功能的便捷操作

实现Laravel双向多态多对多的固定列表功能

要实现多类型用户与多模型的固定关联,同时保留Laravel原生的save/sync/attach等便捷操作,以及直接访问$user->pinned获取列表的需求,我们可以通过双向多态多对多关联来实现,以下是具体方案:


1. 修正模型与关联定义

中间表模型(Pinnable.php)

继承MorphPivot以支持双向多态关联:

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\MorphPivot;

class Pinnable extends MorphPivot
{
    // 若中间表名不是默认的`pinnables`,需手动指定
    // protected $table = 'pinnables';

    public function user()
    {
        return $this->morphTo();
    }

    public function pinnable()
    {
        return $this->morphTo();
    }
}

用Trait复用用户关联逻辑

创建Trait实现多用户类型(Admin/User/SuperAdmin)共享的固定列表关联:

namespace App\Models\Traits;

use App\Models\Pinnable;
use Illuminate\Database\Eloquent\Model;

trait HasPinnables
{
    /**
     * 用户与固定模型的多态多对多关联
     */
    public function pinned()
    {
        return $this->morphToMany(
            Model::class,
            'pinnable',
            'pinnables',
            'user_id',
            'pinnable_id',
            'id',
            'id'
        )->using(Pinnable::class)
          ->withTimestamps();
    }

    /**
     * 访问器:直接获取固定模型列表
     */
    public function getPinnedAttribute()
    {
        return $this->pinned->all();
    }
}

将Trait引入各用户模型:

// User.php
namespace App\Models;

use App\Models\Traits\HasPinnables;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use HasPinnables;
    // ... 其他模型代码
}

// Admin.php
namespace App\Models;

use App\Models\Traits\HasPinnables;
use Illuminate\Database\Eloquent\Model;

class Admin extends Model
{
    use HasPinnables;
    // ... 其他模型代码
}

// SuperAdmin.php
namespace App\Models;

use App\Models\Traits\HasPinnables;
use Illuminate\Database\Eloquent\Model;

class SuperAdmin extends Model
{
    use HasPinnables;
    // ... 其他模型代码
}

可固定模型的反向关联(可选)

如果需要从可固定模型侧查看哪些用户固定了它,可在模型中添加反向关联:

// Post.php(Product/Tag等模型同理)
namespace App\Models;

use App\Models\Pinnable;
use Illuminate\Database\Eloquent\Model;

class Post extends Model implements HasNameInterface
{
    /**
     * 固定此模型的用户关联
     */
    public function pinnedBy()
    {
        return $this->morphedByMany(
            Model::class,
            'user',
            'pinnables',
            'pinnable_id',
            'user_id',
            'id',
            'id'
        )->using(Pinnable::class)
          ->withTimestamps();
    }
}

2. 使用便捷操作

现在可以直接使用Laravel原生关联操作,无需手动处理中间表:

获取固定模型列表

$user = User::find(1);
$pinnedModels = $user->pinned; // 直接返回所有固定的Product/Post/Tag实例

单个模型加入固定列表

$post = Post::find(1);
$user->pinned()->save($post);

同步固定列表(替换现有内容)

$post = Post::find(1);
$product = Product::find(2);
// 直接传入模型实例数组
$user->pinned()->sync([$post, $product]);

// 或传入ID+类型的数组(兼容老写法)
$user->pinned()->sync([
    $post->id => ['pinnable_type' => $post->getMorphClass()],
    $product->id => ['pinnable_type' => $product->getMorphClass()]
]);

批量添加(不替换现有列表)

$user->pinned()->attach($post); // 添加单个模型
// 批量添加多个模型
$user->pinned()->attach([
    $post->id => ['pinnable_type' => $post->getMorphClass()],
    $product->id => ['pinnable_type' => $product->getMorphClass()]
]);

3. 额外优化(可选)

  • 如果需要对固定列表排序,可在中间表添加position字段,然后在关联中加入->orderBy('pinnables.position')
  • 如果需要过滤固定模型类型,可在关联中添加->wherePivot('pinnable_type', Post::class)

内容的提问来源于stack exchange,提问作者D.R.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 08:36:22