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.
相关产品推荐
相关产品推荐

