Laravel同一模型上的多对多关系

5

我正在尝试在我的标签模型上建立双向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实例,但我没有得到。

使用Laravel默认的Eloquent模型,是否有一种方法可以只使用一个模型方法就能完成此操作?

3个回答

7
我找到了一个解决方案,我是这样解决的。
<?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');
    }
}

3

为什么它不起作用?

它不起作用是因为您在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

这是可能的。您需要向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

您的用例可能比您的帖子所示更加复杂,这可能会使这更合理。就我而言,我会考虑其他几个选项。


实际上,我已经考虑过这个问题了,但是问题在于我必须合并两个关系(tag_one_id -> tag_two_id 和 tag_two_id -> tag_one_id)。 - Manu
我希望得到类似的东西,但我希望方法可以返回一个关系。 - Manu
啊,所以你不想让查询构建器执行 SQL。就像那个例子中的访问器一样,但它返回一个关系? - N Mahurin
不确定。问题在于你最终只会得到一个包含两个标签的集合,没有任何明显的方式可以知道它们之间的关系。如果你传递所需的标签ID,那么Tag::whereIn('id', $ids)->get()将返回相同的结果。我正在尝试找出这种情况的用例。 - N Mahurin
我尝试过了,但是它没有奏效,我认为我会制作一个处理这种关系的包。 - Manu
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接