【问题标题】:Laravel belongsToMany on same table: how do I create a symmetrical relation?Laravel belongsToMany 在同一张桌子上:如何创建对称关系?
【发布时间】:2020-09-08 18:29:27
【问题描述】:

我正在使用 Laravel nova 构建一个管理面板。我有一个 Items 表,它本身有一个 belongsToMany,使用(当然)一个数据透视表。

这本身是有效的,但是当我指定项目 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');
    }
}

所以:假设我有一个包含项目 A、B 和 C 的表,并且关系在数据透视表中定义为这样

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

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

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

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

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

另一个选项是“合并”两个相关的 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');
    }

}

这也会导致各种问题。

那么这里的好方法是什么?

【问题讨论】:

  • 我添加了我目前用来定义关系的代码。与这里无关。
  • 我添加了我解决这个问题的方法作为答案,当然欢迎其他方法:)

标签: laravel laravel-7


【解决方案1】:

我选择了向数据透视表添加反向关系的方法,使用观察者和数据透视模型

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

Pivot 模型,注意它扩展了 Pivot 而不是 Model,在 boot 方法中添加了观察者,以及我设置为 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 方法告诉 Item 表上的关系使用数据透视表:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-06-19
    • 2019-09-09
    • 2018-12-03
    • 2018-06-06
    • 2021-03-14
    • 2014-10-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多