Laravel morphToMany关联create()方法无法正确保存relatedPivotKey咨询
多态多对多关联场景的实现方案
针对你这个Attachment同时关联用户头像和评论详情附件的多态多对多场景,结合你提到已经在AppServiceProvider做自定义操作的情况,我整理了两种清晰的实现方式,帮你优雅处理这个关联逻辑:
一、基础版:直接定义模型关联(无需自定义扩展)
这种方式适合快速落地,不需要额外扩展Laravel的关联类,直接通过belongsToMany结合条件过滤实现:
1. Attachment 模型
namespace App\Models; use Illuminate\Database\Eloquent\Model; class Attachment extends Model { protected $fillable = ['key']; // 关联使用此附件的用户(作为头像) public function avatarUsers() { return $this->belongsToMany(User::class, 'attachment_relationships', 'attachment_key', 'target_id') ->wherePivot('target_type', 'user_avatar') ->withPivot('target_type'); } // 关联使用此附件的评论详情(作为附件图) public function commentAttachments() { return $this->belongsToMany(CommentDetail::class, 'attachment_relationships', 'attachment_key', 'target_id') ->wherePivot('target_type', 'comment_detail') ->withPivot('target_type'); } }
2. User 模型
namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { protected $fillable = ['name']; // 获取用户的头像附件(单条,因为头像一般唯一) public function avatar() { return $this->belongsToMany(Attachment::class, 'attachment_relationships', 'target_id', 'attachment_key') ->wherePivot('target_type', 'user_avatar') ->withPivot('target_type') ->limit(1); } }
3. CommentDetail 模型
namespace App\Models; use Illuminate\Database\Eloquent\Model; class CommentDetail extends Model { protected $fillable = ['cid', 'content']; protected $primaryKey = 'cid'; // 注意这里主键是cid,不是默认的id // 获取评论详情的所有附件图片 public function attachments() { return $this->belongsToMany(Attachment::class, 'attachment_relationships', 'target_id', 'attachment_key') ->wherePivot('target_type', 'comment_detail') ->withPivot('target_type'); } }
使用示例
// 获取用户头像 $user = User::find(1); $avatar = $user->avatar->first(); // 获取评论的所有附件 $comment = CommentDetail::find(1); $attachments = $comment->attachments; // 给用户绑定头像 $user->avatar()->attach('avatar_xxx', ['target_type' => 'user_avatar']); // 给评论添加附件 $comment->attachments()->attach('comment_img_yyy', ['target_type' => 'comment_detail']);
二、进阶版:自定义多态关联(统一调用逻辑)
如果想要更优雅的统一调用(类似Laravel原生的morphToMany),可以在AppServiceProvider中注册自定义关联宏,扩展Laravel的关联逻辑:
1. 创建自定义关联类
namespace App\Relations; use Illuminate\Database\Eloquent\Relations\MorphToMany; class MorphToManyByKey extends MorphToMany { /** * 基础约束:关联attachment_key字段 */ public function addConstraints() { if (static::$constraints) { $this->query->where($this->table.'.'.$this->foreignPivotKey, '=', $this->parent->{$this->parentKey}); $this->query->where($this->table.'.'.$this->morphType, '=', $this->morphClass); } } /** * 预加载约束:批量查询关联数据 */ public function addEagerConstraints(array $models) { $this->query->whereIn($this->table.'.'.$this->foreignPivotKey, $this->getKeys($models, $this->parentKey)); $this->query->where($this->table.'.'.$this->morphType, '=', $this->morphClass); } }
2. 在AppServiceProvider中注册关联宏
namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Database\Eloquent\Model; use App\Relations\MorphToManyByKey; class AppServiceProvider extends ServiceProvider { public function boot() { // 正向多态关联(Attachment关联其他模型) Model::macro('morphToManyByKey', function ($related, $name, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null) { $foreignPivotKey = $foreignPivotKey ?: $name.'_key'; $relatedPivotKey = $relatedPivotKey ?: $this->getForeignKey(); $parentKey = $parentKey ?: 'key'; $relatedKey = $relatedKey ?: (new $related)->getKeyName(); $instance = $this->newRelatedInstance($related); $table = $table ?: $this->joiningTable($name); return new MorphToManyByKey( $instance->newQuery(), $this, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey ); }); // 反向多态关联(其他模型关联Attachment) Model::macro('morphedByManyByKey', function ($related, $name, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null) { $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey(); $relatedPivotKey = $relatedPivotKey ?: $name.'_key'; $parentKey = $parentKey ?: $this->getKeyName(); $relatedKey = $relatedKey ?: 'key'; $instance = $this->newRelatedInstance($related); $table = $table ?: $this->joiningTable($name); return new MorphToManyByKey( $instance->newQuery(), $this, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, true ); }); } }
3. 模型中使用自定义关联
// Attachment模型 public function attachables() { return $this->morphToManyByKey(null, 'attachable', 'attachment_relationships', 'attachment_key', 'target_id', 'key'); } // User模型 public function avatar() { return $this->morphedByManyByKey(Attachment::class, 'attachable', 'attachment_relationships', 'target_id', 'attachment_key') ->wherePivot('target_type', 'user_avatar') ->limit(1); } // CommentDetail模型 public function attachments() { return $this->morphedByManyByKey(Attachment::class, 'attachable', 'attachment_relationships', 'target_id', 'attachment_key') ->wherePivot('target_type', 'comment_detail'); }
这种方式的使用逻辑和基础版一致,但关联定义更贴近Laravel原生的多态关联风格,后续扩展其他关联类型也更方便。
内容的提问来源于stack exchange,提问作者xiaomlove
相关产品推荐
相关产品推荐

