简体   繁体   中英

Laravel ManyToMany relationship on same model

I'm trying to make a bidirectional ManyToMany relationship on my Tag model, but I ran into this "issue".

My model looks like this:


<?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');
    }

}

So at the moment let's say I have Tag1 and Tag2 in my tags table, and then I'll relate the Tag2 with the Tag1. Now my pivot table will look like this:

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

When I try this code:

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

I get the Tag2 instance, and it is correct.

But when I try to run this code:

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

I would like to receive the Tag1 instance, but I don't.

Is it possible to get it done with Laravel default Eloquent using just one method on the model?

Why it's not working?

It's not working because in the relationship you added in Tag model is defined to work for one way. But not the reverse way.

It would work if we could have defined two method called tags() as below:

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');
}

Unfortunately, it's not possible.

So, what can be possible solution

One possible solution can be, don't touch the relationship. Instead if you somehow can manage to insert two relationship for these relationship then it will work. For example:

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

This is the solution coming out of my mind right now. There might be a better solution too.

I found a solution and I solved it like this.

<?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');
    }
}

It's possible. You would have to pass a parameter to the tags() method and modify the primary/foreign key fields on the relationship using that parameter. That might be an total pain to have to deal with though and it would almost certainly be less of a headache to just make a second relationship method. It would just end up looking like this:

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

and then you would just have to modify the values when you need Tag::find(2)->tags('tag_two_id', 'tag_one_id')

This can be eager loaded as described here: https://laracasts.com/discuss/channels/general-discussion/eager-load-with-parameters

Your use case might be a lot more complicated than your post suggests, which might make this more reasonable. As it is I would consider a few of the other options.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM