Отношения Laravel ManyToMany на той же модели

Я пытаюсь создать двунаправленную связь 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, используя только один метод в модели?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
4
0
1 244
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Это возможно. Вам нужно будет передать параметр методу 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 23.04.2019 18:43

Я не уверен, что ты имеешь в виду.

N Mahurin 23.04.2019 18:57

Я надеялся на что-то подобное, но таким образом, чтобы я мог вернуть отношения из метода. stackoverflow.com/a/46316876/5280969

Manu 23.04.2019 19:05

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

N Mahurin 23.04.2019 19:20

Да, точно, можно?

Manu 23.04.2019 19:23

Не уверен. Проблема в том, что вы просто получаете набор обоих тегов на одном уровне, без какого-либо реального способа заметить, что они связаны. Это было бы то же самое, что простое получение обоих тегов Tag::whereIn('id', $ids)->get() вернет одно и то же, если вы передадите идентификаторы тегов, которые вам нужны. Я пытаюсь выяснить вариант использования для этого.

N Mahurin 23.04.2019 19:30

Тогда я скажу вам, для чего я пытаюсь его использовать. У меня есть объект тега с именем, но мне также нужно хранить переводы этого тега. Допустим, у меня есть тег «Автомобиль», у меня также есть «Автомобиль» или «Voiture» на разных языках. Но это тоже разные теги. Поэтому, когда я запрашиваю тег «Автомобиль», мне также нужно получить связанные теги («Автомобиль» и «Voiture»). Когда я запрашиваю «Voiture», мне нужно получить связанные («Автомобиль» и «Автомобиль»).

Manu 23.04.2019 19:45

Как насчет нетерпеливой загрузки тегов и тегов этого тега. Нравится Tag::with('tags', 'tags.tags'). Это должно позволить вам получить все теги, связанные с текущим тегом, и теги, связанные с ним. Таким образом, если у вас есть связь между автомобилем и автомобилем, а также между автомобилем и Voiture, вы все равно можете получить доступ к Voiture из автомобиля.

N Mahurin 23.04.2019 20:26

Я пытался, но id не сработал, я думаю, что сделаю пакет, который обрабатывает такие отношения.

Manu 23.04.2019 20:39

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

Это не работает, потому что в отношениях, которые вы добавили в модели 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          |
+----+------------+------------+

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

Ответ принят как подходящий

Я нашел решение, и я решил это так.

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

Другие вопросы по теме