Laravel ManyToMany отношения на той же модели - PullRequest
1 голос
/ 23 апреля 2019

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

Моя модель выглядит так:


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
    protected $table = 'tags';
    public $timestamps = false;

    public function tags()
    {
        return $this->belongsToMany(Tag::class, 'tag_tag', 'tag_one_id', 'tag_two_id');
    }

}

Итак, на данный момент, допустим, у меня есть Tag1 и Tag2 в моей таблице тегов, а затем я свяжу Tag2 с Tag1. Теперь моя сводная таблица будет выглядеть так:

+----+------------+------------+
| id | tag_one_id | tag_two_id |
+----+------------+------------+
| 1  | 1          | 2          |
+----+------------+------------+

Когда я пытаюсь этот код:

$tag = Tag::find(1);
$tag->tags()->get();

Я получаю экземпляр Tag2, и он правильный.

Но когда я пытаюсь запустить этот код:

$tag = Tag::find(2);
$tag->tags()->get();

Я хотел бы получить экземпляр Tag1, но не получаю.

Можно ли сделать это с помощью Eloquent по умолчанию Laravel, используя только один метод в модели?

Ответы [ 3 ]

2 голосов
/ 23 апреля 2019

Почему это не работает?

Это не работает, потому что в отношениях, которые вы добавили в Tag, модель определена для работы в одном направлении.Но не наоборот.

Это сработало бы, если бы мы могли определить два метода с именем tags(), как показано ниже:

public function tags()
{
  return $this->belongsToMany(Tag::class, 'tag_tag', 'tag_one_id', 'tag_two_id');
}

//and

public function tags()
{
  return $this->belongsToMany(Tag::class, 'tag_tag', 'tag_two_id', 'tag_one_id');
}

К сожалению, это невозможно.

Итак, что может быть возможным решением

Одним из возможных решений может быть, не трогать отношения.Вместо этого, если вам как-то удастся вставить две взаимосвязи для этих взаимосвязей, тогда это сработает.Например:

+----+------------+------------+
| id | tag_one_id | tag_two_id |
+----+------------+------------+
| 1  | 1          | 2          |
+----+------------+------------+
| 1  | 2          | 1          |
+----+------------+------------+

Это решение, которое приходит мне в голову прямо сейчас.Там может быть и лучшее решение.

0 голосов
/ 24 апреля 2019

Я нашел решение и решил его следующим образом.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
    /**
     * @inheritdoc
     *
     * @var string
     */
    protected $table = 'tags';

    /**
     * @inheritdoc
     *
     * @var bool
     */
    public $timestamps = false;

    /*
    |--------------------------------------------------------------------------
    | RELATIONS
    |--------------------------------------------------------------------------
    */

    /**
     * Every tag can contain many related tags (TagOne has many TagTwo).
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    protected function tagsOneTwo()
    {
        return $this->belongsToMany(Tag::class, 'tag_tag', 'tag_one_id', 'tag_two_id');
    }

    /**
     * Every tag can contain many related tags (TagTwo has many TagOne).
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    protected function tagsTwoOne()
    {
        return $this->belongsToMany(Tag::class, 'tag_tag', 'tag_two_id', 'tag_one_id');
    }

    /**
     * This method returns a collection with all the tags related with this tag.
     * It is not a real relation, but emulates it.
     *
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function tags()
    {
        return $this->tagsOneTwo()->get()->merge($this->tagsTwoOne()->get())->unique('id');
    }

    /*
    |--------------------------------------------------------------------------
    | FUNCTIONS
    |--------------------------------------------------------------------------
    */

    /**
     * Function to relate two tags together.
     *
     * @param Tag $tag
     * @return void;
     */
    public function attach(Tag $tag)
    {
        if ($this->tags()->contains('id', $tag->getKey())) {
            return;
        }

        $this->tagsOneTwo()->attach($tag->getKey());
    }

    /**
     * Function to un-relate two tags.
     *
     * @param Tag $tag
     * @return void;
     */
    public function detach(Tag $tag)
    {
        if ($this->tags()->contains('id', $tag->getKey())) {
            // Detach the relationship in both ways.
            $this->tagsOneTwo()->detach($tag->getKey());
            $this->tagsTwoOne()->detach($tag->getKey());
        }
    }

    /*
    |--------------------------------------------------------------------------
    | ACCESORS
    |--------------------------------------------------------------------------
    */

    /**
     * Let access the related tags like if it was preloaded ($tag->tags).
     *
     * @return mixed
     */
    public function getTagsAttribute()
    {
        if (! array_key_exists('tags', $this->relations)) {
            $this->setRelation('tags', $this->tags());
        };

        return $this->getRelation('tags');
    }
}
0 голосов
/ 23 апреля 2019

Это возможно.Вам нужно будет передать параметр в метод tags() и изменить поля первичного / внешнего ключа в отношении, используя этот параметр.Хотя это может быть общая боль, с которой приходится иметь дело, и почти наверняка было бы меньше головной боли просто сделать второй метод отношений.В итоге все выглядело бы так:

public function tags($tag1 = 'tag_one_id', $tag2 = 'tag_two_id')
{
    return $this->belongsToMany(Tag::class, 'tag_tag', $tag1, $tag2);
}

, а затем вам просто нужно изменить значения, когда вам нужно Tag::find(2)->tags('tag_two_id', 'tag_one_id')

Это может быть нетерпеливымзагружен, как описано здесь: https://laracasts.com/discuss/channels/general-discussion/eager-load-with-parameters

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

...