Laravel принадлежит множеству в одной таблице: как мне создать симметричное отношение?

#laravel #laravel-7

#laravel #laravel-7

Вопрос:

Я создаю панель администратора с использованием Laravel nova. У меня есть таблица элементов, в которой есть число, принадлежащее самому себе, с использованием (конечно) сводной таблицы.

Это само по себе работает, но когда я указываю, что элемент 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');
    }

}
  

Это также вызвало бы всевозможные проблемы.

Итак, какой здесь был бы хороший подход?

Комментарии:

1. Я добавил код, который я в настоящее время использую для определения отношения. Здесь это не так уж важно.

2. Я добавил способ, которым я решил это, в качестве ответа, другие подходы, конечно, приветствуются 🙂

Ответ №1:

Я выбрал метод добавления обратного отношения к сводной таблице, используя наблюдателя и сводную модель

Обычно модель не создается при наличии отношения belongsToMany. В этом случае мне нужно было одно, поэтому я создал его вручную.

Модель Pivot, обратите внимание, что она расширяет Pivot вместо Model, наблюдатель добавляется в метод загрузки, а $ timestamps, для которого я установил значение false:

 namespace AppModels;

use AppObserversRelationObserver;
use IlluminateDatabaseEloquentRelationsBelongsTo;
use IlluminateDatabaseEloquentRelationsPivot;

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 AppObservers;

use AppModelsItemRelated;

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:

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