Laravel belongsToMany on same table: 如何创建对称关系?

Laravel belongsToMany on same table: how do I create a symmetrical relation?

我正在使用 Laravel nova 构建管理面板。我有一个 Items table,它本身有一个 belongsToMany,使用(当然)一个 pivot table.

这本身是有效的,但是当我指定项目 A 与项目 B 相关时,不会存储该关系的倒数,因此导致在项目 B 上找不到。

我需要它,因为这种关系是对称的,意味着如果 A 与 B 相关,那么 B 也与 A 相关。

存储和检索关系的最佳方式是什么?

这是我当前定义关系的代码,它以一种方式工作。

class Item extends Model
{
    public function related(): BelongsToMany
    {
        return $this->belongsToMany(Item::class, 'item_related', 'item_id', 'related_id');
    }
}

所以:假设我有一个 table,其中包含项目 A、B 和 C,并且关系在主元 table

中定义为这样
item_id - related_id
A       - B
A       - C
B       - C

所以基本上,当我做 B-> 相关时,我希望它 return [A, C]。现在只会return C,而C根本没有任何相关项

这里有两种可能的解决方案,一种是同时插入逆关系,导致

item_id - related_id
A       - B
B       - A
A       - C
C       - A
B       - C
C       - B

这会产生正确的结果,但是如何自动强制 laravel 保存倒置关系,并确保正确完成删除等操作?

另一个选项是 'merging' 两个相关的 belongsToMany 调用。

class Item extends Model
{
    public function related(): BelongsToMany
    {
        // return merged $this->relatedFrom and $this->relatedTo
    }

    public function relatedFrom(): BelongsToMany
    {
        return $this->belongsToMany(Item::class, 'item_related', 'related_id', 'item_id');
    }

    public function relatedTo(): BelongsToMany
    {
        return $this->belongsToMany(Item::class, 'item_related', 'item_id', 'related_id');
    }

}

这也会导致各种问题。

那么什么是好的方法呢?

我选择了向枢轴添加反向关系的方法 table,使用观察者和枢轴模型

通常情况下,当有 belongsToMany 关系时,不会创建模型。在这种情况下我需要一个,所以我手动创建了一个。

Pivot 模型,注意它扩展了 Pivot 而不是 Model,在启动方法中添加了观察者,以及我设置为 false 的 $timestamps:

namespace App\Models;

use App\Observers\RelationObserver;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\Pivot;

class ItemRelated extends Pivot
{

    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'item_related';

    public $timestamps = false;

    public static function boot(): void
    {
        parent::boot();
        parent::observe(new RelationObserver);
    }

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'item_id',
        'related_id',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'item_id' => 'integer',
        'related_id' => 'integer',
    ];

}

观察者只需要一个created和deleted方法,插入和删除分别是插入和删除后的反向关系:

namespace App\Observers;

use App\Models\ItemRelated;

class RelationObserver
{
    public function created(ItemRelated $itemRelated): void
    {
        if (ItemRelated::where([
            ['item_id', '=', $itemRelated->related_id],
            ['related_id', '=', $itemRelated->item_id],
        ])->doesntExist()) {
            $itemRelatedReverse = new ItemRelated();
            $itemRelatedReverse->item_id = $itemRelated->related_id;
            $itemRelatedReverse->related_id = $itemRelated->item_id;
            $itemRelatedReverse->save();
        }
    }

    public function deleted(ItemRelated $itemRelated): void
    {
        ItemRelated::where([
            ['item_id', '=', $itemRelated->related_id],
            ['related_id', '=', $itemRelated->item_id],
        ])->delete();
    }
}

最后,我需要通过链接 using 方法告诉项目 table 上的关系使用主元 table:

public function related(): BelongsToMany
{
    return $this->belongsToMany(Item::class, 'item_related', 'item_id', 'related_id')
        ->using(ItemRelated::class);
}